標準MFC WinSock ActiveX控件開發實例
作者:小輝
摘要:本文主要介紹如何開發一個ActiveX控件,提供接口,與相應事件掛鉤。文中涉及到VARIANT,SAFEARRAY,BSTR的詳細使用方法。
另外還提供了WinSock的詳細開發步驟,以及如何響應網絡超時,網絡斷開的事件方法以及在VC,VB調用該控件的方法。
關鍵字:ActiveX,Socket,VARIANT, SAFEARRAY,BSTR。
一、MFC ActiveX控件開發步驟(VC 6.0):
- New->Projects->MFC ActiveX ControlWizard,然後輸入MFCWinSock工程名。如下圖:
圖一 創建工程 - 一路狂按Next,直至Finsh出現,再按下OK,如下圖:
圖二 創建完成
二、架設Socket環境:
- 首先在StdAfx.h文件中加入下面這句代碼:
#include // MFC socket extensions
- 打開MFCWinSock.cpp文件,添加代碼,看起來如下:
////////////////////////////////////////////////////////////////////////////
// CMFCWinSockApp::InitInstance - DLL initialization
BOOL CMFCWinSockApp::InitInstance()
{
BOOL bInit = COleControlModule::InitInstance();
if (bInit)
{
// TODO: Add your own module initialization code here.
if (!AfxSocketInit())
{
AfxMessageBox("無法初始化Socket,請檢查!");
return FALSE;
}
WSADATA wsaData;
WORD wVersion = MAKEWORD(1, 1);//設定爲Winsock 1.1版
int errCode;
errCode = WSAStartup(wVersion, &wsaData);//啓動Socket服務
if (errCode)
{
AfxMessageBox("無法找到可以使用的 WSOCK32.DLL");
return FALSE;
}
}
return bInit;
}
////////////////////////////////////////////////////////////////////////////
// CMFCWinSockApp::ExitInstance - DLL termination
int CMFCWinSockApp::ExitInstance()
{
// TODO: Add your own module termination code here.
WSACleanup();//結束網絡服務
return COleControlModule::ExitInstance();
}
三,提供控件接口和事件
- 在MFCWinSockCtl.cpp加入如下代碼:
#ifndef WM_MYWINSOCK #define WM_MYWINSOCK WM_USER+1888 #endif
- View->ClassWizard->Automation->Add Method…如下圖:
圖三 創建接口
這個時候,我們爲這個控件添加了一個Connect()的接口,出於通用性,安全性和擴展性的考慮,我們採用了VARIANT類型的參數,
很多人可能都不太瞭解該類型,又或者有接觸過,但被嚇怕了,那麼我們來看清它的本來面目:struct tagVARIANT { union { struct __tagVARIANT { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; _VARIANT_BOOL bool; SCODE scode; CY cyVal; DATE date; BSTR bstrVal; IUnknown __RPC_FAR *punkVal; IDispatch __RPC_FAR *pdispVal; SAFEARRAY __RPC_FAR *parray; BYTE __RPC_FAR *pbVal; SHORT __RPC_FAR *piVal; LONG __RPC_FAR *plVal; FLOAT __RPC_FAR *pfltVal; DOUBLE __RPC_FAR *pdblVal; VARIANT_BOOL __RPC_FAR *pboolVal; _VARIANT_BOOL __RPC_FAR *pbool; SCODE __RPC_FAR *pscode; CY __RPC_FAR *pcyVal; DATE __RPC_FAR *pdate; BSTR __RPC_FAR *pbstrVal; IUnknown __RPC_FAR *__RPC_FAR *ppunkVal; IDispatch __RPC_FAR *__RPC_FAR *ppdispVal; SAFEARRAY __RPC_FAR *__RPC_FAR *pparray; VARIANT __RPC_FAR *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; INT intVal; UINT uintVal; DECIMAL __RPC_FAR *pdecVal; CHAR __RPC_FAR *pcVal; USHORT __RPC_FAR *puiVal; ULONG __RPC_FAR *pulVal; INT __RPC_FAR *pintVal; UINT __RPC_FAR *puintVal; struct __tagBRECORD { PVOID pvRecord; IRecordInfo __RPC_FAR *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; };
它先是一個結構體,裏面有一個重要成員VARTYPE vt;vt即是指明當前的數據類型,比如整型或者字符型,當指明vt後,
後面看到各種變量類型包括在一個聯合體當中,也就是說指明vt後,你只能使用對應的其中之一變量類型。看着這衆多的各種不同
類型變量集中在一起,確實讓人嚇了一跳,但細細看來,大多數變量跟我們平時的用法相似。值得一提的是SAFEARRAY __RPC_FAR *parray;
也許有很多人還沒有接觸過SAFEARRAY類型的變量,SAFEARRAY實際上也是一個結構,大家可以參考MSDN,我也將在後面介紹它的具體使用方法。 - 用同樣的方法創建DisConnect()接口
- 創建兩個事件,FireCloseWinsock()響應網絡斷開事件,FireRecvSockEvent()響應網絡有數據到達的事件。創建方法如下圖:
圖四 創建事件 - 重載控件消息處理函數WindowProc(),在View->ClassWizard中打開類嚮導,在消息映射中找到WindowProc,如下圖:
圖五 重載WindowProc()
四、編寫代碼
- 編寫VariantToLong()轉換函數,該函數代碼如下:
//類型轉換,將VARIANT類型轉換成Long類型 long CMFCWinSockCtrl::VariantToLong(const VARIANT &var) { long r; switch(var.vt) { case VT_UI2://USHORT r = var.uiVal; break; case VT_UI4://ULONG r = var.ulVal; break; case VT_INT://INT r = var.intVal; break; case VT_UINT://UINT r = var.uintVal; break; case VT_I4://LONG r = var.lVal; break; case VT_UI1://BYTE r = var.bVal; break; case VT_I2://SHORT r = var.iVal; break; case VT_R4://FLOAT r = (long)var.fltVal; break; case VT_R8://DOUBLE r = (long)var.dblVal; break; default: r = -1;//無法轉換該值 break; } return r; }
大家可以看到,該函數將最基本的若干中數據類型轉換成了long類型,但VARIANT決不是個簡單的譜,我將在後面繼續揭開它的神祕面紗. - 編寫我們剛纔的接口Connect(),代碼代碼如下: 在MFCWinSockCtrl.h中加入
SOCKET OnlySock;//建立的唯一Socket,不允許重複建立多個 bool isOnlyConnect;//是否建立了連接
然後再編寫Connect(),看起來如下:BOOL CMFCWinSockCtrl::Connect(const VARIANT FAR& RemoteHost, const VARIANT FAR& RemotePort) { // TODO: Add your dispatch handler code here if(isOnlyConnect)//該連接已建立,還沒有斷開 return FALSE; CString IPAddress; int Port;//轉換成整型的端口 switch(RemoteHost.vt) { case VT_BSTR://字符串型 IPAddress = CString(RemoteHost.bstrVal); break; case VT_BYREF|VT_I1://CHAR * IPAddress.Format("%s",RemoteHost.pcVal);//RemoteHost.pbstrVal); break; default: IPAddress = ""; return FALSE; } Port = VariantToLong(RemotePort);//我們編寫的一個VARIANT轉換成long類型的函數 if(Port<=0) return FALSE; _TCHAR *ip = 0; struct hostent *host = 0; struct sockaddr_in addr; ULONG dotIP = inet_addr(IPAddress); OnlySock = socket(AF_INET, SOCK_STREAM, 0); // 判斷是否爲點IP地址格式 if (OnlySock == INVALID_SOCKET) { shutdown(OnlySock, 0x02); closesocket(OnlySock);//釋放佔有的SOCK資源 return FALSE; } memset(&addr, 0, sizeof(struct sockaddr_in)); // 設定 SOCKADDR_IN 結構的內容 // 如果通訊協議是選擇IP Protocol,那此值固定爲AF_INET // AF_INET 與 PF_INET 這兩個常量值相同 addr.sin_family = AF_INET; addr.sin_port = htons(Port); addr.sin_addr.S_un.S_addr = dotIP; if (dotIP == INADDR_NONE) { host = gethostbyname(IPAddress); if (!host) { shutdown(OnlySock, 0x02); closesocket(OnlySock);//釋放佔有的SOCK資源 return FALSE; }; ip = inet_ntoa(*(struct in_addr*)(*host->h_addr_list)); addr.sin_addr.S_un.S_addr = inet_addr(ip); } //開始連線 if (connect(OnlySock, (LPSOCKADDR)&addr, sizeof(SOCKADDR))) { shutdown(OnlySock, 0x02); closesocket(OnlySock);//釋放佔有的SOCK資源 return FALSE; } int iError = WSAAsyncSelect(OnlySock, m_hWnd,WM_MYWINSOCK, FD_READ|FD_CLOSE); //只對網絡斷開和數據到達通知感興趣 if(iError == SOCKET_ERROR)//無法綁定Winsock的事件通知 { shutdown(OnlySock, 0x02); closesocket(OnlySock);//釋放佔有的SOCK資源 return FALSE; } isOnlyConnect = true; return TRUE; }
有必要提一下WSAAsyncSelect(),這裏接收網絡數據到達和斷開的兩個消息,我們收到WM_MYWINSOCK消息時將處理該消息並作爲事件傳送給調用者.
第二個參數,窗口句柄,我們傳送了m_hWnd,這是因爲MFC ActiveX也屬於一個窗口,並且是可見的,因此可以成功。 - 編寫WindowProc(),代碼看起來如下:
LRESULT CMFCWinSockCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: Add your specialized code here and/or call the base class switch(message) { case WM_MYWINSOCK://響應自定義的消息 switch(WSAGETSELECTEVENT(lParam)) { case FD_READ://有新數據到達 FireRecvSockEvent(); break; case FD_CLOSE://對方已斷掉當前連接 FireCloseWinsock(); break; } break; default: break; } return COleControl::WindowProc(message, wParam, lParam); }
本部分結束語:
好了,現在一個可以運行的控件已經完成,裏面提供有Connect()和DisConnect()接口,和RecvSockEvent()及CloseWinsock()事件。以及WinSock的使用方法。
在下一部分(高級篇)將講解兩個重要接口SendData()和GetData(),下期內容如下:
- long SendData(const VARIANT FAR& Data, const VARIANT FAR& DataType,const VARIANT FAR& DataLength, const VARIANT FAR& TimeOut)
- long GetData(VARIANT FAR* Data, const VARIANT FAR& DataType, const VARIANT FAR& DataMaxLength, const VARIANT FAR& TimeOut)
- VARIANT和SAFEARRAY的複雜用法。
- 控件開發出來後在VC和VB環境下的使用方法。
聲明:
- 部分資料來源於網絡,本文所用的所有源代碼僅供非商業用途,並請保留原版權,否則後果自負!
- 歡迎大家拍磚,或指正不足的地方,一起探導更好的方法。
- 歡迎訪問www.vcfans.cn,感謝您的支持!