从C#调用C++函数,传递类型剥离的void指针

本文关键字:剥离 类型 void 指针 调用 C++ 函数 | 更新日期: 2023-09-27 18:25:22

我正在尝试使用用ANSI C编写的第三方DLL(编译为C++)。其中一个函数具有以下C++原型:

tResultCode GetAttrib
(
    tHandle     handle,
    tAttrib     attrib,
    void *      value,
    tAttribType valueType,
    int         valueMaxSize
);

其中GetAttrib的自变量如下:

  • handle是指向DLL中不透明结构的空指针
  • attrib指示从结构中检索哪个属性
  • value—指向valueType所指示类型的变量的指针
  • valueType指示被分配用于保持属性值
  • valueMaxSize是分配给value指向的变量的字节数

eAttribType枚举定义如下:

typedef enum eAttribType
{
    eHandle,
    eBool,
    eEnum,
    eInt,
    eLong,
    eFloat,
    eDouble,
    eDate,
    eTime,
    eTimestamp,
    eString,     // char *
    eChar,       // char
    eVoid,
    eHandle,
    eFunctionPointer
} tAttribType;

eAttrib枚举定义如下:

typedef enum eAttrib
{
    eInvoiceNumber, // eString
    eInvoiceDate,   // eTimestamp
    eUnits,         // eLong
    ePrice,         // eFloat
    eDiscount,      // eDouble
    ePreferredFlag, // eBool
    ...
} tAttrib;

我在C#中声明非托管函数指针如下:

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    internal delegate eResultCode getAttrib(void** handle, eAttrib attrib, void* value, eAttribType attribType, int valueMaxSize);
    internal unsafe getAttrib GetAttrib;

其中:

  • handle是指向C++库结构的指针的void **
  • attrib模拟C++枚举,该枚举指示要检索的属性
  • value是C++变量的void *
  • attribType模仿C++枚举,该枚举指示value的C++类型
  • valueMaxSize是一个整数,表示value指向的变量的大小

我的问题是,我不知道如何使用void *value变量)的不同数据类型从C#调用GetAttrib()。有时这个变量是一个C++char *,其他时候它是一个C++int,还有一些时候它是C++enum,等等

如果有人能告诉我如何正确地分配/填充C#中的char *eString)和longeLong),我认为其余的都会到位(希望)。

从C#调用C++函数,传递类型剥离的void指针

首先,eString是一种特殊情况,因为它是唯一的可变长度类型。其他类型是固定大小的,应该更容易检索。

让我们从eLong开始。这映射到C++long,而这反过来应该可能映射到C#int,但这取决于体系结构,因此您的里程可能会有所不同。

  • 在C#中,int总是32位,而long总是64位
  • 在C++中,没有固定的大小。众所周知,int定义为至少16位,long定义为至少32位。在Visual C++中,它们都是32位的

确保你知道自己在做什么,并且尺码合适。

现在,您需要一个指向非托管函数可以安全写入的不可移动内存的指针。碰巧,GC不会移动值类型堆栈变量,因此您可以简单地编写以下内容:

internal static unsafe int GetUnits(void** handle)
{
    int value;
    GetAttrib(handle, eAttrib.eUnits, &value, eAttribType.eLong, sizeof(int));
    return value;
}

另一种方法涉及stackalloc:

internal static unsafe int GetUnits(void** handle)
{
    var buffer = stackalloc int[1];
    GetAttrib(handle, eAttrib.eUnits, buffer, eAttribType.eLong, sizeof(int));
    return *buffer;
}

如果没有适合的C#内置类型,您可以将其与stackalloc byte[whatever]一起使用。

这显然是未经测试的,因为我没有lib,但它应该做到。

至于字符串,您需要一个托管数组,这样您就可以将它们输入到Encoding.GetString中。是的,你也需要知道编码。我假设这里是ASCII。

internal static unsafe string GetInvoiceNumber(void** handle)
{
    var buffer = new byte[512];
    fixed (byte* bufAddr = &buffer[0])
    {
        GetAttrib(handle, eAttrib.eInvoiceNumber, bufAddr, eAttribType.eString, buffer.Length);
        return Encoding.ASCII.GetString(buffer);
    }
}

由于托管数组位于托管堆上,因此需要固定,以便GC在操作过程中不会移动它。这就是fixed语句的作用。

为了完整性:要通过C++API设置字符串属性,下面的方法可以实现

internal static unsafe string SetInvoiceNumber(void** handle, string value)
{
    var buffer = new byte[512];
    fixed (byte* bufAddr = &buffer[0])
    {
        Encoding.ASCII.GetBytes(value, 0, Math.Min(value.length, buffer.Length - 1), buffer, 0);
        SetAttrib(handle, eAttrib.eInvoiceNumber, bufAddr, eAttribType.eString);
    }
}

其中对应的集合函数具有以下C++原型:

tResultCode SetAttrib
(
    tHandle     handle,
    tAttrib     attrib,
    void *      value,
    tAttribType valueType
);