C# 编组双包装结构 - 为什么 Double 行为奇怪?

问题描述 投票:0回答:1

我正在为用 C 实现的动态链接库编写一个包装器。

许多函数返回本质上是标准的、非托管的、blittable 类型,例如

int
long long
double
。为了区分从库返回的值,我想编写一些简单的
struct
来包装这些基本类型并将导入的函数标记为返回这些类型。

(这还有一个额外的好处,那就是允许我使用

int
实现布尔类型,因此它可以是 4 字节宽,从而与库返回的 4 字节值相匹配。)

首先一切都很顺利,我的

LibraryBoolean
LibraryInteger
LibraryLong
都工作正常,然后我到了
LibraryDouble
...

public struct LibraryDouble
{
    private double value;

    public LibraryDouble(double value)
    {
        this.value = value;
    }

    public override string ToString()
    {
        return this.value.ToString();
    }

    public static implicit operator LibraryDouble(double value)
    {
        return new LibraryDouble(value);
    }

    public static implicit operator double(LibraryDouble number)
    {
        return number.value;
    }
}
[DllImport("Library.dll", EntryPoint = "get_double_value", ExactSpelling = true)]
public static extern LibraryDouble get_double_value([In] int index);

突然每次返回的值都是

4.94065645841247E-324

令人烦恼的是,我发现返回一个普通的

double
的行为符合预期,而奇怪的行为是我拥有包装器结构的结果。然而,我使用过的其他包装结构都没有表现出类似的行为,这似乎是 double 所独有的。

显然,我可以通过编写包装函数来规避这个问题......

public static LibraryDouble get_double_value([In] int index)
{
    return _get_double_value(index);
}

[DllImport("Library.dll", EntryPoint = "get_double_value", ExactSpelling = true)]
private static extern double _get_double_value([In] int index);

但我想确切地知道为什么

double
似乎存在问题,特别是当其他类型似乎都没有出现此问题时。

为什么我会看到这种奇怪的行为?

c# struct double interop marshalling
1个回答
0
投票

您遇到的问题与 LibraryDouble 结构在托管 C# 代码和非托管 C 代码之间封送的方式有关。

当您使用私有字段 (

LibraryDouble
) 声明
value
结构并使用隐式转换运算符时,它会为互操作编组器引入额外的复杂性。编组器需要处理
LibraryDouble
结构体和非托管
double
类型之间的转换。

问题在于编组器如何处理具有私有字段的结构,并且它可能无法将托管

LibraryDouble
结构的内存布局与非托管
double
类型正确对齐。因此,当库函数返回
double
时,它可能无法正确读入
LibraryDouble
结构体。

要解决此问题,您可以使用

StructLayout
属性来指定结构体的布局。此外,您应该使用
Sequential
布局标记结构并将
Pack
属性设置为 1,以确保内存布局与非托管类型兼容:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct LibraryDouble
{
    private double value;

    public LibraryDouble(double value)
    {
        this.value = value;
    }

    public override string ToString()
    {
        return this.value.ToString();
    }

    public static implicit operator LibraryDouble(double value)
    {
        return new LibraryDouble(value);
    }

    public static implicit operator double(LibraryDouble number)
    {
        return number.value;
    }
}

通过使用

StructLayout
显式指定布局,可以帮助确保结构的内存布局与非托管代码期望的布局相匹配。

但是,请记住,如果结构布局与库的本机期望不匹配,则使用具有显式布局的结构可能会导致兼容性问题。请始终参考库文档或标题,以确保正确的对齐和布局。

© www.soinside.com 2019 - 2024. All rights reserved.