使用C/C++类型的非托管函数
指定调用约定
由于非托管DLL在导出非托管函数时会采用一些不同的调用约定,这样就可能出现无法正常调用的情况。
因此必须更具导出非托管函数时采用的调用约定来确定CallConvertion
字段设置的值。
CallConvertion枚举 | 说明 |
---|---|
Cdecl |
(C Declaration的缩写)调用方负责清理堆栈。这就使得开发人员能调用具有可变参数的函数,使之可用于接收可变数目的参数的方法。 |
StdCall |
被调用方负责清理堆栈。Windows平台默认方式。 |
Winapi |
此成员实际上不是调用约定,而是使用默认平台调用约定。Windows为StdCall |
ThisCall |
主要用于调用C++类中的方法,第一个参数是this指针,它存储在ECX寄存器,其他参数推送到堆栈 |
FastCall |
.NET不支持此值 |
由于C#不支持参数类型为varargs类型的非托管函数,就必须显式地对所有参数进行定义(CRE:即在托管代码中定义重载)。在这种情况下,调用约定必须采用
CallConvertion.Cdecl
,因为堆栈是由调用方负责清理的。
调用约定的另一个作用在于它控制了非托管函数的名称重整,因此也影响着非托管函数被导出的实际名称。只要调用约定一致,就无需在意名称重整问题,dotnet会自动处理。
指定入口点
如果没有显式地为DllImport
特性指定EntryPoint
字段,要求在托管代码中声明的函数名必须与非托管函数名保持完全一致。
有时候需要使用不同的函数名,例如防止冲突、非托管函数有非类型化参数等原因。这时候可以包装函数或者指定入口点。
- 使用EntryPoint字段
入口点用于标识非托管函数在DLL中的位置。
[DllImport("NativeLib.dll", EntryPoint="Multiply")]
static extern int MultiplyRenamed(int x, int y);
如果使用了非类型化参数(untyped parameter),意味着函数可以接收任何数据类型作为参数。
void PrintMsg(void* msg, int flag)
{}
[DllImport("NativeLib.dll", EntryPoint="PringMsg")]
static extern void PrintID(ref int id, int flag);
[DllImport("NativeLib.dll", EntryPoint="PringMsg")]
static extern void PrintName(string name, int flag);
指定字符集
众所周知.NET采用Unicode编码,而C/C++之类的非托管代码没有对字符集进行强制规定。因此非托管代码处理字符串时,用于可以采用ANSI也可以采用Unicode。
DllImport的CharSet字段正是用于解决此类字符编码问题的。它的主要作用有两个:一是控制字符串的封送处理方式,二是确定平台调用在DLL中查找函数名的方式。
- 控制字符串的封送处理方式
如果非托管函数的参数和返回值的字符串类型都是char*
,则无需显式指定CharSet字段,默认为ANSI。
如果非托管函数的字符串采用的是宽字符wchar_t*
、WCHAR
等,则需要显式地指定CharSet.Unicode
。
- 确定DLL中查找函数名的方式
一般来说,包含了字符或者字符串参数,或者返回值为字符/字符串的Win32API函数,都有窄版本(ANIS)和宽版本(UNICODE)。其函数名拥有共同的根函数名称,唯一的区别在于后缀不同。
查找顺序:
- 指定为Ansi时:”Foo” -> “FooA”
- 指定为Unicode时:”FooW” -> “Foo”
//Win32API函数MessageBox拥有两个不同版本:MessageBoxA和MessageBoxW。
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
public static extern int MessageBox(int hwnd, string lptext, string lpCaption, int wType);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(int hwnd, string lptext, string lpCaption, int wType);
只要按照Win32API函数的命名约定对非托管函数进行定义,就能像Win32API平台调用那样用CharSet字段控制要调用的版本。
如果
ExactSpelling
字段设置为true,就会影响基于CharSet的P/Invoke自动行为,从而导致上面的方法失效。
(END)