2011-03-01 120 views
2

我有一個非託管的dll,它分配一個結構並傳遞出一個指向該結構的指針。我創建了一個相當於該結構的c#,並且可以愉快地編譯和運行我的代碼來使用它。該結構中有一個可選的指針,允許您掛接一個函數指針,該指針在非託管代碼運行時將被調用。當我嘗試將託管代理掛接到結構的指針並將其傳回時,它會爆發AccessViolationException。我錯過了什麼?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。

感激地收到了任何線索。

一個

+0

不可能沒有看到ManagedDelegateRef的聲明來猜測。肯定是錯的。 – 2011-03-01 21:20:29

+0

是的,請提供有關ManagedDelegateRef的更多詳細信息。 – Coincoin 2011-03-01 21:23:44

回答

1

什麼是ManagedDelegateRef指向?靜態方法或實例方法?如果它是一個實例方法,請確保該實例沒有被垃圾收集。

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代碼委託的調用方法來告訴它使用__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