| 代碼 | C++型別 | 轉換規則(發送端→網路位元組流) |
| 0 | 預設 | int 陣列直接按 char* 傳送(可能截斷) |
| 1 | long 陣列 | 每長整數拆為 4 bytes(高位在前,Big Endian) |
| 2 | int 陣列 | 每整數拆為 4 bytes |
| 3 | unsigned short 陣列 | 每無號短整數拆為 2 bytes |
| 4 | BYTE 陣列 | 1:1 直接傳送 |
| 5 | short 陣列 | 每短整數拆為 2 bytes |
| 6 | float 陣列 | 每浮點數拆為 4 bytes (IEEE 754) |
| 7 | double 陣列 | 每雙精度拆為 8 bytes |
SendData 完整實現
long CMFCWinSockCtrl::SendData(const VARIANT FAR& Data, const VARIANT FAR& DataType,
const VARIANT FAR& DataLength, const VARIANT FAR& TimeOut)
{
// 1. 連線檢查
if (!m_bConnected) return -1; // 未建立連線
// 2. 參數提取與驗證
int nDataType = VariantToLong(DataType);
long nDataLen = VariantToLong(DataLength);
int nTimeOut = VariantToLong(TimeOut);
if (nDataType < 0 || nDataLen <= 0 || nTimeOut < 0) return -2;
// 3. select 等待可寫(非阻塞超時)
fd_set fdWrite; FD_ZERO(&fdWrite); FD_SET(m_hSocket, &fdWrite);
timeval tv = {nTimeOut, 0};
if (select(0, NULL, &fdWrite, NULL, &tv) <= 0) return 0; // 超時
if (!FD_ISSET(m_hSocket, &fdWrite)) return 0;
// 4. 根據 VARIANT 型別轉換為位元組緩衝區
char* pBuffer = new char[nDataLen];
VARIANT vData; VariantInit(&vData);
long nSent = 0;
try {
switch (Data.vt) {
case VT_BSTR: { // BSTR 字串
char* psz = _com_util::ConvertBSTRToString(Data.bstrVal); // 自動處理中文
memcpy(pBuffer, psz, min(nDataLen, (long)strlen(psz)));
delete[] psz;
break;
}
case VT_BYREF | VT_UI1: { // BYTE*
memcpy(pBuffer, Data.pbVal, nDataLen);
break;
}
case VT_BYREF | VT_I1: { // char*
memcpy(pBuffer, Data.pcVal, nDataLen);
break;
}
case VT_ARRAY | VT_I4: { // long 陣列 (SAFEARRAY)
vData.vt = VT_I4;
if (nDataType != 0) { // 按 4 bytes 展開
for (long i=0, m=0; i < nDataLen/4; i++, m+=4) {
SafeArrayGetElement(Data.parray, &i, &vData.lVal);
pBuffer[m] = (vData.lVal >> 24) & 0xFF;
pBuffer[m+1] = (vData.lVal >> 16) & 0xFF;
pBuffer[m+2] = (vData.lVal >> 8) & 0xFF;
pBuffer[m+3] = vData.lVal & 0xFF;
}
} else { // 直接截斷
for (long i=0; i < nDataLen; i++) {
SafeArrayGetElement(Data.parray, &i, &vData.lVal);
pBuffer[i] = (char)vData.lVal;
}
}
break;
}
case VT_ARRAY | VT_INT: { // int 陣列
vData.vt = VT_INT;
if (nDataType != 0) {
for (long i=0, m=0; i < nDataLen/4; i++, m+=4) {
SafeArrayGetElement(Data.parray, &i, &vData.intVal);
pBuffer[m] = (vData.intVal >> 24) & 0xFF;
pBuffer[m+1] = (vData.intVal >> 16) & 0xFF;
pBuffer[m+2] = (vData.intVal >> 8) & 0xFF;
pBuffer[m+3] = vData.intVal & 0xFF;
}
} else { /* 截斷 */ }
break;
}
case VT_ARRAY | VT_UI1: { // BYTE 陣列
vData.vt = VT_UI1;
for (long i=0; i < nDataLen; i++) {
SafeArrayGetElement(Data.parray, &i, &vData.bVal);
pBuffer[i] = vData.bVal;
}
break;
}
default:
delete[] pBuffer; VariantClear(&vData); return -3; // 不支援型別
}
// 5. 發送
nSent = send(m_hSocket, pBuffer, nDataLen, 0);
if (nSent <= 0) nSent = -101; // 對方斷開或錯誤
} catch (...) {
nSent = -99; // 例外
}
delete[] pBuffer;
VariantClear(&vData);
return nSent;
}
關鍵技術點
- _com_util::ConvertBSTRToString:自動處理 Unicode→ANSI 轉換,解決中文亂碼。需
#include <comutil.h> 並連結 comsupp.lib
- 網路位序:採用 Big Endian(高位在前),跨平台相容。移位操作:
(val>>24)&0xFF 取最高位元組
- SAFEARRAY 遍歷:
SafeArrayGetElement / SafeArrayPutElement 配合索引陣列
- select 非阻塞:避免發送/接收無限阻塞,實現應用層超時控制
GetData 完整實現
long CMFCWinSockCtrl::GetData(VARIANT FAR* Data, const VARIANT FAR& DataType,
const VARIANT FAR& DataMaxLength, const VARIANT FAR& TimeOut)
{
if (!m_bConnected) return -1;
int nDataType = VariantToLong(DataType);
long nMaxLen = VariantToLong(DataMaxLength);
int nTimeOut = VariantToLong(TimeOut);
if (nDataType < 0 || nMaxLen <= 0 || nTimeOut < 0) return -2;
// select 等待可讀
fd_set fdRead; FD_ZERO(&fdRead); FD_SET(m_hSocket, &fdRead);
timeval tv = {nTimeOut, 0};
if (select(0, &fdRead, NULL, NULL, &tv) <= 0) return 0;
if (!FD_ISSET(m_hSocket, &fdRead)) return 0;
// 接收到臨時緩衝區
char* pBuffer = new char[nMaxLen + 1];
memset(pBuffer, 0, nMaxLen + 1);
int nRecv = recv(m_hSocket, pBuffer, nMaxLen, 0);
if (nRecv <= 0) { delete[] pBuffer; return -102; }
if (nRecv < nMaxLen) nMaxLen = nRecv; // 實際長度
VARIANT vData; VariantInit(&vData);
long nResult = 0;
try {
switch (Data->vt) {
case VT_BSTR: { // 輸出 BSTR
pBuffer[nMaxLen] = '\0';
Data->bstrVal = _com_util::ConvertStringToBSTR(pBuffer);
break;
}
case VT_BYREF | VT_UI1: // BYTE*
memcpy(Data->pbVal, pBuffer, nMaxLen);
break;
case VT_BYREF | VT_I1: // char*
memcpy(Data->pcVal, pBuffer, nMaxLen);
break;
case VT_BYREF | VT_I4: // long* 陣列指標
for (long i=0; i < nMaxLen; i++)
Data->plVal[i] = (long)(unsigned char)pBuffer[i];
break;
case VT_ARRAY | VT_I4: { // long SAFEARRAY
vData.vt = VT_I4;
if (nDataType != 0) { // 4 bytes 合成一個 long
for (long m=0, i=0; i < nMaxLen; i+=4, m++) {
vData.lVal = ((pBuffer[m]&0xFF)<<24) | ((pBuffer[m+1]&0xFF)<<16) |
((pBuffer[m+2]&0xFF)<<8) | (pBuffer[m+3]&0xFF);
SafeArrayPutElement(Data->parray, &i, &vData.lVal);
}
} else { // 1 byte 1 long
for (long i=0; i < nMaxLen; i++) {
vData.lVal = (long)(unsigned char)pBuffer[i];
SafeArrayPutElement(Data->parray, &i, &vData.lVal);
}
}
break;
}
case VT_ARRAY | VT_INT: { // int SAFEARRAY
vData.vt = VT_INT;
if (nDataType != 0) { /* 同 long 4 bytes 合成 */ }
else { /* 1 byte 1 int */ }
break;
}
case VT_ARRAY | VT_UI1: { // BYTE SAFEARRAY
vData.vt = VT_UI1;
for (long i=0; i < nMaxLen; i++) {
vData.bVal = pBuffer[i] & 0xFF;
SafeArrayPutElement(Data->parray, &i, &vData.bVal);
}
break;
}
default:
delete[] pBuffer; VariantClear(&vData); return -3;
}
nResult = nMaxLen;
} catch (...) {
nResult = -99;
}
delete[] pBuffer;
VariantClear(&vData);
return nResult;
}
VC++ 6.0 客戶端調用範例
專案建立與控件加入
- 新建 MFC Dialog 專案
TestClient
- Project → Add To Project → Components and Controls → Registered ActiveX Controls 選擇
MFCWinSock Control
- 對話框放入控件,ID 為
IDC_MFCWINSOCKCTRL1

