標準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 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();}
三,提供控制項介面和事件
- 在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://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環境下的使用方法。
聲明:
- 部分資料來源於網路,本文所用的所有原始碼僅供非商業用途,並請保留原著作權,否則後果自負!
- 歡迎大家拍磚,或指正不足的地方,一起探導更好的方法。
- 歡迎訪問www.vcfans.cn,感謝您的支援!