65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.30/5 (16投票s)

2005年12月22日

2分钟阅读

viewsIcon

89255

downloadIcon

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 学习愉快..

© . All rights reserved.