事件處理與核心代碼
class CTestClientDlg : public CDialog {
CMFCWinSockCtrl m_sock; // 控件包裝類
// ...
};
void CTestClientDlg::OnConnect() {
UpdateData(TRUE);
if (!m_sock.Connect(COleVariant(m_strIP), COleVariant(m_nPort)))
AfxMessageBox(_T("連線失敗"));
}
void CTestClientDlg::OnRecvSockEventMfcwinsockctrl1() {
// 建立接收長整數 SAFEARRAY
SAFEARRAYBOUND bound = {0, 100}; // 100 elements
VARIANT* pData = new VARIANT; VariantInit(pData);
pData->vt = VT_ARRAY | VT_I4;
pData->parray = SafeArrayCreate(VT_I4, 1, &bound);
long nRecv = m_sock.GetData(pData, COleVariant((long)0),
COleVariant((long)100), COleVariant((long)3));
if (nRecv > 0) {
char szBuf[101] = {0};
long val;
for (long i=0; i < nRecv; i++) {
SafeArrayGetElement(pData->parray, &i, &val);
szBuf[i] = (char)val;
}
AfxMessageBox(CString(szBuf));
}
SafeArrayDestroy(pData->parray);
delete pData;
}
void CTestClientDlg::OnCloseWinsockMfcwinsockctrl1() {
m_sock.DisConnect();
AfxMessageBox(_T("伺服器斷開連線"));
}

