Variant类型在各语言中的参数传递

几年前我用VB开发了一个西门子PPI通信控件,由于VB开发的控件是标准的COM组件,所以想当然的认为VC、C#、Delphi等开发语言可以非常容易的使用。

前段时间由于该控件基于微软的MSCOMM控件,这个控件如果系统没有安装VB,单独注册好像很难成功,这害的一些没有装VB的用户,为了这个小控件必须安装一次VB,这实在是划算不来,所以直接用API串口函数进行了封装改进,这样不仅效率提高了,并且再也不需要MSCOMM控件了。

这一次,我不仅使该控件支持了浮点运算,并且在VC、C#(VB当然就不用多试了,以前就很好的支持)进行了兼容测试。

一试问题就来了,没有改进之前的控件,由于C#封装性能甚好,还能使用,唯一不足的是,控件方法中如果不要求返回的参数前面没有添加ByVal参数,在C#中就转换为 ref ***,害的你还得专门定义临时变量进行传参。

在VC中直接就不行,ReadData和WriteData等几个主要方法根本在VC中无法转换成对应函数,具体错误信息如下:

  
  
  
  
  1. // method 'ReadData' not emitted because of invalid return type or parameter type  
  2.  
  3. // method 'WriteData' not emitted because of invalid return type or parameter type  
  4.  
  5. // method 'PlcLogin' not emitted because of invalid return type or parameter type  
  6.  
  7. // method 'PlcRun' not emitted because of invalid return type or parameter type  
  8.  
  9. // method 'PlcStop' not emitted because of invalid return type or parameter type  
  10.  

经过测试,最后发现VB函数中的byte和数组在VC中根本找不到合适的对应。

由于控件中又新添加了浮点数的支持,所以对参数接口又增加了一层复杂性。突然想到微软的MSCOMM控件各语言肯定都能很好的支持,它的接口参数是怎样的定义的。我在VB、VC、C#分别试了一下,VB中和input和Output属性的类型就是Variant类型,在VC中是VARIANT,在C#中是Object。用Variant还有个好处,就是各种类型的数据都可以传输,没有必要在另外添加接口函数了。

