在 C# 中将 byval C 结构编组为返回值

发布于 2024-10-15 06:56:29 字数 736 浏览 4 评论 0原文

我有非托管代码:

...
typedef struct foo  
{  
 int  a;  
 bool b
 int  c;  
} FOO,*LPFOO;
....
__declspec(dllexport) FOO __stdcall GetFoo()  
{  
   FOO f;  
   <some work>  
   return f;   
}  
....

我已经为 GetFoo 函数声明了 C# 原型:

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct Foo
    {
      public int  a;  
      public bool b
      public int  c; 
    };

    [DllImport("foo.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    [return:MarshalAs( UnmanagedType.Struct)]        
    private static extern Foo GetFoo();

但是当我从 C# 代码调用 GetFoo 时,我总是遇到 MarshalDirectiveException - 方法的类型签名与 PInvoke 不兼容。我应该如何声明 C# 原型?

I have unmanaged code:

...
typedef struct foo  
{  
 int  a;  
 bool b
 int  c;  
} FOO,*LPFOO;
....
__declspec(dllexport) FOO __stdcall GetFoo()  
{  
   FOO f;  
   <some work>  
   return f;   
}  
....

I've declare C# prototype for GetFoo function:

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct Foo
    {
      public int  a;  
      public bool b
      public int  c; 
    };

    [DllImport("foo.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    [return:MarshalAs( UnmanagedType.Struct)]        
    private static extern Foo GetFoo();

But when I calling GetFoo from C# code I alway have MarshalDirectiveException- Method's type signature is not PInvoke compatible. How I should declare C# prototype?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你列表最软的妹 2024-10-22 06:56:29

是的,返回结构的函数往往难以互操作。这样的结构必须是可 blittable 的,以便 pinvoke 编组器可以将指针传递给函数,准备好写入返回值。 “blittable”意味着托管代码中的结构布局需要与结构的非托管布局相同。如果不是,则需要制作副本,pinvoke 编组器不希望在返回值的特定情况下制作该副本。

bool 类型是一个互操作问题,不同的运行时会做出不同的选择。在 C 中通常为 4 个字节(与 Windows BOOL 类型相比,也是 pinvoke 的默认类型),在 COM 互操作(又名 VARIANT_BOOL)中为 2 个字节,在 C++ 中为 1 个字节,在 CLR 中为 1 个字节。由于目标运行时未知,CLR 无法猜测哪个选择是正确的。 BOOL 是默认值,4 个字节。

即使使用 [MarshalAs(UnmanagedType.U1)] 强制精确匹配也不会使其可直接传送。这很奇怪,我认为这是一个 CLR 错误。一个好的解决方法是将其替换为 byte,您可以使用属性将其包装回 bool。请注意,发布的代码片段中有很多错误,我使这个版本可以工作:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        Foo value = GetFoo();
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct Foo {
        public int a;
        private byte _b;
        public bool b {
            get { return _b != 0; }
        }
        public int c;
    };

    [DllImport(@"c:\projects\consoleapplication3\debug\cpptemp10.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "_GetFoo@0")]
    private static extern Foo GetFoo(/*int CoreIndex*/);
}

typedef struct foo  
{  
    int  a;  
    bool b;
    int  c;  
} FOO,*LPFOO;

extern "C" __declspec(dllexport) 
FOO __stdcall GetFoo()  
{  
    FOO f;  
    f.a = 42;
    f.b = true;
    f.c = 101;
    return f;   
}  

Yes, functions that return a structure tend to be difficult to interop with. Such a structure has to be blittable so the pinvoke marshaller can pass a pointer to the function, ready for it to write the return value. Being "blittable" means that the structure layout in managed code needs to be identical to the unmanaged layout of the structure. If it is not then a copy needs to be made, the pinvoke marshaller does not want to make that copy in the specific case of a return value.

The bool type is an interop problem, different runtimes made different choices. It tends to be 4 bytes in C (compare to the Windows BOOL type, also the default for pinvoke), 2 bytes in COM interop (aka VARIANT_BOOL), 1 byte in C++, 1 byte in the CLR. Since the target runtime is unknown, the CLR cannot guess which choice is right. BOOL is the default, 4 bytes.

Even using [MarshalAs(UnmanagedType.U1)] to force an exact match does not make it blittable. Which is pretty odd, I consider this a CLR bug. A good workaround is to replace it with byte, you can use a property to wrap it back to a bool. Beware that there were a lot of mistakes in the posted snippet, I made this version work:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        Foo value = GetFoo();
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct Foo {
        public int a;
        private byte _b;
        public bool b {
            get { return _b != 0; }
        }
        public int c;
    };

    [DllImport(@"c:\projects\consoleapplication3\debug\cpptemp10.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "_GetFoo@0")]
    private static extern Foo GetFoo(/*int CoreIndex*/);
}

typedef struct foo  
{  
    int  a;  
    bool b;
    int  c;  
} FOO,*LPFOO;

extern "C" __declspec(dllexport) 
FOO __stdcall GetFoo()  
{  
    FOO f;  
    f.a = 42;
    f.b = true;
    f.c = 101;
    return f;   
}  
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文