// mscomm.c - MSComm VBX control implementation // // Provides high-speed serial communications for 16-bit Visual Basic 4. // Implements MODEL, control procedure, property/event handling, and // WM_COMMNOTIFY dispatch via hidden notification windows. // // IMPORTANT: VBDerefControl() returns a pointer into VB's compacting heap. // Any VB API call (VBCreateHsz, VBDestroyHsz, VBFireEvent, etc.) can // trigger heap compaction and invalidate the pointer. All code must // re-deref after such calls before accessing control data again. #include #include #include "vbapi.h" #include "serial.h" #include "mscomm.h" // ----------------------------------------------------------------------- // Global data // ----------------------------------------------------------------------- HANDLE ghInstance = NULL; // ----------------------------------------------------------------------- // Convenience macro for re-dereferencing control data after VB API calls // ----------------------------------------------------------------------- #define DEREF(hctl) ((MsCommDataT FAR *)VBDerefControl(hctl)) // ----------------------------------------------------------------------- // Forward declarations - Control procedure // ----------------------------------------------------------------------- LONG FAR PASCAL _export MsCommCtlProc(HCTL hctl, HWND hwnd, USHORT msg, USHORT wp, LONG lp); LRESULT FAR PASCAL _export NotifyWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); // ----------------------------------------------------------------------- // Forward declarations - Internal functions (alphabetical) // ----------------------------------------------------------------------- static void closePort(HCTL hctl, MsCommDataT FAR *pData); static void fireOnComm(HCTL hctl, MsCommDataT FAR *pData, int16_t eventCode); static LONG handleGetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp); static LONG handleSetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp, LONG lp); static void initControlData(HCTL hctl); static int16_t openPort(HCTL hctl, MsCommDataT FAR *pData); static void processEventNotify(HCTL hctl, int16_t commId); static void processReceiveNotify(HCTL hctl, int16_t commId); static void processTransmitNotify(HCTL hctl, int16_t commId); static BOOL registerNotifyClass(HANDLE hInstance); // ----------------------------------------------------------------------- // Forward declarations - DLL entry points // ----------------------------------------------------------------------- int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine); BOOL FAR PASCAL _export VBINITCC(USHORT usVersion, BOOL fRuntime); void FAR PASCAL _export VBTERMCC(void); void FAR PASCAL _export WEP(int nParam); // ----------------------------------------------------------------------- // Enum value lists (null-separated, double-null terminated by compiler) // ----------------------------------------------------------------------- static char szHandshaking[] = "None\0XonXoff\0RtsCts\0Both"; static char szInputMode[] = "Text\0Binary"; // ----------------------------------------------------------------------- // Property descriptors // ----------------------------------------------------------------------- // offset default enum list enumMax static PROPINFO propCommPort = { "CommPort", PF_fSetMsg | PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, commPort), 0, 1L, NULL, 0 }; static PROPINFO propSettings = { "Settings", PF_fSetMsg | PF_fGetMsg | DT_HSZ, 0, 0, 0L, NULL, 0 }; static PROPINFO propPortOpen = { "PortOpen", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, portOpen), 0, 0L, NULL, 0 }; static PROPINFO propInput = { "Input", PF_fGetMsg | PF_fNoShow | DT_HSZ, 0, 0, 0L, NULL, 0 }; static PROPINFO propOutput = { "Output", PF_fSetMsg | PF_fNoShow | DT_HSZ, 0, 0, 0L, NULL, 0 }; static PROPINFO propInBufferSize = { "InBufferSize", PF_fSetMsg | PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, inBufferSize), 0, 4096L, NULL, 0 }; static PROPINFO propOutBufferSize = { "OutBufferSize", PF_fSetMsg | PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, outBufferSize), 0, 4096L, NULL, 0 }; static PROPINFO propInBufferCount = { "InBufferCount", PF_fGetMsg | PF_fNoShow | DT_SHORT, 0, 0, 0L, NULL, 0 }; static PROPINFO propOutBufferCount= { "OutBufferCount",PF_fGetMsg | PF_fNoShow | DT_SHORT, 0, 0, 0L, NULL, 0 }; static PROPINFO propRThreshold = { "RThreshold", PF_fSetData| PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, rThreshold), 0, 0L, NULL, 0 }; static PROPINFO propSThreshold = { "SThreshold", PF_fSetData| PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, sThreshold), 0, 0L, NULL, 0 }; static PROPINFO propHandshaking = { "Handshaking", PF_fSetMsg | PF_fGetData | DT_ENUM, OFFSETIN(MsCommDataT, handshaking), 0, 0L, szHandshaking, 3 }; static PROPINFO propInputLen = { "InputLen", PF_fSetData| PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, inputLen), 0, 0L, NULL, 0 }; static PROPINFO propInputMode = { "InputMode", PF_fSetData| PF_fGetData | DT_ENUM, OFFSETIN(MsCommDataT, inputMode), 0, 0L, szInputMode, 1 }; static PROPINFO propDTREnable = { "DTREnable", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, dtrEnable), 0, 1L, NULL, 0 }; static PROPINFO propRTSEnable = { "RTSEnable", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, rtsEnable), 0, 1L, NULL, 0 }; static PROPINFO propCDHolding = { "CDHolding", PF_fGetMsg | PF_fNoShow | DT_BOOL, 0, 0, 0L, NULL, 0 }; static PROPINFO propCTSHolding = { "CTSHolding", PF_fGetMsg | PF_fNoShow | DT_BOOL, 0, 0, 0L, NULL, 0 }; static PROPINFO propDSRHolding = { "DSRHolding", PF_fGetMsg | PF_fNoShow | DT_BOOL, 0, 0, 0L, NULL, 0 }; static PROPINFO propBreak = { "Break", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, breakState), 0, 0L, NULL, 0 }; static PROPINFO propCommEvent = { "CommEvent", PF_fGetData| PF_fNoShow | DT_SHORT, OFFSETIN(MsCommDataT, commEvent), 0, 0L, NULL, 0 }; static PROPINFO propNullDiscard = { "NullDiscard", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, nullDiscard), 0, 0L, NULL, 0 }; static PROPINFO propEOFEnable = { "EOFEnable", PF_fSetData| PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, eofEnable), 0, 0L, NULL, 0 }; static PROPINFO propParityReplace = { "ParityReplace", PF_fSetMsg | PF_fGetMsg | DT_HSZ, 0, 0, 0L, NULL, 0 }; // ----------------------------------------------------------------------- // Property list (NULL-terminated, order must match IPROP_* indices) // ----------------------------------------------------------------------- static PPROPINFO propList[] = { PPROPINFO_STD_CTLNAME, // 0 - Name PPROPINFO_STD_INDEX, // 1 - Index PPROPINFO_STD_TAG, // 2 - Tag &propCommPort, // 3 &propSettings, // 4 &propPortOpen, // 5 &propInput, // 6 &propOutput, // 7 &propInBufferSize, // 8 &propOutBufferSize, // 9 &propInBufferCount, // 10 &propOutBufferCount, // 11 &propRThreshold, // 12 &propSThreshold, // 13 &propHandshaking, // 14 &propInputLen, // 15 &propInputMode, // 16 &propDTREnable, // 17 &propRTSEnable, // 18 &propCDHolding, // 19 &propCTSHolding, // 20 &propDSRHolding, // 21 &propBreak, // 22 &propCommEvent, // 23 &propNullDiscard, // 24 &propEOFEnable, // 25 &propParityReplace, // 26 NULL }; // ----------------------------------------------------------------------- // Event descriptors // ----------------------------------------------------------------------- static EVENTINFO eventOnComm = { "OnComm", 0, 0, NULL, "", 0 }; // ----------------------------------------------------------------------- // Event list (NULL-terminated) // ----------------------------------------------------------------------- static PEVENTINFO eventList[] = { &eventOnComm, // 0 - OnComm NULL }; // ----------------------------------------------------------------------- // MODEL definition // ----------------------------------------------------------------------- static char szCtlName[] = "MSComm"; MODEL modelMsComm = { VB300_VERSION, // usVersion MODEL_fInitMsg | MODEL_fInvisAtRun,// fl (PCTLPROC)MsCommCtlProc, // pctlproc 0, // fsClassStyle WS_CHILD, // flWndStyle sizeof(MsCommDataT), // cbCtlExtra IDB_MSCOMM, // idBmpPalette szCtlName, // npszDefCtlName NULL, // npszClassName NULL, // npszParentClassName propList, // npproplist eventList, // npeventlist IPROP_COMMPORT, // nDefProp IEVENT_ONCOMM, // nDefEvent 0, // nValueProp 0x0100 // usCtlVersion (1.00) }; // ======================================================================= // Internal helper functions (alphabetical) // ======================================================================= // ----------------------------------------------------------------------- // closePort - Close the serial port and destroy notification window // // No VB API calls here, so pData remains valid throughout. // ----------------------------------------------------------------------- static void closePort(HCTL hctl, MsCommDataT FAR *pData) { if (pData->commId >= 0) { if (pData->breakState) { serialEscape(pData->commId, SERIAL_CLRBREAK); pData->breakState = FALSE; } serialClose(pData->commId); pData->commId = -1; } if (pData->hwndNotify != NULL) { DestroyWindow(pData->hwndNotify); pData->hwndNotify = NULL; } pData->portOpen = FALSE; pData->ctsState = FALSE; pData->dsrState = FALSE; pData->cdState = FALSE; } // ----------------------------------------------------------------------- // fireOnComm - Set commEvent and fire OnComm event to VB // // WARNING: After this function returns, any previously obtained pData // pointer is INVALID. Callers must re-deref via DEREF(hctl). // ----------------------------------------------------------------------- static void fireOnComm(HCTL hctl, MsCommDataT FAR *pData, int16_t eventCode) { pData->commEvent = eventCode; VBFireEvent(hctl, IEVENT_ONCOMM, NULL); // pData is now stale - caller must re-deref } // ----------------------------------------------------------------------- // handleGetProperty - Process VBM_GETPROPERTY for message-based props // ----------------------------------------------------------------------- static LONG handleGetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp) { switch (iProp) { case IPROP_SETTINGS: return (LONG)pData->hszSettings; case IPROP_INPUT: { COMSTAT stat; int16_t bytesToRead; int16_t bytesRead; int16_t commId; int16_t inputLen; HGLOBAL hMem; char FAR *buf; HSZ hsz; if (!pData->portOpen || pData->commId < 0) { return (LONG)VBCreateHsz((_segment)0, ""); } // Save values to locals before any VB API calls commId = pData->commId; inputLen = pData->inputLen; serialGetStatus(commId, &stat); bytesToRead = (int16_t)stat.cbInQue; if (inputLen > 0 && bytesToRead > inputLen) { bytesToRead = inputLen; } if (bytesToRead <= 0) { return (LONG)VBCreateHsz((_segment)0, ""); } // Use GlobalAlloc for potentially large buffers (DLL local heap is small) hMem = GlobalAlloc(GMEM_MOVEABLE, (DWORD)bytesToRead + 1); if (hMem == NULL) { return (LONG)VBCreateHsz((_segment)0, ""); } buf = (char FAR *)GlobalLock(hMem); bytesRead = serialRead(commId, buf, bytesToRead); if (bytesRead <= 0) { GlobalUnlock(hMem); GlobalFree(hMem); return (LONG)VBCreateHsz((_segment)0, ""); } buf[bytesRead] = '\0'; hsz = VBCreateHsz((_segment)0, buf); GlobalUnlock(hMem); GlobalFree(hMem); // pData is stale after VBCreateHsz, but we just return the HSZ return (LONG)hsz; } case IPROP_INBUFFERCOUNT: { COMSTAT stat; if (!pData->portOpen || pData->commId < 0) { return 0L; } serialGetStatus(pData->commId, &stat); return (LONG)(int16_t)stat.cbInQue; } case IPROP_OUTBUFFERCOUNT: { COMSTAT stat; if (!pData->portOpen || pData->commId < 0) { return 0L; } serialGetStatus(pData->commId, &stat); return (LONG)(int16_t)stat.cbOutQue; } // Modem line status: return shadow state maintained by processEventNotify. // The 16-bit comm API only provides transition events (GetCommEventMask), // not current line levels. Shadow state is toggled on each transition. case IPROP_CDHOLDING: return (LONG)(BOOL)pData->cdState; case IPROP_CTSHOLDING: return (LONG)(BOOL)pData->ctsState; case IPROP_DSRHOLDING: return (LONG)(BOOL)pData->dsrState; case IPROP_PARITYREPLACE: return (LONG)pData->hszParityReplace; } return 0L; } // ----------------------------------------------------------------------- // handleSetProperty - Process VBM_SETPROPERTY for message-based props // ----------------------------------------------------------------------- static LONG handleSetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp, LONG lp) { int16_t val; switch (iProp) { case IPROP_COMMPORT: val = (int16_t)lp; if (val < 1 || val > 16) { return (LONG)ERR_InvPropVal; } if (pData->portOpen) { return (LONG)ERR_InvPropSet; } pData->commPort = val; return 0L; case IPROP_SETTINGS: { HSZ oldHsz; HSZ newHsz; LPSTR lpstr; int16_t commId; int16_t port; // Deref the incoming HSZ (VBDerefHsz does not compact) lpstr = VBDerefHsz((HSZ)lp); if (lpstr == NULL) { return (LONG)ERR_InvPropVal; } // Save old HSZ, create new one oldHsz = pData->hszSettings; newHsz = VBCreateHsz((_segment)0, lpstr); // pData may be stale after VBCreateHsz - re-deref pData = DEREF(hctl); pData->hszSettings = newHsz; if (oldHsz) { VBDestroyHsz(oldHsz); // pData may be stale after VBDestroyHsz - re-deref pData = DEREF(hctl); } // If port is open, reconfigure immediately if (pData->portOpen && pData->commId >= 0) { commId = pData->commId; port = pData->commPort; lpstr = VBDerefHsz(pData->hszSettings); if (serialConfigure(commId, port, lpstr) != 0) { return (LONG)ERR_InvPropVal; } } return 0L; } case IPROP_PORTOPEN: if ((BOOL)lp) { if (pData->portOpen) { return 0L; } if (openPort(hctl, pData) != 0) { return (LONG)ERR_InvPropVal; } } else { if (!pData->portOpen) { return 0L; } closePort(hctl, pData); } return 0L; case IPROP_OUTPUT: { int16_t commId; LPSTR lpstr; if (!pData->portOpen || pData->commId < 0) { return (LONG)ERR_InvPropSet; } // Save commId before VB API call commId = pData->commId; lpstr = VBDerefHsz((HSZ)lp); if (lpstr != NULL) { int16_t len = (int16_t)lstrlen(lpstr); if (len > 0) { int16_t written = serialWrite(commId, lpstr, len); if (written < 0) { // Re-deref before fireOnComm since pData may be stale pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EVT_TXFULL); // pData stale after fireOnComm, but we just return } } } return 0L; } case IPROP_INBUFFERSIZE: val = (int16_t)lp; if (val < 64) { return (LONG)ERR_InvPropVal; } if (pData->portOpen) { return (LONG)ERR_InvPropSet; } pData->inBufferSize = val; return 0L; case IPROP_OUTBUFFERSIZE: val = (int16_t)lp; if (val < 64) { return (LONG)ERR_InvPropVal; } if (pData->portOpen) { return (LONG)ERR_InvPropSet; } pData->outBufferSize = val; return 0L; case IPROP_HANDSHAKING: pData->handshaking = (int16_t)lp; if (pData->portOpen && pData->commId >= 0) { serialSetHandshaking(pData->commId, pData->handshaking); } return 0L; case IPROP_DTRENABLE: pData->dtrEnable = (BOOL)lp; if (pData->portOpen && pData->commId >= 0) { serialEscape(pData->commId, pData->dtrEnable ? SERIAL_SETDTR : SERIAL_CLRDTR); } return 0L; case IPROP_RTSENABLE: pData->rtsEnable = (BOOL)lp; if (pData->portOpen && pData->commId >= 0) { serialEscape(pData->commId, pData->rtsEnable ? SERIAL_SETRTS : SERIAL_CLRRTS); } return 0L; case IPROP_BREAK: pData->breakState = (BOOL)lp; if (pData->portOpen && pData->commId >= 0) { serialEscape(pData->commId, pData->breakState ? SERIAL_SETBREAK : SERIAL_CLRBREAK); } return 0L; case IPROP_NULLDISCARD: { int16_t commId; BOOL nullDiscard; LPSTR lpstr; pData->nullDiscard = (BOOL)lp; if (pData->portOpen && pData->commId >= 0) { // Save locals before VBDerefHsz commId = pData->commId; nullDiscard = pData->nullDiscard; lpstr = VBDerefHsz(pData->hszParityReplace); serialSetOptions(commId, nullDiscard, lpstr); } return 0L; } case IPROP_PARITYREPLACE: { HSZ oldHsz; HSZ newHsz; int16_t commId; BOOL nullDiscard; LPSTR lpstr; lpstr = VBDerefHsz((HSZ)lp); oldHsz = pData->hszParityReplace; newHsz = VBCreateHsz((_segment)0, lpstr ? lpstr : ""); // Re-deref after VBCreateHsz pData = DEREF(hctl); pData->hszParityReplace = newHsz; if (oldHsz) { VBDestroyHsz(oldHsz); // Re-deref after VBDestroyHsz pData = DEREF(hctl); } if (pData->portOpen && pData->commId >= 0) { commId = pData->commId; nullDiscard = pData->nullDiscard; lpstr = VBDerefHsz(pData->hszParityReplace); serialSetOptions(commId, nullDiscard, lpstr); } return 0L; } } return 0L; } // ----------------------------------------------------------------------- // initControlData - Initialize per-instance control data // // Re-derefs after each VBCreateHsz to avoid stale pointers. // ----------------------------------------------------------------------- static void initControlData(HCTL hctl) { MsCommDataT FAR *pData; HSZ hsz; pData = DEREF(hctl); pData->commId = -1; pData->hwndNotify = NULL; pData->commPort = 1; pData->inBufferSize = 4096; pData->outBufferSize = 4096; pData->rThreshold = 0; pData->sThreshold = 0; pData->handshaking = HS_NONE; pData->inputLen = 0; pData->inputMode = INPUT_TEXT; pData->commEvent = 0; pData->portOpen = FALSE; pData->dtrEnable = TRUE; pData->rtsEnable = TRUE; pData->breakState = FALSE; pData->nullDiscard = FALSE; pData->eofEnable = FALSE; pData->ctsState = FALSE; pData->dsrState = FALSE; pData->cdState = FALSE; // VBCreateHsz may compact VB's heap - re-deref after each call hsz = VBCreateHsz((_segment)0, "9600,N,8,1"); pData = DEREF(hctl); pData->hszSettings = hsz; hsz = VBCreateHsz((_segment)0, "?"); pData = DEREF(hctl); pData->hszParityReplace = hsz; } // ----------------------------------------------------------------------- // openPort - Open serial port and set up notification window // // Saves all needed control data to locals before VB API calls to avoid // stale pointer issues. Re-derefs when writing results back. // ----------------------------------------------------------------------- static int16_t openPort(HCTL hctl, MsCommDataT FAR *pData) { int16_t commId; int16_t port; int16_t inBufSize; int16_t outBufSize; int16_t handshaking; int16_t rThreshold; int16_t sThreshold; BOOL dtrEnable; BOOL rtsEnable; BOOL nullDiscard; HSZ hszSettings; HSZ hszParityReplace; HWND hwndNotify; LPSTR lpstr; // Snapshot all values we need (pData may become stale after VB API calls) port = pData->commPort; inBufSize = pData->inBufferSize; outBufSize = pData->outBufferSize; handshaking = pData->handshaking; rThreshold = pData->rThreshold; sThreshold = pData->sThreshold; dtrEnable = pData->dtrEnable; rtsEnable = pData->rtsEnable; nullDiscard = pData->nullDiscard; hszSettings = pData->hszSettings; hszParityReplace = pData->hszParityReplace; // Open the comm port commId = serialOpen(port, inBufSize, outBufSize); if (commId < 0) { return -1; } // Configure baud/parity/data/stop lpstr = VBDerefHsz(hszSettings); if (serialConfigure(commId, port, lpstr) != 0) { serialClose(commId); return -1; } // Apply handshaking serialSetHandshaking(commId, handshaking); // Apply null-discard and parity-replace lpstr = VBDerefHsz(hszParityReplace); serialSetOptions(commId, nullDiscard, lpstr); // Set DTR and RTS lines serialEscape(commId, dtrEnable ? SERIAL_SETDTR : SERIAL_CLRDTR); serialEscape(commId, rtsEnable ? SERIAL_SETRTS : SERIAL_CLRRTS); // Create hidden notification window hwndNotify = CreateWindow( NOTIFY_CLASS, "", WS_POPUP, 0, 0, 0, 0, NULL, NULL, ghInstance, NULL ); if (hwndNotify == NULL) { serialClose(commId); return -1; } // Store HCTL in notification window for message dispatch SetWindowWord(hwndNotify, 0, (WORD)hctl); // Enable comm notifications serialEnableNotify(commId, hwndNotify, rThreshold > 0 ? rThreshold : -1, sThreshold > 0 ? sThreshold : -1); // Re-deref to write results back to control data pData = DEREF(hctl); pData->commId = commId; pData->hwndNotify = hwndNotify; pData->portOpen = TRUE; pData->ctsState = FALSE; pData->dsrState = FALSE; pData->cdState = FALSE; return 0; } // ----------------------------------------------------------------------- // processEventNotify - Handle CN_EVENT notification // // Takes commId as a stable local value. Re-derefs pData after every // fireOnComm call since VBFireEvent can trigger heap compaction. // Updates modem line shadow state on CTS/DSR/CD transitions. // ----------------------------------------------------------------------- static void processEventNotify(HCTL hctl, int16_t commId) { MsCommDataT FAR *pData; UINT evtMask; evtMask = serialGetEventMask(commId, EV_CTS | EV_DSR | EV_RLSD | EV_RING | EV_ERR | EV_BREAK); if (evtMask & EV_BREAK) { pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EVT_BREAK); } if (evtMask & EV_CTS) { pData = DEREF(hctl); pData->ctsState = !pData->ctsState; fireOnComm(hctl, pData, COM_EV_CTS); } if (evtMask & EV_DSR) { pData = DEREF(hctl); pData->dsrState = !pData->dsrState; fireOnComm(hctl, pData, COM_EV_DSR); } if (evtMask & EV_RLSD) { pData = DEREF(hctl); pData->cdState = !pData->cdState; fireOnComm(hctl, pData, COM_EV_CD); } if (evtMask & EV_RING) { pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EV_RING); } if (evtMask & EV_ERR) { COMSTAT stat; int16_t errFlags; errFlags = serialGetStatus(commId, &stat); if (errFlags & CE_FRAME) { pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EVT_FRAME); } if (errFlags & CE_OVERRUN) { pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EVT_OVERRUN); } if (errFlags & CE_RXOVER) { pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EVT_RXOVER); } if (errFlags & CE_RXPARITY) { pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EVT_RXPARITY); } if (errFlags & CE_TXFULL) { pData = DEREF(hctl); fireOnComm(hctl, pData, COM_EVT_TXFULL); } } } // ----------------------------------------------------------------------- // processReceiveNotify - Handle CN_RECEIVE notification // ----------------------------------------------------------------------- static void processReceiveNotify(HCTL hctl, int16_t commId) { MsCommDataT FAR *pData; COMSTAT stat; pData = DEREF(hctl); if (pData->rThreshold <= 0) { return; } serialGetStatus(commId, &stat); if ((int16_t)stat.cbInQue >= pData->rThreshold) { fireOnComm(hctl, pData, COM_EV_RECEIVE); // pData stale after fireOnComm, but we just return } } // ----------------------------------------------------------------------- // processTransmitNotify - Handle CN_TRANSMIT notification // ----------------------------------------------------------------------- static void processTransmitNotify(HCTL hctl, int16_t commId) { MsCommDataT FAR *pData; COMSTAT stat; pData = DEREF(hctl); if (pData->sThreshold <= 0) { return; } serialGetStatus(commId, &stat); if ((int16_t)stat.cbOutQue <= pData->sThreshold) { fireOnComm(hctl, pData, COM_EV_SEND); // pData stale after fireOnComm, but we just return } } // ----------------------------------------------------------------------- // registerNotifyClass - Register hidden notification window class // ----------------------------------------------------------------------- static BOOL registerNotifyClass(HANDLE hInstance) { WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = NotifyWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(WORD); // Room for HCTL wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = NOTIFY_CLASS; return RegisterClass(&wc); } // ======================================================================= // Hidden notification window procedure // // Receives WM_COMMNOTIFY from the comm driver. Saves commId to a local // variable and passes it to process* functions, which independently // deref the control data (avoiding stale pointer chains across VBFireEvent). // ======================================================================= LRESULT FAR PASCAL _export NotifyWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { if (msg == WM_COMMNOTIFY) { HCTL hctl; MsCommDataT FAR *pData; int16_t commId; UINT notifyCode; hctl = (HCTL)GetWindowWord(hwnd, 0); if (hctl == 0) { return 0L; } pData = DEREF(hctl); if (pData == NULL || pData->commId < 0) { return 0L; } // Save commId to local - stable across VBFireEvent calls commId = pData->commId; notifyCode = LOWORD(lp); // Each process* function independently derefs and re-derefs as needed if (notifyCode & CN_RECEIVE) { processReceiveNotify(hctl, commId); } if (notifyCode & CN_TRANSMIT) { processTransmitNotify(hctl, commId); } if (notifyCode & CN_EVENT) { processEventNotify(hctl, commId); } return 0L; } return DefWindowProc(hwnd, msg, wp, lp); } // ======================================================================= // VBX Control procedure // ======================================================================= LONG FAR PASCAL _export MsCommCtlProc(HCTL hctl, HWND hwnd, USHORT msg, USHORT wp, LONG lp) { MsCommDataT FAR *pData; switch (msg) { case VBM_INITIALIZE: initControlData(hctl); return 0L; case VBM_SETPROPERTY: pData = DEREF(hctl); return handleSetProperty(hctl, pData, wp, lp); case VBM_GETPROPERTY: pData = DEREF(hctl); return handleGetProperty(hctl, pData, wp); case WM_DESTROY: { HSZ hszSettings; HSZ hszParityReplace; pData = DEREF(hctl); // Close port if open if (pData->portOpen) { closePort(hctl, pData); } // Save HSZ handles to locals, then destroy them. // VBDestroyHsz may compact the heap, so don't access pData after. hszSettings = pData->hszSettings; hszParityReplace = pData->hszParityReplace; pData->hszSettings = 0; pData->hszParityReplace = 0; if (hszSettings) { VBDestroyHsz(hszSettings); } if (hszParityReplace) { VBDestroyHsz(hszParityReplace); } break; } } return VBDefControlProc(hctl, hwnd, msg, wp, lp); } // ======================================================================= // DLL entry points // ======================================================================= // ----------------------------------------------------------------------- // LibMain - DLL initialization // ----------------------------------------------------------------------- int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine) { ghInstance = hInstance; if (wHeapSize > 0) { UnlockData(0); } return 1; } // ----------------------------------------------------------------------- // VBINITCC - Called by VB to register the control // ----------------------------------------------------------------------- BOOL FAR PASCAL _export VBINITCC(USHORT usVersion, BOOL fRuntime) { if (!registerNotifyClass(ghInstance)) { return FALSE; } return VBRegisterModel(ghInstance, &modelMsComm); } // ----------------------------------------------------------------------- // VBTERMCC - Called by VB when unloading the control // ----------------------------------------------------------------------- void FAR PASCAL _export VBTERMCC(void) { UnregisterClass(NOTIFY_CLASS, ghInstance); } // ----------------------------------------------------------------------- // WEP - DLL termination (required for 16-bit Windows DLLs) // ----------------------------------------------------------------------- void FAR PASCAL _export WEP(int nParam) { (void)nParam; }