文章轉載自:http://www.vckbase.com/document/viewdoc/?id=1884
标準MFC WinSock ActiveX控件開發執行個體(II)進階篇
作者:小輝
下載下傳源代碼
摘要:本文主要利用VARIANT類型作參數進行的網絡資料傳送和接收,以及SAFEARRAY,BSTR的詳細使用方法。
另外還提供該控件在VC,VB下的調用方式以及相關資料的處理。
關鍵字:ActiveX,Socket,VARIANT, SAFEARRAY,BSTR。
回顧:在上一篇文章《标準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調用控件方式: 建立一對話框工程,然後在工程中添加該控件,設定如下圖:
标準MFC WinSock ActiveX控件開發執行個體(二) 圖一 建立新對話框工程,并加入控件
響應控件的斷網和資料到達事件,設定如下圖:
标準MFC WinSock ActiveX控件開發執行個體(二) 圖二 響應控件的兩個事件
添加相應代碼,看起來如下:
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的很多自動化功能,請看下圖:
标準MFC WinSock ActiveX控件開發執行個體(二) 圖三 VB調用控件方法
同樣,輕按兩下我們的控件,然後添加控件事件,如下圖:
标準MFC WinSock ActiveX控件開發執行個體(二) 圖四 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的調用方法,由于這段時間忙