从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
)和long
(eLong
),我认为其余的都会到位(希望)。
首先,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
);