程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 標准MFC WinSock ActiveX控件開發實例

標准MFC WinSock ActiveX控件開發實例

編輯:關於VC++

一、MFC ActiveX控件開發步驟(VC 6.0):

New->Projects->MFC ActiveX ControlWizard,然後輸入MFCWinSock工程名。如下圖:

圖一 創建工程

一路狂按Next,直至Finsh出現,再按下OK,如下圖:

 

圖二 創建完成

二、架設Socket環境:

首先在StdAfx.h文件中加入下面這句代碼: #include <afxsock.h>
// 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,感謝您的支持!

本文配套源碼

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved