什么可能导致P / Invoke参数通过时失序?

这是一个特别在ARM上发生的问题,而不是在x86或x64上。 我有一个用户报告的这个问题,并能够通过Windows IoT在Raspberry Pi 2上使用UWP进行重现。 我以前遇到过这种types的调用约定不匹配的问题,但是我在P / Invoke声明中指定了Cdecl,并试图在本地显式地添加__cdecl,并得到了相同的结果。 这里是一些信息:

P /调用声明( 参考 ):

[DllImport(Constants.DllName, CallingConvention = CallingConvention.Cdecl)] public static extern FLSliceResult FLEncoder_Finish(FLEncoder* encoder, FLError* outError); 

C#结构( 参考 ):

 internal unsafe partial struct FLSliceResult { public void* buf; private UIntPtr _size; public ulong size { get { return _size.ToUInt64(); } set { _size = (UIntPtr)value; } } } internal enum FLError { NoError = 0, MemoryError, OutOfRange, InvalidData, EncodeError, JSONError, UnknownValue, InternalError, NotFound, SharedKeysStateError, } internal unsafe struct FLEncoder { } 

C头中的函数( 引用 )

 FLSliceResult FLEncoder_Finish(FLEncoder, FLError*); 

FLSliceResult可能会导致一些问题,因为它是由值返回,并在本机上有一些C ++的东西?

本地结构有实际的信息,但对于C API,FLEncoder被定义为一个不透明的指针 。 当在x86和x64上调用上面的方法时,情况会很顺利,但在ARM上,我观察到以下情况。 第一个参数的地址是SECOND参数的地址,第二个参数是空的(例如,当我loginC#端的地址时,例如,0x054f59b8和0x0583f3bc,但是在本机端参数是0x0583f3bc和0x00000000)。 什么会导致这种乱序问题? 有没有人有任何想法,因为我难住…

这里是我运行重现的代码:

 unsafe { var enc = Native.FLEncoder_New(); Native.FLEncoder_BeginDict(enc, 1); Native.FLEncoder_WriteKey(enc, "answer"); Native.FLEncoder_WriteInt(enc, 42); Native.FLEncoder_EndDict(enc); FLError err; NativeRaw.FLEncoder_Finish(enc, &err); Native.FLEncoder_Free(enc); } 

运行一个C + +应用程序与以下工作正常:

 auto enc = FLEncoder_New(); FLEncoder_BeginDict(enc, 1); FLEncoder_WriteKey(enc, FLSTR("answer")); FLEncoder_WriteInt(enc, 42); FLEncoder_EndDict(enc); FLError err; auto result = FLEncoder_Finish(enc, &err); FLEncoder_Free(enc); 

这个逻辑可以触发与最新的开发人员构build的崩溃,但不幸的是,我还没有想出如何可靠地通过Nuget提供本地debugging符号,使它可以通过(只build立一切从源代码似乎做.. 。)所以debugging有点尴尬,因为需要构build本地和托pipe组件。 如果有人想尝试的话,我很乐意提供这方面的build议。 但是如果有人以前经历过这个,或者对此有什么想法,请添加一个答案,谢谢! 当然,如果有人想要一个复制的情况下(不是简单地build立一个不提供源代码或很难build立一个),然后留下一个评论,但我不想通过制作一个如果没有人会使用它(我不知道在实际的ARM上运行Windows的东西是多么的stream行)

编辑有趣的更新:如果我在C#中“伪造”签名并删除第二个参数,那么第一个参数通过OK。

编辑2第二个有趣的更新:如果我将大小的C#FLSliceResult定义从UIntPtr更改为ulong那么参数进来正确…这是没有意义的,因为ARM上的size_t应该是无符号整型。

编辑3添加[StructLayout(LayoutKind.Sequential, Size = 12)]在C#中的定义也使这个工作,但为什么? C / C ++中针对此体系结构的sizeof(FLSliceResult)返回8,因为它应该。 在C#中设置相同的大小会导致崩溃,但将其设置为12会使其工作。

编辑4我简化了testing用例,以便我也可以编写一个C ++testing用例。 在C#UWP中失败,但在C ++ UWP中成功。

编辑5 这里是反汇编C ++和C#的指令进行比较(尽pipeC#我不知道要拿多less,所以我犯了太多的错误)

编辑6进一步的分析表明,在“好”运行时,当我说谎,并说C#的结构是12个字节,返回值传递给寄存器r0,其他两个参数通过r1,r2进来。 然而,在坏运行,这是转移,使两个参数通过r0,r1进来,返回值是在别的地方(堆栈指针?)

编辑7我咨询了ARM体系结构的过程调用标准 。 我发现这个引用:“大于4字节的组合types,或者其大小不能由调用者和被调用者静态确定的复合types,在调用函数时作为额外参数存储在内存中(§5.5,规则A .4)。在函数调用过程中的任何一点都可以修改用于结果的内存。 这意味着传入r0是正确的行为,因为额外的参数意味着第一个参数(因为C调用约定没有指定参数个数的方法)。 我想知道CLR是否将这与另一个关于基本 64位数据types的规则相混淆:“在r0和r1中返回一个双字大小的基本数据types(例如,long long,double和64-bit容器化的向量)。

编辑8好吧有很多证据指向CLR在这里做错了事情,所以我提交了一个错误报告 。 我希望有人注意到在所有自动化机器上发布的回购问题:-S。