c#调用c++动态库的一些理解

c#调用c++动态库一般我们这样写

[DllImport("UCamer.dll", CallingConvention = CallingConvention.Winapi)]
public extern static void Disp_Destroy(IntPtr hShow);
DllImport的第一个参数UCamer.dll是动态库dll的路径,此dll放在程序运行的根目录或者c:windows/sytem32下

  CallingConvention 参数是c#调用c++的方式 是个枚举 msdn解释如下

  

Cdecl 调用方清理堆栈。这使您能够调用具有 varargs 的函数(如 Printf),使之可用于接受可变数目的参数的方法。 
FastCall 不支持此调用约定。
StdCall 被调用方清理堆栈。这是使用平台 invoke 调用非托管函数的默认约定。 
ThisCall 第一个参数是 this 指针,它存储在寄存器 ECX 中。其他参数被推送到堆栈上。此调用约定用于对从非托管 DLL 导出的类调用方法。 
Winapi 此成员实际上不是调用约定,而是使用了默认平台调用约定。例如,在 Windows 上默认为 StdCall,在 Windows CE.NET 上默认为 Cdecl。 

 从上面来看Winapi方式是根据系统自动选择调用规约的。 而thisCall是对c++类的调用方法。 所以 一般情况下我们选择Winapi就可以了。

 

c#调用dll另一个难点:数据类型转换

百度文库这篇文章基本把c++与c#的对应数据类型总结完了。但是为什么这里还要说呢

1,百度文库这篇文章,包括大部分度娘的类型转换的资料中 对c++中的返回值类型char[] 都转换成了char[] 没做任何改变。

  c++中char占一个字节,assic编码。而c#中的char占2个字节(我是在中文版的vs中测试的)。

  如果这样转换就会出现问题,很容易发生越界,或者读写到受保护的内存等问题。

  解决方法是 把char[] 变成c#中的byte[] ,再用Encoding.Assic.getstring方法转换。

2,关于指针,c++所有的指针 在c#上用Intptr ,问题又来了。假如返回的Intptr是个数组指针,在c#中我们怎么读取数组里面的元素呢?

  Marshal.Copy();  

Marshal类是c#中专门把非托管内存转换成托管内存的神器,不需要unsafe。 Marshal中copy方法是最最常用的方法。里面有16个重载。能解决目前你能遇到的大部分问题。

msdn中对各个重载解释如下

名称说明
    Copy(Byte[], Int32, IntPtr, Int32) 安全关键。将一维的托管 8 位无符号整数数组中的数据复制到非托管内存指针。
    Copy(Char[], Int32, IntPtr, Int32) 安全关键。将数据从一维的托管字符数组复制到非托管内存指针。
    Copy(Double[], Int32, IntPtr, Int32) 安全关键。将数据从一维的托管双精度浮点数组复制到非托管内存指针。
    Copy(Int16[], Int32, IntPtr, Int32) 安全关键。将一维的托管 16 位有符号整数数组中的数据复制到非托管内存指针。
    Copy(Int32[], Int32, IntPtr, Int32) 安全关键。将数据从一维的托管 32 位有符号整数数组复制到非托管内存指针。
    Copy(Int64[], Int32, IntPtr, Int32) 安全关键。将一维的托管 64 位有符号整数数组中的数据复制到非托管内存指针。
    Copy(IntPtr, Byte[], Int32, Int32) 安全关键。将数据从非托管内存指针复制到托管 8 位无符号整数数组。
    Copy(IntPtr, Char[], Int32, Int32) 安全关键。将数据从非托管内存指针复制到托管字符数组。
    Copy(IntPtr, Double[], Int32, Int32) 安全关键。将数据从非托管内存指针复制到托管双精度浮点数组。
    Copy(IntPtr, Int16[], Int32, Int32) 安全关键。将非托管内存指针中的数据复制到托管 16 位有符号整数数组。
    Copy(IntPtr, Int32[], Int32, Int32) 安全关键。将非托管内存指针中的数据复制到托管 32 位有符号整数数组。
    Copy(IntPtr, Int64[], Int32, Int32) 安全关键。将非托管内存指针中的数据复制到托管 64 位有符号整数数组。
    Copy(IntPtr, Single[], Int32, Int32) 安全关键。将数据从非托管内存指针复制到托管单精度浮点数组。
    Copy(Single[], Int32, IntPtr, Int32) 安全关键。将数据从一维的托管单精度浮点数组复制到非托管内存指针。

