使用 .NET Native 构建时,参数不会传递给 x86 上的非托管 DLL
Posted
技术标签:
【中文标题】使用 .NET Native 构建时,参数不会传递给 x86 上的非托管 DLL【英文标题】:Parameters aren’t passed to unmanaged DLL on x86 when building with .NET Native 【发布时间】:2015-12-09 19:58:08 【问题描述】:我正在构建一个 Windows 10 通用应用程序(手机 + 平板电脑)+ 库。
在解决方案中,我有 C++ dll 项目,该项目构建从 C# 调用的非托管 my.dll
。
DLL 有这样的导出:
// === C++ ===
typedef struct int f1; uint32_t f2; R;
// A and B are also structures.
MY_EXPORT R the_function( A *a, const B *b, const uint8_t *c );
// === C# ===
[DllImport( "my.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl )]
extern static R the_function(A a, B b, byte[] c);
[StructLayout( LayoutKind.Sequential )]
internal struct R
public int f1; // Actually a enum but it shouldn’t matter.
public uint f2_id;
internal struct A
IntPtr nativePtr;
internal struct B
IntPtr nativePtr;
测试应用可在 ARM 和 X64 平台上运行。如果未选中“使用 .NET Native 工具链编译”,则它适用于 X86。
如果选中“使用 .NET Native 工具链编译”,则非托管 DLL 在 X86 上崩溃,表示访问冲突。我可以在 Debug 和 Release 版本中重现。
使用调试器时,我发现参数的传递方式存在错误。 在 C# 方面,在一些编译器生成的 C# 代码中,有这样一行:
unsafe___value = global::McgInterop.my_PInvokes.the_function( a, b, unsafe_c );
在调试器中,我确认参数没问题。
在 C++ 方面,这些值是错误的。 b的值是a传入的值,c的值是b传入的值。
我尝试创建一个简约的示例但失败了,它可以正常工作。 my.dll 导出 100 多个导出的 __cdecl 方法,这是我正在开发的大型跨平台 C++ SDK,旨在将其引入 Windows 10 平台,看起来其余方法都可以正常工作。
有什么想法吗?或者至少我如何隔离问题?提前致谢。
更新:好的,这是一个最小的复制。
非托管代码:
typedef struct
int f1;
DWORD f2;
R;
R __cdecl nativeBug( int a, int b )
CStringA str;
str.Format( "Unmanaged DLL: a = %i, b = %i\n", a, b );
::OutputDebugStringA( str );
R res
11, 12
;
return res;
C# 商店应用:
[StructLayout( LayoutKind.Sequential )]
struct R
public int f1;
public uint f2;
[DllImport( "NativeBugDll.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl )]
extern static R nativeBug( int a, int b );
private void Page_Loaded( object sender, RoutedEventArgs e )
App.Current.UnhandledException += app_UnhandledException;
R r = nativeBug( 1, 2 );
Debug.WriteLine( "Result: f1=0, f2=1", r.f1, r.f2 );
private void app_UnhandledException( object sender, UnhandledExceptionEventArgs e )
Debug.WriteLine( "Unhandled exception: " + e.Message );
没有 .NET Native 的调试输出很好:
Unmanaged DLL: a = 1, b = 2
Result: f1=11, f2=12
这是使用 .NET Native 构建的调试输出:
Unmanaged DLL: a = 91484652, b = 1
Unhandled exception: Object reference not set to an instance of an object.
STATUS_STACK_BUFFER_OVERRUN encountered
然后视觉工作室完全挂起。
即使使用 .NET Native,X64 构建也能正常工作。
【问题讨论】:
显示 C++ 代码(至少是函数签名) IIRC,x86 是您提到的三个平台中唯一一个 stdcall 和 cdecl 规则不同的平台。 请看更新,添加标题。 你说得对,调用约定是正确的,在 X86 上有十几个,在 ARM 和 X64 上只有一个。令我困惑的是我的 X86 版本运行良好,除非更高级别的 C# 部分是由 .NET 本机工具链编译的。顺便说一句,我正在开发一个商业销售的 SDK,默认情况下 VS2015 使用 .NET native 编译发布版本,我不希望付费客户会因为无法使用 .NET native 编译而感到高兴…… 是的,我完全理解 net native 需要工作的原因。 【参考方案1】:哎呀!看起来 .NET Native 中可能存在错误。我已经请微软这里的人看一下。如果您想在内部与我们联系,请随时通过 dotnetnative@microsoft.com 向我们发送邮件。
随着我们了解的更多,我会更新此内容。
编辑: 因此,如果本机函数返回这样的结构,则肯定存在真正的错误。优化器最终处于这样一种状态,即在两个参数之后将一个额外的参数推入堆栈,这就是导致错误的原因。
我已经打开了一个错误,我们将为 VS 的更新 2 修复这个错误。
C#:
[StructLayout(LayoutKind.Sequential)]
struct R
public int f1;
public int f2;
[DllImport("DllImport_NativeDll.dll")]
extern static R nativeBug(int a, int b);
public static void Run()
R r = nativeBug(1, 2);
原生:
typedef struct
int f1;
int f2;
R;
extern "C" __declspec(dllexport) R nativeBug(int a, int b)
R res
11, 12
;
return res;
生成的代码:
00f1766b 8b55fc mov edx,dword ptr [ebp-4]
00f1766e 52 push edx
00f1766f 8b45f8 mov eax,dword ptr [ebp-8]
00f17672 50 push eax
00f17673 8d4ddc lea ecx,[ebp-24h]
00f17676 51 push ecx <-- Bonus and unfortunate push
00f176ab ff1524b4d200 call dword ptr [PInvokeAndCom!_imp__nativeBug (00d2b424)]
【讨论】:
谢谢,我刚刚通过电子邮件发送了我的最小复制解决方案。【参考方案2】:MY_EXPORT R the_function( A *a, const B *b, const uint8_t *c );
前两个参数包含结构的地址。
extern static R the_function(A a, B b, byte[] c);
在这里,您按值传递结构。鉴于问题中的代码,这是我能看到的唯一区别。要传递结构的地址,请将 C# 更改为:
extern static R the_function(ref A a, ref B b, byte[] c);
【讨论】:
这些结构表示具有 C 风格 API 的引用计数对象。我希望 C# 代码将这些指针视为不透明的。这就是为什么在 C# 代码中我将它们的原始指针存储在另一个结构中。我本来可以只使用 IntPtr 类型,但是使用这些 C# 结构,编译器可以确保类型安全,例如我不能将 B 的实例传递给 a_release(A a) 调用,因为它不会编译。请注意,我的代码可以在 ARM(所有配置)、X64(所有配置)和 X86(关闭 .NET Native 时)上编译和运行,这就是我认为我正确地完成了这部分的原因。 好吧,我猜不出这些细节。为什么要隐瞒这么多?你需要帮助吗? 抱歉,我好像在隐瞒。这只是我正在开发的一个复杂的 SDK。我的问题已经有两页了,如果我要告诉所有这些技术细节,它会更长。 Hmya,我想您应该知道您正在查看虚假代码,但在 jitted 时也无法工作。上帝禁止他做最少的复制品。 @HansPassant 我将非托管内存指针存储在 IntPtr 数据类型中。 IntPtr 的文档说“一种特定于平台的类型,用于表示指针或句柄。”这有什么好骗的?几乎所有非托管互操作代码都会做类似的事情。以上是关于使用 .NET Native 构建时,参数不会传递给 x86 上的非托管 DLL的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Typescript 构建 React Native 导航以进行属性传递
在 React Native 中的 SQLite 上传递两个参数时无法获取数据
React Native 到 Swift 桥 - 传递参数和回调
在 react-native android 应用程序中使用 axios 传递参数