VB 6.0 客戶端調用範例
VB 對 VARIANT/SAFEARRAY 有自動封送,調用極簡潔:


Private Sub cmdConnect_Click()
MFCWinSock1.Connect CStr(txtIP.Text), CLng(txtPort.Text)
End Sub
Private Sub cmdSend_Click()
' 發送字串,型別0,長度50,超時3秒
MFCWinSock1.SendData "Welcome to MFC WinSock!", 0, 50, 3
End Sub
Private Sub MFCWinSock1_CloseWinsock()
MFCWinSock1.DisConnect
MsgBox "伺服器斷開連線"
End Sub
Private Sub MFCWinSock1_RecvSockEvent()
Dim data As Variant
Dim arrLong(100) As Long ' 定型陣列
data = arrLong ' Variant 自動變為 VT_ARRAY|VT_I4
Dim l As Long
l = MFCWinSock1.GetData(data, 0, 100, 3)
If l > 0 Then
MsgBox "收到首字元碼: " & data(0)
End If
End Sub
部署與 Release 版本注意事項
- 控件 DLL 需註冊:
regsvr32 MFCWinSock.ocx
- 目標機需安裝 MFC42.dll、MSVCRT.dll(VC6 Runtime)
- Release 版還需隨控件分發:
ago4501.dll、v4501v.dll(若用Matcom)或純 WinSock 則無需
- 數位簽章 .ocx 避免 IE/Edge 安全警告
擴充方向
- 伺服器模式:加入
Listen(port)、Accept()、OnAccept 事件
- UDP 支援:
SendTo/RecvFrom、廣播、多播
- SSL/TLS:整合 OpenSSL,提供
ConnectSSL、憑證驗證回調
- 非同步完成通知:IOCP 或
WSAEventSelect 替代 WSAAsyncSelect
- 封包協議框架:長度前綴、分隔符、Protobuf 整合
總結
本控件實現了 型別安全的網路資料收發、應用層超時控制、跨語言二進制相容(VC/VB/Scripting Host 均可用)。核心難點在於 VARIANT/SAFEARRAY 正確封送、網路位序轉換、非阻塞 I/O 模式。掌握此模式,可快速構建各類 ActiveX 網路元件(HTTP、Modbus、自定義協議)。
- 上一篇文章: 任意數學表達式解析計算引擎設計與實現:支援函數嵌套、多變量、遞歸下降解析
- 下一篇文章: MFC WinSock ActiveX控件開發實戰(一):從專案建立到基礎連線事件實現
Copyright ©
程式碼之家 All Rights Reserved