935 lines
32 KiB
C
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;
|
|
}
|