在 C# 中使用 _CDECL 调用约定(更改编译器服务)






4.30/5 (16投票s)
2005年12月22日
2分钟阅读

89255

479
一篇关于在 C# 中使用 __cdecl 回调的文章,更改编译器服务。
引言
默认情况下,C# 编译器遵循 __stdcall
调用约定。 现在,如果我们有一个现有的非托管 DLL,它遵循 __cdecl
调用约定,那么我们如何将其放入我们的 C# 代码中呢?
假设我们有一个带有以下声明的 C++ 非托管代码
// Function pointer declaration. typedef int (__cdecl *funcptr)( int, int); //Using that function pointer int UseFuncptr(int a,int b,funcptr fptr) { return(fptr(a,b)); }
如果我们想从 C# 实现这个函数,我们可以这样实现
[DllImport(“dllname.dll”)]
public static extern int UseFuncptr(int a,int b,MulticastDelegate callbk);
//This delegate will follow __stdcall
public delegate int mydg(int a,int b);
[STAThread]
static void Main(string[] args)
{
int a = 10, b = 10;
mydg dg = new mydg(Sum);
a = UseFuncptr(a, b, dg)
…………
}
//Trying to implement this method through callback
public static int Sum(int a,int b)
{
return (a+b);
}
在这里,mydg
不能满足我们的目的,无法调用 Sum
方法并将它的地址传递给非托管方法 UseFuncPtr
,因为我们使用的调用约定与非托管 DLL 中的调用约定不同。 在执行这段代码时,会抛出一个运行时错误
背景
出现这个问题的原因是 C# 和 C 或 C++ 的调用约定不匹配。 C# 中的默认调用约定是 __stdcall
,而 C 或 C++ 中是 __cdecl
。
Microsoft .NET IDE 没有提供更改调用约定的方法,因此没有简单的方法来调用这样的方法。
使用代码
现在,为了在 C# 中实现 __cdecl
调用约定,我们需要向 MSIL 代码注入一部分。
我们的主要目标是更改 JIT 编译器中委托 (mydg
) 的调用约定。 在构建我们的应用程序之后,我们将使用 .NET 提供的 ILDASM 工具从 .exe 或 .dll 创建 .IL 文件。
在那里我们找到委托声明,如下所示
.method /*0600000C*/ public hidebysig virtual instance int32 Invoke(int32 a,
int32 b) runtime managed
// SIG: 20 02 08 08 08
{
} // .......:Invoke
现在,我们必须将编译器服务更改为 __cdecl
。 我们将在 IL 代码中添加以下行
.method /*0600000C*/ public hidebysig virtual instance
int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)
Invoke(int32 a,
int32 b) runtime managed
// SIG: 20 02 08 08 08
{
} // ................::Invoke
现在,我们将它保存到 IL 文件。 然后使用 ILASM 将此 IL 构建到相应的 .exe 或 .dll。
>ILASM /exe ILfilename.IL
这将创建我们需要的可执行文件,它与 __cdecl
调用约定兼容。 这样,我们可以轻松地在 C# 中实现 __cdecl
调用约定。
关注点
假设在你的代码中有 20 个委托,现在的问题是如何识别 MSIL 代码中我们需要注入代码的确切位置。 我发现了一种使用 Attribute
类解决这个问题的方法。 我们将创建一个简单的属性类
[AttributeUsage(AttributeTargets.Delegate)]
public class CallconvAttribute : System.Attribute
{
public CallconvAttribute()
{
}
}
我们将声明我们的委托 mydg
为
[Callconv]public delegate int mydg(int a,int b);
现在,如果我们构建这个 .exe 或 .dll 并通过 ILDASM 转储 IL,我们可以通过属性类轻松识别委托。 MSIL 将如下所示。
.custom /*0C00000D:0600000F*/ instance void
CallDLLFromDotNet.CallconvAttribute/* 02000004 */::.ctor()
/* 0600000F */ = ( 01 00 00 00 )
.method /*0600000B*/ public hidebysig specialname rtspecialname
instance void .ctor(object 'object',
native int 'method') runtime managed
// SIG: 20 02 01 1C 18
{
} // ...............::.ctor
.method /*0600000C*/ public hidebysig virtual
instance int32
//Here I need to inject that code.
Invoke(int32 a,
int32 b) runtime managed
// SIG: 20 02 08 08 08
{
} // .................::Invoke
现在,代码注入将变得容易得多。 通过 CallconvAttribute
,我们可以轻松识别我们的委托声明。
代码
在源代码中,你会找到一个 .NET 控制台应用程序和一个非托管 DLL,我们在其中实现了整个概念。 祝您 MSIL 学习愉快..