我遇到了类似的情况:我将一个指向托管回调的指针传递给非托管代码,当回调被调用时,函数运行一次,然后程序崩溃了。
我没有收到 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