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

標准MFC WinSock ActiveX控件開發實例(II)高級篇

編輯:關於VC++

回顧:在上一篇文章《標准MFC WinSock ActiveX控件開發實例》中我們詳細介紹了控件的開發過程,以及接口和事件的

添加和響應方法。現在我們將繼續上次沒有寫完的控件繼續進行開發,並完善作為一個WinSock控件應該具備的功能。

二、按照前一篇文章提到的知識,現在我們來添加兩個新的接口分別是SendData()和GetData(),它們看起來如下:

//網絡數據發送,在指定的超時時間內進行發送然後返回,成功返回實際發送字節數,否則返回負數
long CMFCWinSockCtrl::SendData(const VARIANT FAR& Data,

const VARIANT FAR& DataType,

const VARIANT FAR& DataLength,

const VARIANT FAR& TimeOut)
{

// TODO: Add your dispatch handler code here

return 0;
}

//獲取數據,並指定獲取數據的超時時間,返回實際獲取到的數據長度,否則返回負數
long CMFCWinSockCtrl::GetData(VARIANT FAR* Data,

const VARIANT FAR& DataType,

const VARIANT FAR& DataMaxLength,

const VARIANT FAR& TimeOut)
{

// TODO: Add your dispatch handler code here

return 0;
}

兩個接口的參數除了第一個參數外,其它都類似。SendData()是發送數據,不要求將數據帶回,因此直接用 VARIANT,而GetData()則要求將數據帶回來給調用者,因此定義為 VARIANT *類型,第二個參數DataType故名思義是定義所傳送或接收數據的類型,第三個參數是傳送或接收數據的長度,這裡的長度以char作為一個長度,假如傳入的類型是int類型,則長度為4,如果定義的是字符串,一個中文字符占用2個長度。最後一個參數,是網絡發送或讀取時的超時時間。

三、為Connect()接口添加源代碼,看起來如下:

//網絡數據發送,在指定的超時時間內進行發送然後返回,成功返回實際發送字節數,否則返回負數
long CMFCWinSockCtrl::SendData(const VARIANT FAR& Data,

const VARIANT FAR& DataType,

const VARIANT FAR& DataLength,

const VARIANT FAR& TimeOut)
{

// TODO: Add your dispatch handler code here

if(!OnlySock)

return -1;//網絡尚未開始建立連接

int gDataType = VariantToLong(DataType);

long gDataLength = VariantToLong(DataLength);

int gTimeOut = VariantToLong(TimeOut);

if(gDataType < 0)

return -2;

if(gDataLength <= 0)

return -2;

if(gTimeOut < 0)

return -2;

switch(gDataType)

{

case 0://默認形式,這時如果發現Data為整型數組,將不進行任何轉換,直接把一個int傳給一個char傳送(數據可能溢出范圍)

case 1://當指定該值為1時,當Date為長整型數組時,將把一個long轉換成四個char傳送

case 2://當指定該值為2時,當Date為整型數組時,將把一個int轉換成四個char傳送

case 3://當指定該值為3時,當Date為無符號短整型數組時,將把一個unsigned short轉換成兩個char傳送

case 4://當指定該值為4時,當Date為BYTE數組時,將把一個BYTE轉換成一個char傳送

case 5://當指定該值為5時,當Date為短整型數組時,將把一個short轉換成兩個char傳送

case 6://當指定該值為6時,當Date為浮點型數組時,將把一個float轉換成四個char傳送

case 7://當指定該值為7時,當Date為雙精度數組時,將把一個double轉換成八個char傳送

break;

default://如果不在上面取值范圍內,將按當前的Data相應類型進行傳送

break;

}

timeval tv;

fd_set fdwrite;

int len = 0;

long m = 0;

long n = 0;

long changetype = 0;//將浮點型數據進行類型轉換,再進行傳送

VARIANT gData;

VariantInit(&gData);

//送出信息至服務器

FD_ZERO(&fdwrite);

tv.tv_sec = gTimeOut;//指定時間後返回

tv.tv_usec = 0;

FD_SET(OnlySock,&fdwrite);//是否可以發送數據

select(0,NULL,&fdwrite,NULL,&tv);

char *buffer = NULL;

if(FD_ISSET(OnlySock,&fdwrite))

{

switch(Data.vt)

{

case VT_BSTR://按字符串形式發送

buffer = _com_util::ConvertBSTRToString(Data.bstrVal);

break;

case VT_BYREF|VT_UI1:
//按BYTE*形式發送

buffer = new char[gDataLength];

memcpy(buffer,Data.pbVal,gDataLength);

break;

case VT_BYREF|VT_I1://按 char * 發送

buffer = new char[gDataLength];

memcpy(buffer,Data.pcVal,gDataLength);

break;

case VT_ARRAY|VT_I4://以長整型數組發送

gData.vt = VT_I4;

if(gDataType!=0)//long = char*4

{

//sizeof(long),在這裡一個長整型的長度為4個char

buffer = new char[gDataLength];

for(m=0,n=0; n<gDataLength/4; n++)

{


SafeArrayGetElement(Data.parray,&n,&gData.lVal);


buffer[m++] = (gData.lVal>>24)&0xff;


buffer[m++] = (gData.lVal>>16)&0xff;


buffer[m++] = (gData.lVal>>8)&0xff;


buffer[m++] = gData.lVal&0xff;

}

}

else//long = char*1 //數據可能溢出

{

buffer = new char[gDataLength];

for(m=0,n=0; n<gDataLength; n++)

{


SafeArrayGetElement(Data.parray,&n,&gData.lVal);


buffer[n] = (char)gData.lVal;

}

}

break;

case VT_ARRAY|VT_INT://以整型數組發送

gData.vt = VT_INT;

if(gDataType != 0)

{

//一個int等於四個char

buffer = new char[gDataLength];

for(m=0,n=0; n<gDataLength/4; n++)

{


SafeArrayGetElement(Data.parray,&n,&gData.intVal);


buffer[m++] = (gData.intVal>>24)&0xff;


buffer[m++] = (gData.intVal>>16)&0xff;


buffer[m++] = (gData.intVal>>8)&0xff;


buffer[m++] = gData.intVal&0xff;

}

}

else

{

buffer = new char[gDataLength];

for(n=0; n<gDataLength; n++)

{


SafeArrayGetElement(Data.parray,&n,&gData.intVal);


buffer[n] = (char)gData.intVal;

}

}

break;

case VT_ARRAY|VT_UI1://以BYTE數組發送

gData.vt = VT_UI1;//一個char等於一個BYTE不必進行轉換

buffer = new char[gDataLength];

for(n=0; n<gDataLength; n++)

{

SafeArrayGetElement(Data.parray,&n,&gData.bVal);

buffer[n] = gData.bVal;

}

break;

default://在這裡沒有一一列出其它類型,剩下的就由閣下進行數據轉換處理了,我就偷懶了^_^

return -3;//傳入的數據類型不被支持

}

len = send(OnlySock, buffer, gDataLength, 0);//發送數據

delete[] buffer;

buffer = NULL;

if (len<=0)// == SOCKET_ERROR)

{

return -101;//無法發送數據,對方可能已斷開連接

}

}

else

{

return 0;//網絡超時

}

VariantClear(&gData);

return len;
}

