摘要:本文主要介紹如何開發一個ActiveX控件,提供接口,與相應事件掛鉤。文中涉及到VARIANT,SAFEARRAY,BSTR的詳細使用方法。
另外還提供了WinSock的詳細開發步驟,以及如何響應網絡超時,網絡斷開的事件方法以及在VC,VB調用該控件的方法。
關鍵字:ActiveX,Socket,VARIANT, SAFEARRAY,BSTR。
1、MFC ActiveX控件開發步驟(VC 6.0):
安全
New->Projects->MFC ActiveX ControlWizard,而後輸入MFCWinSock工程名。以下圖:
網絡
圖一 建立工程
socket
一路狂按Next,直至Finsh出現,再按下OK,以下圖:
函數
圖二 建立完成
2、架設Socket環境:
ui
首先在StdAfx.h文件中加入下面這句代碼: #include <afxsock.h>// MFC socket extensions
spa
打開MFCWinSock.cpp文件,添加代碼,看起來以下:code
////////////////////////////////////////////////////////////////////////////// CMFCWinSockApp::InitInstance - DLL initializationBOOL 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 terminationint CMFCWinSockApp::ExitInstance(){// TODO: Add your own module termination code here.WSACleanup();//結束網絡服務return COleControlModule::ExitInstance();}
三,提供控件接口和事件
orm
在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()
4、編寫代碼
編寫VariantToLong()轉換函數,該函數代碼以下: //類型轉換,將VARIANT類型轉換成Long類型long CMFCWinSockCtrl::VariantToLong(const VARIANT &var){long r;switch(var.vt){case VT_UI2://USHORTr = var.uiVal;break;case VT_UI4://ULONGr = var.ulVal;break;case VT_INT://INTr = var.intVal;break;case VT_UINT://UINTr = var.uintVal;break;case VT_I4://LONGr = var.lVal;break;case VT_UI1://BYTEr = var.bVal;break;case VT_I2://SHORTr = var.iVal;break;case VT_R4://FLOATr = (long)var.fltVal;break;case VT_R8://DOUBLEr = (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 hereif(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 classswitch(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環境下的使用方法。