我有一个非托管的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
答案 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