最后我定义的接口如下(主要接口):

  
  
  
  
  1. 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  
  2.  
  3. Public Function WriteData(ByVal lngAddr As Long, ByVal 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  
  4.  
  5.    
  6.  

在VC中对应的接口如下:

 

  
  
  
  
  1. long ReadData(long lngAddr, VARIANT* vData, long lngNum, long bytLen, long bytType, long Addr);  
  2.  
  3. long WriteData(long lngAddr, const VARIANT& vData, long lngNum, long bytLen, long bytType, long Addr);  

 

在C#中的接口如下:

    

  
  
  
  
  1. public virtual int ReadData(int lngAddr, ref object vData);  
  2.  
  3.     public virtual int ReadData(int lngAddr, ref object vData, int lngNum, PPILEN bytLen, PPITYPE bytType, int addr);  
  4.  
  5.       public virtual int WriteData(int lngAddr, object vData);  
  6.  
  7.   public virtual int WriteData(int lngAddr, object vData, int lngNum, PPILEN bytLen, PPITYPE bytType, 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 '打开错误陷阱

    '------------------------------------------------

  

  
  
  
  
  1.   Dim i As Long  
  2.  
  3.     Dim bytType As Byte  
  4.  
  5.     Dim lngRet As Long  
  6.  
  7.     Dim lngData() As Long  
  8.  
  9.     Dim fData() As Single  
  10.  
  11.     Dim vData As Variant  
  12.  
  13.    
  14.  
  15.     Select Case cmbType.ListIndex  
  16.  
  17.     Case 0: bytType = PPI_I  
  18.  
  19.     Case 1: bytType = PPI_Q  
  20.  
  21.     Case 2: bytType = PPI_M  
  22.  
  23.     Case 3: bytType = PPI_V  
  24.  
  25.     Case 4: bytType = PPI_S  
  26.  
  27.     Case 5: bytType = PPI_SM  
  28.  
  29.     End Select 
  30.  
  31.    
  32.  
  33.     S7_PPI1.FixAddr = cmbNo.ListIndex + 1  
  34.  
  35.     lngRet = S7_PPI1.ReadData(Val(txtAddr), vData, Val(cmbNum.Text), Val(cmbLen.ListIndex), Val(bytType))  
  36.  
  37.      
  38.  
  39.     If lngRet = 0 Then 
  40.  
  41.         txtData = "" 
  42.  
  43.         If cmbLen.ListIndex = 3 Then 
  44.  
  45.             fData = vData  
  46.  
  47.             For i = 1 To Val(cmbNum.Text)  
  48.  
  49.                 txtData = txtData & Format(fData(i - 1), "0.00") & " " 
  50.  
  51.             Next 
  52.  
  53.         Else 
  54.  
  55.             lngData = vData  
  56.  
  57.             For i = 1 To Val(cmbNum.Text)  
  58.  
  59.                 txtData = txtData & Format(lngData(i - 1), "0") & " " 
  60.  
  61.             Next 
  62.  
  63.         End If  
  64.  
  65.     Else 
  66.  
  67.         txtData = "Error" 
  68.  
  69.     End If  
  70.  
  71.    
  72.  
  73.     '------------------------------------------------  
  74.  
  75.     Exit Sub  
  76.  
  77.     '----------------  
  78.  
  79. ToExit:  
  80.  
  81.     MsgBox Err.Description  
  82.  
  83. End Sub  
  84.  
  85. Private Sub cmdWriteData_Click()  
  86.  
  87.     On Error GoTo ToExit '打开错误陷阱  
  88.  
  89.     '------------------------------------------------  
  90.  
  91.     Dim bytType As Byte  
  92.  
  93.     Dim strData() As String  
  94.  
  95.     Dim lngRet As Long  
  96.  
  97.     Dim lngData(100) As Long  
  98.  
  99.     Dim fData(100) As Single  
  100.  
  101.     Dim i As Long  
  102.  
  103.    
  104.  
  105.     Select Case cmbType.ListIndex  
  106.  
  107.     Case 0: bytType = PPI_I  
  108.  
  109.     Case 1: bytType = PPI_Q  
  110.  
  111.     Case 2: bytType = PPI_M  
  112.  
  113.     Case 3: bytType = PPI_V  
  114.  
  115.     Case 4: bytType = PPI_S  
  116.  
  117.     Case 5: bytType = PPI_SM  
  118.  
  119.     End Select 
  120.  
  121.    
  122.  
  123.     If Len(txtData) > 0 Then 
  124.  
  125.         strData = Split(txtData, " ")  
  126.  
  127.         If cmbLen.ListIndex = 3 Then 
  128.  
  129.             For i = 0 To UBound(strData)  
  130.  
  131.                 fData(i) = Val(strData(i))  
  132.  
  133.             Next 
  134.  
  135.             lngRet = S7_PPI1.WriteData(Val(txtAddr), fData, UBound(strData) + 1, Val(cmbLen.ListIndex), Val(bytType), cmbNo.ListIndex + 1)  
  136.  
  137.         Else 
  138.  
  139.             For i = 0 To UBound(strData)  
  140.  
  141.                 lngData(i) = Val(strData(i))  
  142.  
  143.             Next 
  144.  
  145.             lngRet = S7_PPI1.WriteData(Val(txtAddr), lngData, UBound(strData) + 1, Val(cmbLen.ListIndex), Val(bytType), cmbNo.ListIndex + 1)  
  146.  
  147.         End If  
  148.  
  149.    
  150.  
  151.         If lngRet = 0 Then 
  152.  
  153.             '  
  154.  
  155.         Else 
  156.  
  157.             txtData = "Error" 
  158.  
  159.         End If  
  160.  
  161.     End If  
  162.  
  163.     '------------------------------------------------  
  164.  
  165.     Exit Sub  
  166.  
  167.     '----------------  
  168.  
  169. ToExit:  
  170.  
  171.     MsgBox Err.Description  
  172.  
  173. End Sub  
  174.  
  175.    
  176.  

在C#中:

 


 

  
  
  
  
  1. /// <summary>  
  2.  
  3.         /// 读数据  
  4.  
  5.         /// </summary>  
  6.  
  7.         /// <param name="sender"></param>  
  8.  
  9.         /// <param name="e"></param>  
  10.  
  11.         private void btnRead_Click(object sender, EventArgs e)  
  12.  
  13.         {  
  14.  
  15.             int intAddr = int.Parse(txtFixAddr.Text);  
  16.  
  17.             object vData = new object();  
  18.  
  19.             
  20.  
  21.             /*  
  22.  
  23.             [PPI_I] = &H81  
  24.  
  25.             [PPI_Q] = &H82  
  26.  
  27.             [PPI_M] = &H83  
  28.  
  29.             [PPI_V] = &H84  
  30.  
  31.             [PPI_S] = 4  
  32.  
  33.             [PPI_SM] = 5  
  34.  
  35.             */ 
  36.  
  37.             PPIV2.PPITYPE DataType= PPIV2.PPITYPE.PPI_V;  
  38.  
  39.             switch (cmbDataType.SelectedIndex)  //数据类型  
  40.  
  41.             {  
  42.  
  43.                 case 0:  
  44.  
  45.                     DataType = PPIV2.PPITYPE.PPI_I;  
  46.  
  47.                     break;  
  48.  
  49.                 case 1:  
  50.  
  51.                     DataType = PPIV2.PPITYPE.PPI_Q;  
  52.  
  53.                     break;  
  54.  
  55.                 case 2:  
  56.  
  57.                     DataType = PPIV2.PPITYPE.PPI_M;  
  58.  
  59.                     break;  
  60.  
  61.                 case 3:  
  62.  
  63.                     DataType = PPIV2.PPITYPE.PPI_V;  
  64.  
  65.                     break;  
  66.  
  67.                 case 4:  
  68.  
  69.                     DataType = PPIV2.PPITYPE.PPI_S;  
  70.  
  71.                     break;  
  72.  
  73.                 case 5:  
  74.  
  75.                     DataType = PPIV2.PPITYPE.PPI_SM;  
  76.  
  77.                     break;  
  78.  
  79.             }  
  80.  
  81.             if (axS7_PPI1.ReadData(int.Parse(txtDataAddr.Text), ref vData, cmbLen.SelectedIndex+1  , (PPIV2.PPILEN)cmbDataMode.SelectedIndex, DataType, intAddr) == 0)  
  82.  
  83.             {  
  84.  
  85.                 if (cmbDataMode.SelectedIndex == 3)  
  86.  
  87.                 {  
  88.  
  89.                     float[] fData = (float[])vData;  
  90.  
  91.                     txtData.Text = "";  
  92.  
  93.                     for (int i = 0; i < fData.Length; i++)  
  94.  
  95.                     {  
  96.  
  97.                         txtData.Text += fData[i].ToString("0.00") + " ";  
  98.  
  99.                     }  
  100.  
  101.                 }  
  102.  
  103.                 else 
  104.  
  105.                 {  
  106.  
  107.                     Int32[] intData = (Int32[])vData;  
  108.  
  109.                     txtData.Text = "";  
  110.  
  111.                     for (int i = 0; i < intData.Length; i++)  
  112.  
  113.                     {  
  114.  
  115.                         txtData.Text += intData[i].ToString() + " ";  
  116.  
  117.                     }  
  118.  
  119.                 }  
  120.  
  121.             }  
  122.  
  123.             else 
  124.  
  125.             {  
  126.  
  127.                 txtData.Text = "ERROR";  
  128.  
  129.             }  
  130.  
  131.         }  
  132.  
  133.    
  134.  
  135.         /// <summary>  
  136.  
  137.         /// 写数据  
  138.  
  139.         /// </summary>  
  140.  
  141.         /// <param name="sender"></param>  
  142.  
  143.         /// <param name="e"></param>  
  144.  
  145.         private void btnWrite_Click(object sender, EventArgs e)  
  146.  
  147.         {          
  148.  
  149.    
  150.  
  151.             int intAddr = int.Parse(txtFixAddr.Text);  
  152.  
  153.             object vData = new object();  
  154.  
  155.    
  156.  
  157.             /*  
  158.  
  159.             [PPI_I] = &H81  
  160.  
  161.             [PPI_Q] = &H82  
  162.  
  163.             [PPI_M] = &H83  
  164.  
  165.             [PPI_V] = &H84  
  166.  
  167.             [PPI_S] = 4  
  168.  
  169.             [PPI_SM] = 5  
  170.  
  171.             */ 
  172.  
  173.             PPIV2.PPITYPE DataType = PPIV2.PPITYPE.PPI_V;  
  174.  
  175.             switch (cmbDataType.SelectedIndex)  //数据类型  
  176.  
  177.             {  
  178.  
  179.                 case 0:  
  180.  
  181.                     DataType = PPIV2.PPITYPE.PPI_I;  
  182.  
  183.                     break;  
  184.  
  185.                 case 1:  
  186.  
  187.                     DataType = PPIV2.PPITYPE.PPI_Q;  
  188.  
  189.                     break;  
  190.  
  191.                 case 2:  
  192.  
  193.                     DataType = PPIV2.PPITYPE.PPI_M;  
  194.  
  195.                     break;  
  196.  
  197.                 case 3:  
  198.  
  199.                     DataType = PPIV2.PPITYPE.PPI_V;  
  200.  
  201.                     break;  
  202.  
  203.                 case 4:  
  204.  
  205.                     DataType = PPIV2.PPITYPE.PPI_S;  
  206.  
  207.                     break;  
  208.  
  209.                 case 5:  
  210.  
  211.                     DataType = PPIV2.PPITYPE.PPI_SM;  
  212.  
  213.                     break;  
  214.  
  215.             }  
  216.  
  217.             long lngRet = 0;  
  218.  
  219.             if (cmbDataMode.SelectedIndex == 3)  
  220.  
  221.             {  
  222.  
  223.                 float[] fData = new float[100];  
  224.  
  225.                 fData[0] = float.Parse(txtData.Text);  
  226.  
  227.                 lngRet = axS7_PPI1.WriteData(int.Parse(txtDataAddr.Text), fData, 1, (PPIV2.PPILEN)cmbDataMode.SelectedIndex, DataType, intAddr);  
  228.  
  229.             }  
  230.  
  231.             else 
  232.  
  233.             {  
  234.  
  235.                 Int32[] intData = new Int32[100];  
  236.  
  237.                 intData[0] = Int32.Parse(txtData.Text);  
  238.  
  239.                 lngRet = axS7_PPI1.WriteData(int.Parse(txtDataAddr.Text), intData, 1, (PPIV2.PPILEN)cmbDataMode.SelectedIndex, DataType, intAddr);  
  240.  
  241.             }  
  242.  
  243.             if (lngRet != 0)  
  244.  
  245.             {  
  246.  
  247.                 txtData.Text = "ERROR";  
  248.  
  249.             }  
  250.  
  251.         }  
  252.  
  253.    
  254.  

在VC中:

 


 

  
  
  
  
  1. //读数据  
  2.  
  3. void CPPI_TestDlg::OnReadData()  
  4.  
  5. {  
  6.  
  7.        //pCombo->GetLBText (pCombo->GetCurSel (), strType);  
  8.  
  9.        long lngFixAddr=0,lngDataAddr=0;  
  10.  
  11.        char strAddr[255];  
  12.  
  13.        m_FixAddr.GetWindowText(strAddr,255);  
  14.  
  15.        sscanf(strAddr,"%ld",&lngFixAddr);  
  16.  
  17.         
  18.  
  19.        m_DataAddr.GetWindowText(strAddr,255);  
  20.  
  21.        sscanf(strAddr,"%ld",&lngDataAddr);  
  22.  
  23.     /*  
  24.  
  25.        [PPI_I] = &H81  
  26.  
  27.     [PPI_Q] = &H82  
  28.  
  29.     [PPI_M] = &H83  
  30.  
  31.     [PPI_V] = &H84  
  32.  
  33.     [PPI_S] = 4  
  34.  
  35.     [PPI_SM] = 5  
  36.  
  37.        */ 
  38.  
  39.     long DataType;  
  40.  
  41.        switch (m_DataType.GetCurSel())  //数据类型  
  42.  
  43.        {  
  44.  
  45.        case 0:  
  46.  
  47.               DataType=0x81;   
  48.  
  49.               break;  
  50.  
  51.        case 1:  
  52.  
  53.               DataType=0x82;  
  54.  
  55.               break;  
  56.  
  57.        case 2:  
  58.  
  59.               DataType=0x83;  
  60.  
  61.               break;  
  62.  
  63.        case 3:  
  64.  
  65.               DataType=0x84;  
  66.  
  67.               break;  
  68.  
  69.        case 4:  
  70.  
  71.               DataType=0x4;  
  72.  
  73.               break;  
  74.  
  75.        case 5:  
  76.  
  77.               DataType=0x5;  
  78.  
  79.               break;  
  80.  
  81.        }  
  82.  
  83.    
  84.  
  85.        long lngRet;  
  86.  
  87.     VARIANT vData;  
  88.  
  89.        VariantInit (&vData);  
  90.  
  91.        if(m_DataMode.GetCurSel()==3)  //浮点数  
  92.  
  93.        {  
  94.  
  95.            vData.vt = VT_R4 | VT_ARRAY;  
  96.  
  97.            vData.parray=SafeArrayCreateVector(VT_R4, 0, 255 );      
  98.  
  99.        }  
  100.  
  101.        else 
  102.  
  103.        {  
  104.  
  105.            vData.vt = VT_I4 | VT_ARRAY;  
  106.  
  107.            vData.parray=SafeArrayCreateVector(VT_I4, 0, 255 );     //SAFEARRAY vd;  
  108.  
  109.        }     
  110.  
  111.        lngRet=m_PPI.ReadData(lngDataAddr,&vData,m_DataNum.GetCurSel()+1,m_DataMode.GetCurSel(),DataType,lngFixAddr);  
  112.  
  113.        if(lngRet==0)  
  114.  
  115.        {  
  116.  
  117.               CString strData;  
  118.  
  119.               if(m_DataMode.GetCurSel()==3)  //浮点数  
  120.  
  121.               {  
  122.  
  123.                      float *fData;  
  124.  
  125.                      SafeArrayAccessData(vData.parray, (void**)&fData );  
  126.  
  127.                      for(int i=0;i<m_DataNum.GetCurSel()+1;i++)  //(int)vData.parray->cbElements  
  128.  
  129.                      {  
  130.  
  131.                         CString cData;  
  132.  
  133.                         cData.Format("%04.2f ",fData[i]);  
  134.  
  135.                         strData+= cData;  
  136.  
  137.                      }  
  138.  
  139.                      SafeArrayUnaccessData(vData.parray);  
  140.  
  141.               }  
  142.  
  143.               else 
  144.  
  145.               {  
  146.  
  147.                      long *lngData;  
  148.  
  149.                      SafeArrayAccessData(vData.parray, (void**)&lngData );  
  150.  
  151.                      for(int i=0;i<m_DataNum.GetCurSel()+1;i++)  //(int)vData.parray->cbElements  
  152.  
  153.                      {  
  154.  
  155.                         CString cData;  
  156.  
  157.                         cData.Format("%ld ",lngData[i]);  
  158.  
  159.                         strData+= cData;  
  160.  
  161.                      }  
  162.  
  163.                      SafeArrayUnaccessData(vData.parray);  
  164.  
  165.               }  
  166.  
  167.    
  168.  
  169.               m_Data.SetWindowText(strData);  
  170.  
  171.        }  
  172.  
  173.        else 
  174.  
  175.        {  
  176.  
  177.         m_Data.SetWindowText(_T("ERROR"));  
  178.  
  179.        }  
  180.  
  181.        SafeArrayUnaccessData(vData.parray);  
  182.  
  183.        SafeArrayDestroy(vData.parray);  
  184.  
  185.     VariantClear(&vData);  
  186.  
  187. }  
  188.  
  189.    
  190.  
  191. //写数据  
  192.  
  193. void CPPI_TestDlg::OnWrite()  
  194.  
  195. {  
  196.  
  197.        long lngFixAddr=0,lngDataAddr=0;  
  198.  
  199.        char strAddr[255];  
  200.  
  201.        m_FixAddr.GetWindowText(strAddr,255);  
  202.  
  203.        sscanf(strAddr,"%ld",&lngFixAddr);  
  204.  
  205.         
  206.  
  207.        m_DataAddr.GetWindowText(strAddr,255);  
  208.  
  209.        sscanf(strAddr,"%ld",&lngDataAddr);  
  210.  
  211.    
  212.  
  213.     long DataType;  
  214.  
  215.        switch (m_DataType.GetCurSel())  
  216.  
  217.        {  
  218.  
  219.        case 0:  
  220.  
  221.               DataType=0x81;  
  222.  
  223.               break;  
  224.  
  225.        case 1:  
  226.  
  227.               DataType=0x82;  
  228.  
  229.               break;  
  230.  
  231.        case 2:  
  232.  
  233.               DataType=0x83;  
  234.  
  235.               break;  
  236.  
  237.        case 3:  
  238.  
  239.               DataType=0x84;  
  240.  
  241.               break;  
  242.  
  243.        case 4:  
  244.  
  245.               DataType=0x4;  
  246.  
  247.               break;  
  248.  
  249.        case 5:  
  250.  
  251.               DataType=0x5;  
  252.  
  253.               break;  
  254.  
  255.        }  
  256.  
  257.        long lngRet;  
  258.  
  259.     VARIANT vData;  
  260.  
  261.        VariantInit (&vData);  
  262.  
  263.        if(m_DataMode.GetCurSel()==3)  //浮点数  
  264.  
  265.        {  
  266.  
  267.            vData.vt = VT_R4 | VT_ARRAY;  
  268.  
  269.            vData.parray=SafeArrayCreateVector(VT_R4, 0, 255 );  
  270.  
  271.                
  272.  
  273.               float *fDatas,fData;  
  274.  
  275.               SafeArrayAccessData(vData.parray, (void**)&fDatas );  
  276.  
  277.               m_Data.GetWindowText(strAddr,255);  
  278.  
  279.               sscanf(strAddr,"%f",&fData);  
  280.  
  281.               fDatas[0]=fData;  
  282.  
  283.               SafeArrayUnaccessData(vData.parray);  
  284.  
  285.        }  
  286.  
  287.        else 
  288.  
  289.        {  
  290.  
  291.            vData.vt = VT_I4 | VT_ARRAY;  
  292.  
  293.            vData.parray=SafeArrayCreateVector(VT_I4, 0, 255 );     //SAFEARRAY vd;  
  294.  
  295.                
  296.  
  297.               long *lngDatas,lngData;  
  298.  
  299.               SafeArrayAccessData(vData.parray, (void**)&lngDatas );  
  300.  
  301.               m_Data.GetWindowText(strAddr,255);  
  302.  
  303.               sscanf(strAddr,"%ld",&lngData);  
  304.  
  305.               lngDatas[0]=lngData;  
  306.  
  307.               SafeArrayUnaccessData(vData.parray);  
  308.  
  309.        }  
  310.  
  311.    
  312.  
  313.     lngRet=m_PPI.WriteData(lngDataAddr,(const VARIANT &)vData,1,m_DataMode.GetCurSel(),DataType,lngFixAddr);  
  314.  
  315.        if(lngRet!=0)  
  316.  
  317.        {  
  318.  
  319.               m_Data.SetWindowText(_T("ERROR"));  
  320.  
  321.        }  
  322.  
  323.        SafeArrayUnaccessData(vData.parray);  
  324.  
  325.        SafeArrayDestroy(vData.parray);  
  326.  
  327.     VariantClear(&vData);  
  328.  
  329. }  
  330.  
  331.    
  332.  

详细的示例请在下面的链接下载:

http://www.sky-walker.com.cn/yefan/PPIV2.rar

 

早期版本的控件下载链接:

http://www.sky-walker.com.cn/yefan/S7_PPI.rar

 

 

你可能感兴趣的:(参数,语言,类型,休闲,variant)