从非托管 C DLL 转换混合类型返回数据?

问题描述

我正在编写一个包装器 C# DLL 来与供应商提供的用 C 编写的非托管 DLL 进行交互。在大多数情况下,一切都很顺利,但是在提供的文档中有些函数看起来像这样:

int get_value(int unit,int id,int* value)

我的问题是 int* value一个混合了 int 和 float 的数组...文档说明了哪个,即第一个值是 int,第二个和第三个是float 等。如果我特别将 DLL value 称为 intfloat,我会取得一些成功。这将返回我正在寻找的一些数据,但仅针对一种类型。例如,这有点工作:

[DllImport("supplier.dll")]
public static extern int get_value(int unit,float* value)

public static int GetValue(int unit,out float[] value)
{
     float[] value = new float[3];
     int ret = get_value(unit,id,value);
     
     return ret;
}

value 与 [NaN,value1,value2] 一起返回,因为 0 索引实际上是一个整数并且没有正确解释。我曾尝试在非托管 DLL 中使用 IntPtr[] 作为返回 value,然后使用 Marshal.copy() 从内存位置获取数据,但这并不顺利。这是一个有前途的方法吗?如果是这样,我不确定要使用哪个重载,举个例子会很有帮助!

解决方法

int* value 是一个混合了 int 和 float 的数组...

这严重违反了 strict aliasing rule,并且会调用未定义的行为。在 C 中,你必须使用联合来代替。文档应该是这样的

union Value
{
    int i;
    float f;
};

int get_value(int unit,int id,union Value* value);

不考虑supplier.dll C#中的原始UB,你可以用FieldOffset模拟类似的功能

[StructLayout(LayoutKind.Explicit)] 
public struct Value
{
    [FieldOffset(0)] public int i;
    [FieldOffset(0)] public float f;
}

[DllImport("supplier.dll")]
public static extern int get_value(int unit,out Value[] value);

public static int GetValue(int unit,out Value[] value)
{
     float[] value = new float[3];
     int ret = get_value(unit,id,value);
     
     return ret;
}

获取数组后,只要使用正确的字段就可以得到期望值

var ret = GetValue(unit,out Value[] value);
var f0 = value[0].f;
var i1 = value[1].i;

如果你真的想在没有联合的情况下做到这一点,那么你必须像这样获得浮点数的二进制表示

float f0 = value[0];
int i1 = BitConverter.SingleToInt32Bits(value[1]);
int i2 = BitConverter.SingleToInt32Bits(value[2]);

value[1] = BitConverter.Int32BitsToSingle(f0 + 0.123f);

但是如果数组总是有 3 个这样的项目,那么为什么要返回一个数组而不是一个结构体呢?

public struct Values
{
    public float f0;
    public int i1;
    public int i2;
}

[DllImport("supplier.dll")]
public static extern int get_value(int unit,out Values values);

public static int GetValue(int unit,out Values values) {...}