在這裡,我們利用了_com_util::ConvertBSTRToString() 將BSTR轉換成char *類型,它能自動對中文字符進行轉換,解決了利用某些方法轉換時,中文字符變成亂碼的BUG。前提是,在使用該方法時,你要先 #include "comutil.h" ,然後在Project Settings-Link-Object/library modules 中加入" comsupp.lib kernel32.lib "

同理,將char *轉換成BSTR類型,可以通過_com_util::ConvertStringToBSTR()實現。在VC中,通過sizeof()我們可以看到int和long的長度都是4,而char的長度為1,因此,如果傳入的是長整型或者整型數組,我將它轉換成4個char,然後發送出去,轉換方法可以通過移位處理,如下 :

//long轉換為4個char
char buffer[4];
long lData_1 = 12345678;
long lData_2 = 0;
buffer[0] = (lData>>24)&0xff;
buffer[1] = (lData>>16)&0xff;
buffer[2] = (lData>>8)&0xff;
buffer[3] = lData&0xff;

//4個char組成一個long
lData_2 = ((buffer[0]&0xff)<<24) +

((buffer[1]&0xff)<<16) +

((buffer[2]&0xff)<<8) +

(buffer[3]&0xff);

四、現在來看看GetData()的處理,具體實現,請看如下代碼:

// TODO: Add your dispatch handler code here

if(!OnlySock)

return -1;//網絡尚未開始建立連接

int gDataType = VariantToLong(DataType);

long gDataMaxLength = VariantToLong(DataMaxLength);

int gTimeOut = VariantToLong(TimeOut);

if(gDataType < 0)

return -2;

if(gDataMaxLength <= 0)

return -2;

if(gTimeOut < 0)

return -2;

switch(gDataType)