我们也可以使用共享内存的方式进行操作。部分代码如下

//初始化返回图片的大小等信息
myContext = new VlcControlWpfRendererContext(width, height, System.Windows.Media.PixelFormats.Bgr24);
//创建共享内存区域
myBitmapSectionPointer = Win32Interop.CreateFileMapping(new IntPtr(-1), IntPtr.Zero, Win32Interop.PageAccess.ReadWrite, 0, myContext.Size, null);
//获取共享内存的首地址
 map = Win32Interop.MapViewOfFile(myBitmapSectionPointer, Win32Interop.FileMapAccess.AllAccess, 0, 0, (uint)myContext.Size);
//把接收后的图片拷入共享内存区域
Win32Interop.CopyMemory(map, data, myContext.Size);
//把共享内存中的数组转换为图片 myBitmap
= (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(myBitmapSectionPointer, myContext.Width, myContext.Height, myContext.PixelFormat, myContext.Stride, 0);

 

 

其实Intptr本质也是一个Int,Int在c#中和Int32是一样的。所以基本上指针,Long,int在c#中都是int。只是这样些 方便大家知道他是c++中的什么类型,方便转换而已。

 

3,c++中的函数指针  与c#中的委托

 

这是c++中对函数指针的定义

typedef VOID (WINAPI *PUSERCALL)( PUCHAR pData, ULONG Length, PVOID pUserData );

对应c#中的例子如下

 public delegate void PUSERCALL(IntPtr pData, uint Length, UInt32 pUserData);

 

4,我们知道int是占4个字节的。 

  下面这个是c++的一个方法

 

U_CAMER LONG WINAPI CAMER_GetPropery( HANDLE hCamer, _CMRCTL Propery );

假如我们把此函数翻译成c#中的下面这个函数

[DllImport("UCamer.dll", CallingConvention = CallingConvention.Winapi)]
public extern static Uint16 CAMER_GetPropery(IntPtr hCamer, CMRCTL Propery);

我们在c#调用此方法

uint16 m_HiWi_temp = (uint)BCamera.CAMER_GetPropery(m_hCamer, CMRCTL.OUT_SIZE);

发现一个很有趣的问题,此处调用没有问题。也有值,但是他是取的int32中4个字节的2个字节。

 

我们看原本c++对此函数的调用

*((PULONG)m_HiWi) = *((PULONG)m_Display) = CAMER_GetPropery( m_hCamer, OUT_SIZE );
m_hShow = Disp_Create( m_hWnd, m_HiWi[1], m_HiWi[0], m_nColor, (USERDRAW)((m_ReDrawLine == TRUE) ? DrawLine : NULL), this );

本来CAMER_GetPropery函数只返回了一个long类型。c++中通过指针的转换。把long类型转换成了 pulong,也是ulong的数组。

那c#中怎么我们该怎么调用呢

 int m_HiWi_temp = BCamera.CAMER_GetPropery(m_hCamer, CMRCTL.OUT_SIZE);
                byte[] m_byte_HiWi = BitConverter.GetBytes(m_HiWi_temp);
                byte[] temp1 = new byte[2] { m_byte_HiWi[0], m_byte_HiWi[1] };
                byte[] temp2 = new byte[2] { m_byte_HiWi[2], m_byte_HiWi[3] };
                int width = BitConverter.ToInt16(temp1, 0);
                int high = BitConverter.ToInt16(temp2, 0);

这里举这个例子是说c++有时真的就是返回一个int类型,但是在c++中可以轻松把int类型通过指针轻松转换成两个uint16的数组。所以c#中我们再转换的时候一定有注意了。

 

总结:其实数据类型的转换主要是对数据存储空间的转换,c++中的数据类型占用多大的空间,只要转换成c#中占同等空间的数据类型就可以了。只是c#看哪种数据类型在操作相应的操作方便些。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。