zoukankan      html  css  js  c++  java
  • 对C#调用C++的dll的一点思考

      最近在对接C++程序的时候碰到了一些问题,然后花了一段时间才解决,今天就这些小问题来做一个总结,很多时候由于对另外一种开发语言的不熟悉,会在使用的过程中遇到很多的问题,这些问题看似简单但是背后却有很多的东西需要去总结的,下面就最近遇到的两个调用C++ API的示例来做一个总结。

          1 首先我们看看如果有下面的一些C++的接口,我们怎样来进行对接。

      typedef unsigned long	KEIO_LONG;
      typedef char	KEIO_BYTE;
      typedef void *		KEIO_HANDLE;
    
      int KEIOAPI OpenKeio(KEIO_HANDLE * phKeio);
      int KEIOAPI CloseKeio(KEIO_HANDLE hKeio);
    
      int KEIOAPI GetKeioTime1(KEIO_HANDLE hKeio, KEIO_LONG * pTime);
      int KEIOAPI GetKeioCode1(KEIO_HANDLE hKeio, KEIO_BYTE * pKeioCodeBuf, KEIO_LONG nKeioCodeBuf);
      int KEIOAPI GetKeioTime2(KEIO_HANDLE hKeio, KEIO_LONG * pTime);
      int KEIOAPI GetKeioCode2(KEIO_HANDLE hKeio, int flag, KEIO_BYTE * pKeioCodeBuf, KEIO_LONG nKeioCodeBuf);
      int KEIOAPI GetKeioCode3(KEIO_HANDLE hKeio, KEIO_BYTE * pKeioCodeBuf, KEIO_LONG nKeioCodeBuf);
    

      我们知道C#中调用C++的dll的时候是需要经过平台转换的,那么上面的API要经过转换以后得到的具体的类型是什么样的呢?

     #region 接口函数
      [DllImport("keIO.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
      public static extern int OpenKeio(ref IntPtr phKeio);
    
     [DllImport("keIO.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
      public static extern int CloseKeio(IntPtr phKeio);
    
     [DllImport("keIO.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
      public static extern int GetKeioTime1(IntPtr hKeio, ref int pTime);
    
     [DllImport("keIO.dll", CharSet = CharSet.Ansi,CallingConvention = CallingConvention.StdCall)]
      public static extern int GetKeioCode1(IntPtr hKeio, StringBuilder pKeioCodeBuf,int nKeioCodeBuf);
    
      [DllImport("keIO.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
      public static extern int  GetKeioTime2(IntPtr hKeio,ref int pTime);
    
      [DllImport("keIO.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
      public static extern int  GetKeioCode2(IntPtr hKeio, int flag, StringBuilder pKeioCodeBuf, int nKeioCodeBuf);      
    
     #endregion  

      这里我们需要特别注意的是GetKeioCode1和GetKeioCode2这两个函数,刚开始在调用的时候一直在报错“”托管的PInvoke签名和非托管的目标签名不匹配“”那么到底是哪里类型不匹配呢?我们来看看这个函数的三个变量,第一个很明显是一个指针类型,这个在C#语言中通过IntPtr类型来匹配,第二个是无符号的char类型的指针,刚开始一直把精力放在这个上面,通过查阅相关资料,通过使用StringBulider类型来匹配,注意第二个参数是传出的类型,也就四相当于C#中的out类型,传入StringBulider类型的变量能够获取函数内部的赋值,第三个“很自然”的使用long类型去匹配,一切看似都是非常自然,但是在使用的时候总是报“类型不匹配”,到底是哪个类型不匹配呢?

      报错的截图如下:

     找了很长时间,最后发现是C++中的long类型的变量和C#中long类型的变量字节数不匹配,C++(编译器编译成32位时)中long类型占4个字节而C#中long的类型占8个字节,字节数不匹配,最终将GetKeioCode1和GetKeioCode2中第三个变量用C#中的int变量去替换就好了,这给了后面的开发对接工作一个警示,关于C++中变量的字节数目的定义可以参考下下面的这篇博客:http://blog.csdn.net/acelit/article/details/69218776,C#中通过下面的测试结果可以有一个直观的了解:

    Debug.WriteLine("在32位模式下:");
    Debug.WriteLine(string.Format("Int16型变量占用的字节数:{0}", sizeof(Int16)));
    Debug.WriteLine(string.Format("int型变量占用的字节数:{0}",sizeof(int)));
    Debug.WriteLine(string.Format("Int32型变量占用的字节数:{0}", sizeof(Int32)));
    Debug.WriteLine(string.Format("Int64型变量占用的字节数:{0}", sizeof(Int64)));
    Debug.WriteLine(string.Format("char型变量占用的字节数:{0}", sizeof(char)));
    Debug.WriteLine(string.Format("long型变量占用的字节数:{0}", sizeof(long)));
    Debug.WriteLine(string.Format("double型变量占用的字节数:{0}", sizeof(double)));
    Debug.WriteLine(string.Format("decimal型变量占用的字节数:{0}", sizeof(decimal)));
    

     输出的结果是: 

    在32位模式下:
    Int16型变量占用的字节数:2
    int型变量占用的字节数:4
    Int32型变量占用的字节数:4
    Int64型变量占用的字节数:8
    char型变量占用的字节数:2
    long型变量占用的字节数:8
    double型变量占用的字节数:8
    decimal型变量占用的字节数:16
    

      那么同样编译成x64程序呢?

    在64位模式下:
    Int16型变量占用的字节数:2
    int型变量占用的字节数:4
    Int32型变量占用的字节数:4
    Int64型变量占用的字节数:8
    char型变量占用的字节数:2
    long型变量占用的字节数:8
    double型变量占用的字节数:8
    decimal型变量占用的字节数:16
    

      看了,好像没有区别,那么到底编译成x86和编译成x64的区别是什么呢?这里也可以参考下面这篇博客:http://blog.csdn.net/zuguangboy/article/details/51509670

           我们可以看到在这里编译成32位和64位程序,对变量的占用字节数是没有区别的,另外关于C#引用C++的时候需要做以下几点的总结:

       A:普通的指针类型,使用IntPtr来进行转换就可以了,如果是指针的指针就在IntPtr的前面加上一个ref表示对指针的引用。

       B:int类型的指针、long类型的指针在作为函数参数使用的时候在C#中调用统一在变量前面加ref,例如:ref int、ref long

            C:关于char类型的指针,如果仅仅是作为参数传入的话用string,如果当前参数需要返回到调用者,那么使用StringBulider变量

       D:这里还需要特别注意的是C++编译成的dll的版本号,如果C++编译成的版本号为x86即32位程序,那么.net程序必须也编译成32位的,这一点特别需要注意,dll的类型必须完全统一。

      2  我们再来看一个关于返回值的例子

      C++函数的原型

    #ifndef VMAXAPI
    #define VMAXAPI __stdcall
    
    Const   char *  VMAXAPI    VmaxSDKGetSystemVersion(void *pCtrl);  

      C#对接的类型

     #region 获取当前软件的版本号 2017-11-27
      [DllImport("VmaxSDK.dll", CharSet = System.Runtime.InteropServices.CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
      public static extern  IntPtr VmaxSDKGetSystemVersion(IntPtr pLPtr);
     #endregion
    

      在对接之后该怎样来使用呢?我们知道C++中返回的是一个char类型的指针,实际上最终返回的是一个字符串,但这里C#是不能够直接使用string来对接的。

    对接的时候我们需要进行一个转换:

     IntPtr currentVersionPtr = Processor.DICS.VmaxSDKGetSystemVersion(m_DICSPtr);
     string currentVersion = Marshal.PtrToStringAnsi(currentVersionPtr);
    

      这里m_DICSPtr是一个IntPtr类型,返回的currentVersionPtr实际上是一个内存中的32位地址值,这个可以通过编译器来查看。

  • 相关阅读:
    .net证书Rsa加密
    $.ajax
    EF通用CRED
    JSON.pase()
    mysql 使用EF6.0CodeFirst
    jenkins 自动构建——shell脚本
    nginx配置示例
    easyui 随笔
    javascript 随笔
    asp.net mvc4 过滤器的简单应用:登录验证
  • 原文地址:https://www.cnblogs.com/seekdream/p/7928377.html
Copyright © 2011-2022 走看看