{

case 0://默認形式,這時如果發現Data為整型數組,將不進行任何轉換,直接把一個int傳給一個char傳送(數據可能溢出范圍)

case 1://當指定該值為1時,當Date為長整型數組時,將把一個long轉換成四個char傳送

case 2://當指定該值為2時,當Date為整型數組時,將把一個int轉換成四個char傳送

case 3://當指定該值為3時,當Date為無符號短整型數組時,將把一個unsigned short轉換成兩個char傳送

case 4://當指定該值為4時,當Date為BYTE數組時,將把一個BYTE轉換成一個char傳送

case 5://當指定該值為5時,當Date為短整型數組時,將把一個short轉換成兩個char傳送

case 6://當指定該值為6時,當Date為浮點型數組時,將把一個float轉換成四個char傳送

case 7://當指定該值為7時,當Date為雙精度數組時,將把一個double轉換成八個char傳送

break;

default://如果不在上面取值范圍內,將按當前的Data相應類型進行傳送

break;

}

timeval tv;

fd_set fdread;

int len = -3;//如果找不到該連接,則返回-3

long n = 0;

long m = 0;

long changetype = 0;

VARIANT gData;

VariantInit(&gData);

char *buffer=NULL;

buffer = new char[gDataMaxLength+1];

memset(buffer, 0, gDataMaxLength+1);

FD_ZERO(&fdread);

tv.tv_sec = gTimeOut;//超過指定時間後返回

tv.tv_usec = 0;

FD_SET(OnlySock,&fdread);//是否可以讀取數據

select( 0,&fdread,NULL,NULL,&tv);

if(FD_ISSET(OnlySock,&fdread))

{

len = recv(OnlySock, buffer, gDataMaxLength, 0);

if (len<=0)

{

delete[] buffer;

return -102;//無法讀取數據,對方可能已斷開連接

}

if(len<gDataMaxLength)//如果讀取數據的長度小於傳入的長度,將傳入的長度更改為實際長度

gDataMaxLength = len;

switch(Data->vt)

{

case VT_BSTR://按字符串形式接收

buffer[gDataMaxLength] = '\0';

Data->bstrVal = _com_util::ConvertStringToBSTR(buffer);

break;

case VT_BYREF|VT_UI1:
//按BYTE*形式接收

memcpy(Data->pbVal,buffer,gDataMaxLength);

break;

case VT_BYREF|VT_I1://按 char * 形式接收

memcpy(Data->pcVal,buffer,gDataMaxLength);

break;

case VT_BYREF|VT_I4://以長整型指針接收

buffer[gDataMaxLength]='\0';

for(n=0; n<gDataMaxLength; n++)

{

Data->plVal[n] = buffer[n];

}

break;

case VT_ARRAY|VT_I4://以長整型數組接收

gData.vt = VT_I4;

if(gDataType != 0)

{

for(m=0,n=0; n<gDataMaxLength;n++)

{


gData.lVal = ((buffer[m]&0xff)<<24) +



((buffer[m+1]&0xff)<<16) +



((buffer[m+2]&0xff)<<8) +



(buffer[m+3]&0xff);

SafeArrayPutElement(Data->parray,&n,&gData.lVal);


m = m+4;

}

}

else

{

for(n = 0; n<gDataMaxLength; n++)

{


gData.lVal = (long)buffer[n];


SafeArrayPutElement(Data->parray,&n,&gData.lVal);

}

}

break;

case VT_ARRAY|VT_INT://以整型數組接收

gData.vt = VT_INT;

if(gDataType != 0)

{

for(m=0,n=0; n<gDataMaxLength;n++)

{


gData.intVal = ((buffer[m]&0xff)<<24) +



((buffer[m+1]&0xff)<<16) +



((buffer[m+2]&0xff)<<8) +



(buffer[m+3]&0xff);





SafeArrayPutElement(Data->parray,&n,&gData.intVal);


m = m+4;

}

}

else

{

for(n = 0; n<gDataMaxLength; n++)

{


gData.intVal = (int)buffer[n];


SafeArrayPutElement(Data->parray,&n,&gData.intVal);

}

}

break;

case VT_ARRAY|VT_UI1://以BYTE數組接收

gData.vt = VT_UI1;

for(n = 0; n<gDataMaxLength; n++)

{

gData.bVal = buffer[n]&0xff;

SafeArrayPutElement(Data->parray,&n,&gData.bVal);

}

break;

default://其它類型,請各位看官自行實現處理,嘿嘿

delete[] buffer;

return -3;//無法識別傳入的數據類型

}

}

else

{

delete[] buffer;

return 0;//網絡數據讀取超時

}

VariantClear(&gData);

delete[] buffer;

return len;

五、接下來,我們看看VC和VB如何調用該控件:

