您的位置:首页 > 其它

标准MFC WinSock ActiveX控件开发实例(II)高级篇

2009-05-05 09:51 288 查看
标准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
= (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
= (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
= 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
= buffer
;
}
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
;
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
;
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
&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; nparray,&n,&change);
pData
= (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,感谢您的支持!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: