WinComm/vbx/mscomm.c
2026-02-23 20:53:02 -06:00

935 lines
32 KiB
C

// 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 <windows.h>
#include <string.h>
#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;
}