首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

MFC WinSock ActiveX控件開發實戰(二):高級功能——數據收發、超時控制、VC/VB雙環境調用

     編輯:關於VC++

承接上篇《標準MFC WinSock ActiveX控件開發實例》,本文完善控件核心通訊能力:實現 SendData / GetData 兩大核心介面,支援多種資料型別(字串、位元組陣列、整數/長整數/浮點數陣列)、網路位序轉換、select非阻塞超時控制,並詳細示範 VC++ 與 VB6 兩種環境下的控件調用完整代碼。

核心介面設計


// 發送數據:指定超時、資料型別、長度,返回實際發送位元組數或錯誤碼
long SendData(
    const VARIANT FAR& Data,        // 發送緩衝區
    const VARIANT FAR& DataType,    // 型別代碼 0-7
    const VARIANT FAR& DataLength,  // 發送長度(以char為單位)
    const VARIANT FAR& TimeOut      // 超時秒數
);

// 接收數據:指定最大接收長度、超時,返回實際接收長度
long GetData(
    VARIANT FAR* Data,              // 接收緩衝區(輸出參數)
    const VARIANT FAR& DataType,    // 型別代碼
    const VARIANT FAR& DataMaxLength, // 最大接收長度
    const VARIANT FAR& TimeOut      // 超時秒數
);

DataType 型別代碼對照表

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 客戶端調用範例

專案建立與控件加入

  1. 新建 MFC Dialog 專案 TestClient
  2. Project → Add To Project → Components and Controls → Registered ActiveX Controls 選擇 MFCWinSock Control
  3. 對話框放入控件,ID 為 IDC_MFCWINSOCKCTRL1

VC專案加入控件

事件處理與核心代碼


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("伺服器斷開連線"));
}

VC事件處理

VB 6.0 客戶端調用範例

VB 對 VARIANT/SAFEARRAY 有自動封送,調用極簡潔:

VB專案加入控件

VB事件選擇


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 安全警告

擴充方向

  1. 伺服器模式:加入 Listen(port)、Accept()、OnAccept 事件
  2. UDP 支援:SendTo/RecvFrom、廣播、多播
  3. SSL/TLS:整合 OpenSSL,提供 ConnectSSL、憑證驗證回調
  4. 非同步完成通知:IOCP 或 WSAEventSelect 替代 WSAAsyncSelect
  5. 封包協議框架:長度前綴、分隔符、Protobuf 整合

總結

本控件實現了 型別安全的網路資料收發、應用層超時控制、跨語言二進制相容(VC/VB/Scripting Host 均可用)。核心難點在於 VARIANT/SAFEARRAY 正確封送、網路位序轉換、非阻塞 I/O 模式。掌握此模式,可快速構建各類 ActiveX 網路元件(HTTP、Modbus、自定義協議)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
代碼C++型別轉換規則(發送端→網路位元組流)
0預設int 陣列直接按 char* 傳送(可能截斷)
1long 陣列每長整數拆為 4 bytes(高位在前,Big Endian)
2int 陣列每整數拆為 4 bytes
3unsigned short 陣列每無號短整數拆為 2 bytes
4BYTE 陣列1:1 直接傳送
5short 陣列每短整數拆為 2 bytes
6float 陣列每浮點數拆為 4 bytes (IEEE 754)
7double 陣列每雙精度拆為 8 bytes