使用 .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 传递参数

用户注销 React Native 应用程序时如何删除 Firebase 云消息传递令牌?

如何在 Pig 中使用 MapReduce Native 传递命令行参数