C#interop AccessViolationException与托管回调

时间:2011-03-01 21:14:18

标签: c# interop

我有一个非托管的dll,它分配一个struct并传递一个指向该struct的指针。我创建了一个与该结构相当的c#,可以愉快地编译和运行我的代码来使用它。结构中有一个可选指针,允许您挂接一个在非托管代码运行时调用的函数指针。当我尝试将一个托管委托挂钩到struct的指针并将其传回时,它会因为一个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而爆炸。

感激地收到了任何线索。

A

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

我碰到了类似的东西:我传递了一个指向托管回调到非托管代码的指针,当调用回调函数运行一次时,程序崩溃了。

我没有得到AccessViolationException - 我没有得到任何例外 - 但是问题的原因可能与我的相同。

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

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

我猜你的非托管代码正在使用默认的__cdecl约定。如果您可以更改非托管代码中的约定,则可能是您的修复。

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

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

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

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

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