VC調用控件方式: 新建一對話框工程,然後在工程中添加該控件,設置如下圖:

圖一 創建新對話框工程,並加入控件

響應控件的斷網和數據到達事件,設置如下圖:

圖二 響應控件的兩個事件

添加相應代碼,看起來如下:

void CTestMFCWinSockDlg::OnRecvSockEventMfcwinsockctrl1()
{

// TODO: Add your control notification handler code here

SAFEARRAYBOUND Bound[1];//一維數組

Bound[0].lLbound=0;

Bound[0].cElements=100;//該一維數組最大接收100個元素

VARIANT *data;

data = new VARIANT;

VariantInit(data);

data->vt = VT_ARRAY|VT_I4;//指明為長整型數組

data->parray = SafeArrayCreate(VT_I4,1,Bound);//創建SAFEARRAY結構

long l = m_sock.GetData(data,

COleVariant((long)0),

COleVariant((long)100),

COleVariant((long)3));

if(l<=0)

{

;//在這裡判斷出錯信息,並作相應處理,我就偷懶了.

}

char pData[100]={0};//這裡以字符數組顯示結果

long change = 0;

for(long n=0; n<l; n++)

{

SafeArrayGetElement(data->parray,&n,&change);

pData[n] = (char)change;

}

CString mess;

mess.Format("%s",pData);

AfxMessageBox(mess);

SafeArrayDestroy(data->parray);

delete data;

}

void CTestMFCWinSockDlg::OnCloseWinsockMfcwinsockctrl1()
{

// TODO: Add your control notification handler code here

m_sock.DisConnect();//調用斷開連接接口

AfxMessageBox("服務器斷開了該次連接,請檢查!");
}

void CTestMFCWinSockDlg::OnConnect()
{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

if(!m_sock.Connect(COleVariant(m_ip),COleVariant(m_port)))

AfxMessageBox("與服務器建立連接失敗,請確認服務器是否存在!");
}

VB調用控件方式: VB時面調用要方便很多,這得益於VB的很多自動化功能,請看下圖:

圖三 VB調用控件方法

同樣,雙擊我們的控件,然後添加控件事件,如下圖:

圖四 VB響應控件事件

然後,添加相關代碼如下:

Private Sub Command1_Click()
MFCWinSock1.Connect CStr(ip), CLng(port)
End Sub

Private Sub Command2_Click()
MFCWinSock1.SendData "SendData: 歡迎使用!", 0, 50, 3
End Sub

Private Sub MFCWinSock1_CloseWinsock()
MFCWinSock1.DisConnect
MsgBox "服務器斷開了連接,請檢查!"

End Sub

Private Sub MFCWinSock1_RecvSockEvent()
Dim data As Variant
Dim data2(100) As Long
Dim data3 As String
Dim l As Long
data = data2 '在VB裡當把一個Variant變量data等於另一個確定變量data2時,data將被初始化為與data2相同的類型變量
'data = data3 '如果讓data等於data3,那麼data將變成字符串型的變量參數
l = MFCWinSock1.GetData(data, 0, 100, 3) '這時data裡面已存放了接收到的數據
data3 = data(0) '這裡只顯示接收到的首字符編碼
MsgBox data3
End Sub

大家可以看到,對於SAFEARRAY類型的數據進行相關處理也並不可怕,由於在源碼裡給出了具體代碼和詳細注解,在這裡我就不再贅述了,

至於BSTR和char *類型的數據,相信不用我多說,大家也已經知道如何使用了。

結束語:

全文至此暫告一段落,本文向大家展示了MFC ActiveX控件的魅力,以及所用的VARIANT類型參數,還詳細給出了WinSock的開發代碼,

以用在VC,VB的調用方法,由於這段時間忙於一些新項目的開發,因此沒辦法花太多時間進行詳細解釋,所以很多地方都直接給出源代碼

再加上注解,而沒有進行通俗的講解,還請各位讀者仔細查看源代碼。

本控件目前只能作為客戶端,閣下還可以繼續進行完善,比如進行端口的監聽,實現服務器的相關處理等等,但這已經不是本文的目的,

授人以魚,不如授人如漁,剩下的功能,就由各位讀者去實現了,也歡迎與我進行交流,謝謝!

另外:本文的示例,需要一個服務器程序,大家可以在網上隨便下載一下進行測試,我就不提供了。

聲明:

部分資料來源於網絡,本文所用的所有源代碼僅供非商業用途,並請保留原版權,否則後果自負!

歡迎大家拍磚,指正錯誤或不足的地方,一起探導更好的方法。

歡迎訪問www.vcfans.cn,感謝您的支持!

本文配套源碼

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