几年前我用VB开发了一个西门子PPI通信控件,由于VB开发的控件是标准的COM组件,所以想当然的认为VC、C#、Delphi等开发语言可以非常容易的使用。
前段时间由于该控件基于微软的MSCOMM控件,这个控件如果系统没有安装VB,单独注册好像很难成功,这害的一些没有装VB的用户,为了这个小控件必须安装一次VB,这实在是划算不来,所以直接用API串口函数进行了封装改进,这样不仅效率提高了,并且再也不需要MSCOMM控件了。
这一次,我不仅使该控件支持了浮点运算,并且在VC、C#(VB当然就不用多试了,以前就很好的支持)进行了兼容测试。
一试问题就来了,没有改进之前的控件,由于C#封装性能甚好,还能使用,唯一不足的是,控件方法中如果不要求返回的参数前面没有添加ByVal参数,在C#中就转换为 ref ***,害的你还得专门定义临时变量进行传参。
在VC中直接就不行,ReadData和WriteData等几个主要方法根本在VC中无法转换成对应函数,具体错误信息如下:
// method 'ReadData' not emitted because of invalid return type or parameter type
// method 'WriteData' not emitted because of invalid return type or parameter type
// method 'PlcLogin' not emitted because of invalid return type or parameter type
// method 'PlcRun' not emitted because of invalid return type or parameter type
// method 'PlcStop' not emitted because of invalid return type or parameter type
经过测试,最后发现VB函数中的byte和数组在VC中根本找不到合适的对应。
由于控件中又新添加了浮点数的支持,所以对参数接口又增加了一层复杂性。突然想到微软的MSCOMM控件各语言肯定都能很好的支持,它的接口参数是怎样的定义的。我在VB、VC、C#分别试了一下,VB中和input和Output属性的类型就是Variant类型,在VC中是VARIANT,在C#中是Object。用Variant还有个好处,就是各种类型的数据都可以传输,没有必要在另外添加接口函数了。
最后我定义的接口如下(主要接口):
Public Function ReadData(ByVal lngAddr As Long,vData As Variant,Optional ByVal lngNum As Long = 1,Optional ByVal bytLen As PPILEN = PPI_B,Optional ByVal bytType As PPITYPE = PPI_V,Optional ByVal Addr As Long = 0) As Long Public Function WriteData(ByVal lngAddr As Long,ByVal vData As Variant,Optional ByVal Addr As Long = 0) As Long
在VC中对应的接口如下:
long ReadData(long lngAddr,VARIANT* vData,long lngNum,long bytLen,long bytType,long Addr); long WriteData(long lngAddr,const VARIANT& vData,long Addr);
在C#中的接口如下:
public virtual int ReadData(int lngAddr,ref object vData); public virtual int ReadData(int lngAddr,ref object vData,int lngNum,PPILEN bytLen,PPITYPE bytType,int addr); public virtual int WriteData(int lngAddr,object vData); public virtual int WriteData(int lngAddr,object vData,int addr);
以为这样定义就万事大吉了,事后一试我又错了,在C#中没有任何问题(看了微软还是在C#上下了很大的功夫),在VC简单的定义一个VARIANT变量直接传递给控件,VB控件老是报错,根本无法使用。后来想为什么MSCOMM控件可以,我的控件不可以。天杀的MSCOMM肯定是VC开发的,而我的控件是VB开发的,VB和C#的包容性都很强,而VC却高高在上不肯屈就。
正一筹莫展准备放弃的时候,突然想到了以前用VC开发的OPC程序,上面有很多关于VARIANT的应用,一看就明白了,原来在VC中VARIANT的用法是有讲究的。
下面我就详细说一下控件同样的接口在不同语言中如何使用。
在VB中:
Private Sub cmdReadData_Click() On Error GoTo ToExit '打开错误陷阱 '------------------------------------------------ Dim i As Long Dim bytType As Byte Dim lngRet As Long Dim lngData() As Long Dim fData() As Single Dim vData As Variant Select Case cmbType.ListIndex Case 0: bytType = PPI_I Case 1: bytType = PPI_Q Case 2: bytType = PPI_M Case 3: bytType = PPI_V Case 4: bytType = PPI_S Case 5: bytType = PPI_SM End Select S7_PPI1.FixAddr = cmbNo.ListIndex + 1 lngRet = S7_PPI1.ReadData(Val(txtAddr),vData,Val(cmbNum.Text),Val(cmbLen.ListIndex),Val(bytType)) If lngRet = 0 Then txtData = "" If cmbLen.ListIndex = 3 Then fData = vData For i = 1 To Val(cmbNum.Text) txtData = txtData & Format(fData(i - 1),"0.00") & " " Next Else lngData = vData For i = 1 To Val(cmbNum.Text) txtData = txtData & Format(lngData(i - 1),"0") & " " Next End If Else txtData = "Error" End If '------------------------------------------------ Exit Sub '---------------- ToExit: MsgBox Err.Description End Sub Private Sub cmdWriteData_Click() On Error GoTo ToExit '打开错误陷阱 '------------------------------------------------ Dim bytType As Byte Dim strData() As String Dim lngRet As Long Dim lngData(100) As Long Dim fData(100) As Single Dim i As Long Select Case cmbType.ListIndex Case 0: bytType = PPI_I Case 1: bytType = PPI_Q Case 2: bytType = PPI_M Case 3: bytType = PPI_V Case 4: bytType = PPI_S Case 5: bytType = PPI_SM End Select If Len(txtData) > 0 Then strData = Split(txtData," ") If cmbLen.ListIndex = 3 Then For i = 0 To UBound(strData) fData(i) = Val(strData(i)) Next lngRet = S7_PPI1.WriteData(Val(txtAddr),fData,UBound(strData) + 1,Val(bytType),cmbNo.ListIndex + 1) Else For i = 0 To UBound(strData) lngData(i) = Val(strData(i)) Next lngRet = S7_PPI1.WriteData(Val(txtAddr),lngData,cmbNo.ListIndex + 1) End If If lngRet = 0 Then ' Else txtData = "Error" End If End If '------------------------------------------------ Exit Sub '---------------- ToExit: MsgBox Err.Description End Sub
在C#中:
/// <summary> /// 读数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnRead_Click(object sender,EventArgs e) { int intAddr = int.Parse(txtFixAddr.Text); object vData = new object(); /* [PPI_I] = &H81 [PPI_Q] = &H82 [PPI_M] = &H83 [PPI_V] = &H84 [PPI_S] = 4 [PPI_SM] = 5 */ PPIV2.PPITYPE DataType= PPIV2.PPITYPE.PPI_V; switch (cmbDataType.SelectedIndex) //数据类型 { case 0: DataType = PPIV2.PPITYPE.PPI_I; break; case 1: DataType = PPIV2.PPITYPE.PPI_Q; break; case 2: DataType = PPIV2.PPITYPE.PPI_M; break; case 3: DataType = PPIV2.PPITYPE.PPI_V; break; case 4: DataType = PPIV2.PPITYPE.PPI_S; break; case 5: DataType = PPIV2.PPITYPE.PPI_SM; break; } if (axS7_PPI1.ReadData(int.Parse(txtDataAddr.Text),ref vData,cmbLen.SelectedIndex+1,(PPIV2.PPILEN)cmbDataMode.SelectedIndex,DataType,intAddr) == 0) { if (cmbDataMode.SelectedIndex == 3) { float[] fData = (float[])vData; txtData.Text = ""; for (int i = 0; i < fData.Length; i++) { txtData.Text += fData[i].ToString("0.00") + " "; } } else { Int32[] intData = (Int32[])vData; txtData.Text = ""; for (int i = 0; i < intData.Length; i++) { txtData.Text += intData[i].ToString() + " "; } } } else { txtData.Text = "ERROR"; } } /// <summary> /// 写数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnWrite_Click(object sender,EventArgs e) { int intAddr = int.Parse(txtFixAddr.Text); object vData = new object(); /* [PPI_I] = &H81 [PPI_Q] = &H82 [PPI_M] = &H83 [PPI_V] = &H84 [PPI_S] = 4 [PPI_SM] = 5 */ PPIV2.PPITYPE DataType = PPIV2.PPITYPE.PPI_V; switch (cmbDataType.SelectedIndex) //数据类型 { case 0: DataType = PPIV2.PPITYPE.PPI_I; break; case 1: DataType = PPIV2.PPITYPE.PPI_Q; break; case 2: DataType = PPIV2.PPITYPE.PPI_M; break; case 3: DataType = PPIV2.PPITYPE.PPI_V; break; case 4: DataType = PPIV2.PPITYPE.PPI_S; break; case 5: DataType = PPIV2.PPITYPE.PPI_SM; break; } long lngRet = 0; if (cmbDataMode.SelectedIndex == 3) { float[] fData = new float[100]; fData[0] = float.Parse(txtData.Text); lngRet = axS7_PPI1.WriteData(int.Parse(txtDataAddr.Text),1,intAddr); } else { Int32[] intData = new Int32[100]; intData[0] = Int32.Parse(txtData.Text); lngRet = axS7_PPI1.WriteData(int.Parse(txtDataAddr.Text),intData,intAddr); } if (lngRet != 0) { txtData.Text = "ERROR"; } }
在VC中:
//读数据 void CPPI_TestDlg::OnReadData() { //pCombo->GetLBText (pCombo->GetCurSel (),strType); long lngFixAddr=0,lngDataAddr=0; char strAddr[255]; m_FixAddr.GetWindowText(strAddr,255); sscanf(strAddr,"%ld",&lngFixAddr); m_DataAddr.GetWindowText(strAddr,&lngDataAddr); /* [PPI_I] = &H81 [PPI_Q] = &H82 [PPI_M] = &H83 [PPI_V] = &H84 [PPI_S] = 4 [PPI_SM] = 5 */ long DataType; switch (m_DataType.GetCurSel()) //数据类型 { case 0: DataType=0x81; break; case 1: DataType=0x82; break; case 2: DataType=0x83; break; case 3: DataType=0x84; break; case 4: DataType=0x4; break; case 5: DataType=0x5; break; } long lngRet; VARIANT vData; VariantInit (&vData); if(m_DataMode.GetCurSel()==3) //浮点数 { vData.vt = VT_R4 | VT_ARRAY; vData.parray=SafeArrayCreateVector(VT_R4,255 ); } else { vData.vt = VT_I4 | VT_ARRAY; vData.parray=SafeArrayCreateVector(VT_I4,255 ); //SAFEARRAY vd; } lngRet=m_PPI.ReadData(lngDataAddr,&vData,m_Datanum.GetCurSel()+1,m_DataMode.GetCurSel(),lngFixAddr); if(lngRet==0) { CString strData; if(m_DataMode.GetCurSel()==3) //浮点数 { float *fData; SafeArrayAccessData(vData.parray,(void**)&fData ); for(int i=0;i<m_Datanum.GetCurSel()+1;i++) //(int)vData.parray->cbElements { CString cData; cData.Format("%04.2f ",fData[i]); strData+= cData; } SafeArrayUnaccessData(vData.parray); } else { long *lngData; SafeArrayAccessData(vData.parray,(void**)&lngData ); for(int i=0;i<m_Datanum.GetCurSel()+1;i++) //(int)vData.parray->cbElements { CString cData; cData.Format("%ld ",lngData[i]); strData+= cData; } SafeArrayUnaccessData(vData.parray); } m_Data.SetWindowText(strData); } else { m_Data.SetWindowText(_T("ERROR")); } SafeArrayUnaccessData(vData.parray); SafeArrayDestroy(vData.parray); VariantClear(&vData); } //写数据 void CPPI_TestDlg::OnWrite() { long lngFixAddr=0,&lngDataAddr); long DataType; switch (m_DataType.GetCurSel()) { case 0: DataType=0x81; break; case 1: DataType=0x82; break; case 2: DataType=0x83; break; case 3: DataType=0x84; break; case 4: DataType=0x4; break; case 5: DataType=0x5; break; } long lngRet; VARIANT vData; VariantInit (&vData); if(m_DataMode.GetCurSel()==3) //浮点数 { vData.vt = VT_R4 | VT_ARRAY; vData.parray=SafeArrayCreateVector(VT_R4,255 ); float *fDatas,fData; SafeArrayAccessData(vData.parray,(void**)&fDatas ); m_Data.GetWindowText(strAddr,255); sscanf(strAddr,"%f",&fData); fDatas[0]=fData; SafeArrayUnaccessData(vData.parray); } else { vData.vt = VT_I4 | VT_ARRAY; vData.parray=SafeArrayCreateVector(VT_I4,255 ); //SAFEARRAY vd; long *lngDatas,lngData; SafeArrayAccessData(vData.parray,(void**)&lngDatas ); m_Data.GetWindowText(strAddr,&lngData); lngDatas[0]=lngData; SafeArrayUnaccessData(vData.parray); } lngRet=m_PPI.WriteData(lngDataAddr,(const VARIANT &)vData,lngFixAddr); if(lngRet!=0) { m_Data.SetWindowText(_T("ERROR")); } SafeArrayUnaccessData(vData.parray); SafeArrayDestroy(vData.parray); VariantClear(&vData); }