调用C++dll方法
我们在写C#程序时,常常需要调用C++编写的dll,在C#中调用dll是非常方便的,只需要把你要使用的dll放在程序的当前目录下,然后:
[DLLImport("DLL文件名")]
修饰符 extern static 返回变量类型 方法名称(参数列表)
// DLL文件名:你想要调用的库文件名
// 修饰符:public
// 返回变量类型:在dll文件中你需调用方法的返回变量类型
// 方法名称:在dll文件中你需调用方法的名称
// 参数列表:在dll文件中你需调用方法的列表
DLLImport()中还可以添加的属性有:
// EntryPoint 指示函数入口点 如:EntryPoint="MessageBoxA"
// CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;
// SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;
// ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;
// PreserveSig指示方法的签名应当被保留还是被转换, 如:PreserveSig=true;
// CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;
类型转换
由于C++的dll主要是基于win 32平台开发的非托管代码,或activeX的组件,而C#这样的托管代码是基于.net开发的。我们在C#中调用C++的dll时,会遇到参数的数据类型转换和指针或地址参数传送问题。这里把数据类型转换做一个总结:
C++中的数据类型 | C#中的数据类型 |
---|---|
char, INT8, CHAR | SByte |
unsigned char, UINT8, UCHAR , BYTE | Byte |
int *, int & | ref int |
char *, string | string |
short, short int, INT16, SHORT | Int16 |
int, long, long int, INT32, LONG32, BOOL , INT | Int32 |
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t | UInt16 |
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT | UInt32 |
unsigned __int64, UINT64, DWORDLONG, ULONGLONG | UInt64 |
float, FLOAT | Single |
double, long double, DOUBLE | Double |
BSTR, LPCTSTR | StringBuilder |
LPCWSTR, handle, hwnd, unsigned char *, void * | IntPtr |
结构体的话,需要在C#里重新定义一个Struct
CallBack回调函数需要封装在一个委托里,如:delegate static extern int FunCallBack(string str);
使用举例
C++dll中函数:int __stdcall FunctionName(unsigned char *param1, unsigned int param2);
在C#中我们可以这样调用:
[DllImport("dll名字.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public extern static int FunctionName(ref byte param1, UInt32 param2);
这里unsigned char *我们使用ref byte转换,unsigned int我们使用UInt32转换,我们调用这个函数时,利用参数传递地址只需要传入数组首地址就可以了
// 先初始化一个数组和一个unsigned int变量
string param1Str = "Hello World";
byte[] param1 = Encoding.Unicode.GetBytes(param1Str);
UInt32 param2 = 100;
// 要将其首地址传送过去,只要将param1数组的第一个元素用ref修饰
int a = FunctionName(ref param1[0], param2);
// 如果我们想要用param1作为输出,可以在调用函数之后直接使用或是把byte转换为string使用
byte[] param1 = new byte[100];
int a = FunctionName(ref param1[0], param2);
string param = Encoding.Unicode.GetString(param1);
对于char*,void*这种我们也可以直接用InPtr转换,还是以上面这个函数为例子,使用InPtr我们可以这样声明和调用:
[DllImport("dll名字.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public extern static int FunctionName(IntPtr param1, UInt32 param2);
string param1Str = "Hello World";
IntPtr param1 = Marshal.StringToHGlobalUni(param1Str);
UInt32 param2 = 100;
int a = FunctionName(param1, param2);
// 用param1作为输出,我们要先使用Marshal.AllocHGlobal分配给param1内存,记得使用完后释放,否则会造成内存泄漏
IntPtr param1 = IntPtr.Zero;
param1 = Marshal.AllocHGlobal(100);
int a = FunctionName(param1, param2);
string param = Marshal.PtrToStringUni(param1);
// 使用完后,释放为param1申请的内存
Marshal.FreeHGlobal(param1);
除了使用Marshal.AllocHGlobal分配内存,也可以使用GCHandle分配内存
byte[] temp = new byte[100];
GCHandle param1GC = GCHandle.Alloc(temp, GCHandleType.Pinned);
IntPtr param1 = param1GC.AddrOfPinnedObject();
// 和使用Marshal.AllocHGlobal分配内存一样调用
int a = FunctionName(param1, param2);
string param = Marshal.PtrToStringUni(param1);
// 释放分配的内存
param1GC.Free();