将C Struct编组为C#委托的返回值 [英] Marshalling of C Struct as return value of C# delegate

查看:135
本文介绍了将C Struct编组为C#委托的返回值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试从绑定到本机函数的委托中按值返回一个小的(8字节)结构,但是在面向.NET Framework 2.0时遇到以下错误(定向时该代码似乎可以正常工作4.0 +):

I'm attempting to return a small (8 byte) struct by value from a delegate bound to a native function, but am running into the following error when targeting the .NET Framework 2.0 (the code seems to work correctly when targeting 4.0+):

testclient.exe中发生了'System.AccessViolationException'类型的未处理异常

An unhandled exception of type 'System.AccessViolationException' occurred in testclient.exe

其他信息:尝试读取或写入受保护的内存.这通常表明其他内存已损坏.

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

我怀疑我弄乱了托管类型注释,以致返回值未正确编组,但是我看不到我在做什么错.下面是重现该问题的小型本机测试DLL和托管客户端的代码.

I suspect I've messed up the managed type annotations such that the return value isn't being marshalled correctly, but I can't see what I'm doing wrong. Below is the code for a small native test DLL and managed client which reproduces the problem.

//Natural alignment, blittable, sizeof(StatusBlock) == 8
struct StatusBlock{
    std::uint32_t statusA;
    std::uint32_t statusB;
};

/*
 * When compiled this function stores the 64bit return value in the
 * eax:edx register pair as expected.
 */
static StatusBlock __cdecl SomeFunction(std::uint32_t const someVal){
    return StatusBlock{ someVal, 0x1234ABCD };
}

//Exported
extern "C" PVOID __stdcall GetFunctionPointer(){
    return &SomeFunction;
}

C#测试客户端

class Program
{

    //Blittable, Marshal.SizeOf(typeof(StatusBlock)) == 8
    [StructLayout(LayoutKind.Sequential)]
    private struct StatusBlock
    {
        public UInt32 statusA;
        public UInt32 statusB;
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate StatusBlock SomeFunction(UInt32 someVal);

    [DllImport("testlib.dll",CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr GetFunctionPointer();

    static void Main(string[] args)
    {
        var fnPtr = GetFunctionPointer();

        System.Diagnostics.Debug.Assert(fnPtr != IntPtr.Zero);

        var someFn = (SomeFunction)Marshal.GetDelegateForFunctionPointer(fnPtr, typeof(SomeFunction));

        /* 
         * Crashes here with a System.AccessViolationException when targeting .NET Framework 2.0.
         * Works as expected when targeting .NET Framework 4.0 +
         */
        var statusBlock = someFn(22);
    }
}

值得注意的是,如果委托的返回类型为Uint64,则该应用程序在.NET 2.0和4.0情况下均按预期工作.但是,我不必这样做. StatusBlock应该正确地编组.

It's worth noting that if the return type of the delegate is Uint64 the application works as expected in both the .NET 2.0 and 4.0 case. However, I shouldn't have to do this; StatusBlock should marshal correctly.

定位.NET 4.0时,我是否很幸运?任何对我做错事情的见解将不胜感激.

Have I just been lucky when targeting .NET 4.0? Any insight into what I'm doing wrong would be much appreciated.

推荐答案

.NET绝对是错误.

tl; dr

.NET Framework 2生成不正确的(可能是不安全的)存根.

It is by all means fault in .NET.

tl;dr

.NET Framework 2 generates an incorrect (possibly insecure) stub.

我进行了一些测试:

  1. 我使用的是4字节长的结构,而不是8字节长的结构.可行!
  2. 我使用x64而不是x86.可行!

弄清楚它在所有其他情况下都有效,所以我决定使用windbg对它进行本地调试,以查看崩溃的位置(因为Visual Studio不允许我通过反汇编窗口介入"本地call ).

Figuring out that it works in all other cases, I decided to native-debug it with windbg to see where it crashes (because Visual Studio won't allow me to "step in" a native call with the disassembly window).

猜猜我发现了什么:

Guess what I found:

.NET框架生成了一个调用memcpy的存根,当它尝试复制到edi时失败,该存根当时的值为0x16(== 22),这是在C#代码!

The .NET framework generated a stub that is calling memcpy, and it fails when it tries to copy into edi, which at that time had the value 0x16 (==22), which was the parameter sent in the C# code!

因此,让我们看看如果我向该函数发送有效的指针会发生什么情况

So, lets see what would happen if I were to send a valid pointer to the function:

unsafe
{
    long* ptr = &something;
    uint ptr_value = (uint)ptr;
    Console.WriteLine("Pointer address: {0:X}", (long)ptr);

    var statusBlock = someFn(ptr_value);
    Console.WriteLine("A: {0}", statusBlock.statusA);

    Console.WriteLine("B: {0:X}", statusBlock.statusB);
}

输出:(当给出有效的指针时,它可以工作并且不会崩溃!)

Output: (it works and doesn't crash when a valid pointer is given!)

Marshal.SizeOf(typeof(StatusBlock)) = 8
Running .NET Version 2
Pointer address: 49F15C
A: 0
B: 0

因此,我得出结论,这是.NET Framework 2中无法解决的问题.

So, I conclude this is an unsalvageable problem in .NET Framework 2.

当C函数定义为返回大于8个字节的struct时,该函数实际上应返回指向本地函数stackstruct的指针,并且调用方应使用memcpy复制它到它自己的stack(这是C规范的一部分,由编译器实现-程序员只需返回"一个结构,编译器就可以完成繁重的工作).

When a C function is defined to return a struct larger than 8 bytes, the function shall actually return a pointer to the struct in the local function's stack and the caller should use memcpy to copy it to it's own stack (this is a part of C's specifications and is implemented by the compiler - the programmer simply "returns" a struct and the compiler does the heavy lifting).

但是,对于8个字节(structlong long),大多数C编译器都在eax:edx中返回它. .NET的开发人员可能错过了这一点.错误可能是有人写了size >= 8而不是size > 8 ...

However, for 8 bytes (either struct or long long), most C compilers return it in eax:edx. Probably .NET's developers have missed that. The mistake is probably that someone wrote size >= 8 instead size > 8...

更糟糕的是,它将结果写在给定的指针上!

What's worse, it writes the result on the pointer given!

before: 0x1111222244445555
after : 0x1234ABCD007BEF5C

它将指针更改为返回值!如您所见,调用后的第一个dword是0x1234ABCD(与本机DLL中一样),第二个dword是指向值的指针,即给定的参数someVal

It changes the pointer to be the return value! As you can see, the first dword after the call is is 0x1234ABCD (as in the native DLL) and the second dword is the pointer to the value, i.e the parameter someVal that was given!

这更有趣,因为如果您将指针传递给StatusBlock结构-它实际上将在此特定情况下起作用(因为返回值中的第一个dword用作指针)

It's even more funny, because if you pass a pointer to a StatusBlock struct - it will actually work for this specific case (because the first dword in the return value is used as a pointer)

返回一个long变量并自行创建该结构.

Return a long variable and make the struct yourself.

这篇关于将C Struct编组为C#委托的返回值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