- 变参函数定义
假设变参函数userlog定义在DLL文件MyDll.dll里面:
__declspec(dllexport)
int userlog(char* fmt, ...)
{
...
}
- c#源文件调用
internal class Program
{
[DllImport("MyDll.dll")]
public static extern int userlog(string format, __arglist);
static void Main(string[] args)
{
userlog("0000", __arglist());
userlog("0000:a=%d", __arglist(100));
userlog("0000:a=%d,b=%d", __arglist(100, 200));
}
}
注意这里所有的参数必须要用__arglist组织起来,不能想C/C++的传统写法,直接罗列在后面,而且即使没有变参数,也必须使用一个空的__arglist,像第一个例子的写法。
- CallingConvention=decl/StdCall的几个试验
下面我们做几个试验:
.NET | 结果 |
---|---|
[DllImport("dll", CallingConvention=decl))] int userlog(string format, __arglist) |
成功 |
[DllImport("dll", CallingConvention=StdCall))] int userlog(string format, __arglist) |
失败 Vararg functions must use the cdecl calling convention |
[DllImport("dll")] int userlog(string format, __arglist) |
成功 |
结论是:对于.net的变参函数调用int userlog(string format, __arglist)
:
- 在c/c++ .dll里面定义的函数的CallingConvention都是__cdecl(后面会有进一步解释)。
- 在.net里面引用申明的CallingConvention:
2.1. Cdecl:匹配,测试成功。
2.2. StdCall: 不匹配,测试失败:Vararg functions must use the cdecl calling convention
。
2.3. <empty>: 使用默认值,我们知道通常.net的默认值是StdCall,应该是不匹配的,但是好像.net为外部变参函数修改了默认值变成Cdecl了;所以使用默认值也能通过。
再做一个固定参数的例子:
.NET | 结果 |
---|---|
[DllImport("dll", CallingConvention=decl))] int userlog(string format, int a, int b) |
成功 |
[DllImport("dll", CallingConvention=StdCall))] int userlog(string format, int a, int b) |
失败 PInvokeStackImbalance |
[DllImport("dll")] int userlog(string format, int a, int b) |
失败 PInvokeStackImbalance |
这里可以看出默认值的差异:
- 对应固定参数的默认值是StdCall,而对于变参的默认值是decl
另外我们可以在c/c++的DLL函数声明的时候使用__cdecl/__stdcall
关键字来定义CallingConvention:
__declspec(dllexport) int userlog(char* fmt, ...) { ... }
__declspec(dllexport) int __cdecl userlog(char* fmt, ...) { ... }
__declspec(dllexport) int __stdcall userlog(char* fmt, ...) { ... }
最后的总结:
- c/c++函数缺省的CallingConvention是__cdecl
- 可以通过属性
__cdecl/__stdcall
来改变函数的CallingConvention - 但是对变参函数无效,变参函数永远是
__cdecl
,无论用户设置了什么值都被忽略。
- 可以通过属性
- .net函数的缺省CallingConvention是__stdcall
- 当调用dll函数时,同样可以使用CallingConvention更改其值,目标是使得caller和callee使用相同的CallingConvention,否则就会出PInvokeStackImbalance错。
- 对于变参函数也要特殊处理,不能使用显式的设置成__stdcall,否则和callee的固定的__cdecl不匹配,所以可以设置成__cdecl,或者不设置使用默认是,此时变参的默认值是__cdecl而不是通常固定参数的__stdcall了。
网友评论