zhouqijie

使用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字段,要求在托管代码中声明的函数名必须与非托管函数名保持完全一致。

有时候需要使用不同的函数名,例如防止冲突、非托管函数有非类型化参数等原因。这时候可以包装函数或者指定入口点。

入口点用于标识非托管函数在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

一般来说,包含了字符或者字符串参数,或者返回值为字符/字符串的Win32API函数,都有窄版本(ANIS)和宽版本(UNICODE)。其函数名拥有共同的根函数名称,唯一的区别在于后缀不同。

查找顺序:

  1. 指定为Ansi时:”Foo” -> “FooA”
  2. 指定为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)