【问题标题】:C# interop AccessViolationException with managed callback带有托管回调的 C# 互操作 AccessViolationException
【发布时间】:2011-09-20 04:42:49
【问题描述】:

我有一个非托管 dll,它分配一个结构并传递一个指向该结构的指针。我已经创建了一个与该结构等效的 c#,并且可以愉快地编译和运行我的代码来使用它。该结构中有一个可选指针,允许您连接一个函数指针,该指针将在非托管代码运行时调用。当我尝试将托管委托连接到结构的指针并将其传回时,它会因 AccessViolationException 而爆炸。我错过了什么?

更多细节:

非托管 c 代码:

typedef struct MyStruct {
   :
   :
   int flags
   :
   int (*cback)(MyStruct *s, Other *o)
   :
} MyStruct;

C# 等效项:

[StructLayout(LayoutKind.Sequential)]
public class MyStruct
{
   :
   :
   [MarshalAs(UnmanagedType.I4)]
   public int flags;
   :
   public IntPtr cback;
   :
};

获得了指向非托管结构的指针

managedMyStruct = (MyStruct)
    Marshal.PtrToStructure(pUnmanagedMyStruct, typeof(MyStruct));                    

managedMyStruct.cback = 
    Marshal.GetFunctionPointerForDelegate(ManagedDelegateRef);                    

// Update pointer
Marshal.StructureToPtr(managedMyStruct, pUnmanagedStruct, true);

当我将 pUnmanagedStruct 传递给最终调用 cback 的非托管函数时,我的 cback 委托被调用一次,应用程序因 AccessViolationException 而崩溃。

感激地收到任何线索。

一个

【问题讨论】:

  • 没有看到 ManagedDelegateRef 的声明就无法猜测。肯定是错的。
  • 是的,请提供有关 ManagedDelegateRef 的更多详细信息。

标签: c# interop


【解决方案1】:

ManagedDelegateRef 指向什么?静态方法还是实例方法?如果是实例方法,请确保该实例不会被垃圾回收。

【讨论】:

    【解决方案2】:

    我遇到了类似的情况:我将一个指向托管回调的指针传递给非托管代码,当回调被调用时,函数运行一次,然后程序崩溃了。

    我没有收到 AccessViolationException - 我没有收到任何个异常 - 但您的问题的原因可能与我的相同。

    我的问题的解决方法如下:

    根据[1]有不同的函数调用约定:旧的__cdecl和新的__stdcall;非托管 C/C++ 默认使用 __cdecl,C# 默认使用 __stdcall

    我猜您的非托管代码正在使用默认的__cdecl 约定。如果您可以更改非托管代码中的约定,那么这可能是您的解决方案。

    不幸的是,我使用的是第三方 DLL,无法更改其中的非托管调用约定。我的程序需要做的是告诉 C# 我传递的委托是使用 __cdecl 约定。

    不幸的是,没有办法直接告诉 C#。 (你会认为会有一个可以使用的属性,但显然 MS 还没有为 C# 实现一个,尽管我相信托管 C++ 有一个)。

    为了解决这个问题,需要使用一些技巧:

    程序的输出 (DLL/EXE) 需要使用 Visual Studio 命令提示符中的ildasm 命令进行反编译:

    cmd>  ildasm /out=output.il OUTPUT.EXE
    

    然后在 IL 代码中的委托的 Invoke 方法中添加一个属性,告诉它使用 __cdecl 调用约定:

    // output.il
    .
    .
    .
    
    .class public auto ansi sealed NAMESPACE.ManagedDelegate
           extends [mscorlib]System.MulticastDelegate
    {
      .custom instance void NAMESPACE.UseCCallingConventionAttribute::.ctor() = ( 01 00 00 00 ) 
      .method public hidebysig specialname rtspecialname 
              instance void  .ctor(object 'object',
                                   native int 'method') runtime managed
      {
      } // end of method ManagedDelegate::.ctor
    
      .method public hidebysig newslot virtual 
              instance void  Invoke(native int pUser,
                                    int32 state) runtime managed
      {
      } // end of method ManagedDelegate::Invoke
    
      .method public hidebysig newslot virtual 
              instance class [mscorlib]System.IAsyncResult 
              BeginInvoke(native int pUser,
    
    .
    .
    .
    

    成为:

    .
    .
    .
    
    .class public auto ansi sealed NAMESPACE.ManagedDelegate
           extends [mscorlib]System.MulticastDelegate
    {
      .custom instance void NAMESPACE.UseCCallingConventionAttribute::.ctor() = ( 01 00 00 00 ) 
      .method public hidebysig specialname rtspecialname 
              instance void  .ctor(object 'object',
                                   native int 'method') runtime managed
      {
      } // end of method ManagedDelegate::.ctor
    
      .method public hidebysig newslot virtual 
              instance void #####modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)##### Invoke(native int pUser,
                                    int32 state) runtime managed
      {
      } // end of method ManagedDelegate::Invoke
    
      .method public hidebysig newslot virtual 
              instance class [mscorlib]System.IAsyncResult 
              BeginInvoke(native int pUser,
    
    .
    .
    .
    

    没有散列。 (本例中委托类型的名称是 ManagedDelegate -- 我不确定您的类型名称是什么。)

    (注意:[1] 和 [2] 建议在委托上放置一个占位符属性,以便您可以轻松地在 .il 文件中找到该方法;UseCCallingConventionAttribute 是我的。)

    然后代码文件用ilasm重新编译:

    cmd>  ilasm output.il
    

    使用/DLL/EXE,具体取决于您的输出类型——/EXE 是默认值。

    这是对我的项目有效的修复方法。

    [1] 中更详细地概述了整个过程,有人在 [2] 中发布了一个 Perl 脚本来执行此操作。我还没有实现自动化,而且我是 VS n00b,所以我不知道这是否可以作为构建中的一个步骤添加,但它确实存在。

    希望这会有所帮助。

    [1]http://www.codeproject.com/KB/cs/cdeclcallback.aspx

    [2]http://www.dotnet247.com/247reference/msgs/17/87210.aspx

    【讨论】:

      猜你喜欢
      • 2021-05-31
      • 1970-01-01
      • 1970-01-01
      • 2012-01-03
      • 1970-01-01
      • 2011-06-19
      • 2013-10-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多