【在 C# 中通过 P/Invoke 调用 C++ DLL 时的数据类型转换】
在 C# 中通过 P/Invoke 调用 C++ DLL 时,数据类型需要手动转换,因为 C# 和 C++ 的底层内存表示不同。以下是常见数据类型的转换方法及注意事项:
一、基本数据类型
C++ 类型 | C# 对应类型 | 备注 |
---|---|---|
int | int | 32 位整数 |
long | int (Windows) | Windows 下 long 为 32 位 |
long long | long | 64 位整数 |
float | float | 32 位浮点数 |
double | double | 64 位浮点数 |
char | byte 或 sbyte | 取决于 char 是否签名 |
bool | [MarshalAs(UnmanagedType.I1)] bool | C++ bool 可能是 1 字节 |
示例
// C++ 导出函数
extern "C" __declspec(dllexport) int Add(int a, int b);
// C# 声明
[DllImport("MyCppDll.dll")]
public static extern int Add(int a, int b);
二、字符串类型
C++ 的字符串(char*
、wchar_t*
)需要显式转换:
C++ 类型 | C# 对应类型 | MarshalAs 属性 |
---|---|---|
const char* | string 或 [In] StringBuilder | UnmanagedType.LPStr |
char* (可修改) | [Out] StringBuilder | UnmanagedType.LPStr |
const wchar_t* | string 或 [In] StringBuilder | UnmanagedType.LPWStr |
wchar_t* (可修改) | [Out] StringBuilder | UnmanagedType.LPWStr |
示例
// C++ 导出函数
extern "C" __declspec(dllexport) void Greet(char* name, wchar_t* message);
// C# 声明
[DllImport("MyCppDll.dll", CharSet = CharSet.Ansi)]
public static extern void Greet([MarshalAs(UnmanagedType.LPStr)] string name,[MarshalAs(UnmanagedType.LPWStr)] StringBuilder message
);// 调用
StringBuilder message = new StringBuilder(100);
Greet("Alice", message);
Console.WriteLine(message.ToString());
三、指针和句柄
1. 通用指针 (void*
)
- 在 C# 中用
IntPtr
表示,可存储任意指针。 - 需要手动转换(如
Marshal.Copy
读写内存)。
// C++ 导出函数
extern "C" __declspec(dllexport) void ProcessData(void* data, int length);
// C# 声明
[DllImport("MyCppDll.dll")]
public static extern void ProcessData(IntPtr data, int length);// 调用
byte[] buffer = { 1, 2, 3 };
fixed (byte* p = buffer)
{ProcessData((IntPtr)p, buffer.Length);
}
2. 结构体指针
- 定义对应的 C# 结构体,并用
StructLayout
指定内存布局。
// C++ 结构体
struct Point { int x; int y; };
extern "C" __declspec(dllexport) void PrintPoint(Point* p);
// C# 结构体
[StructLayout(LayoutKind.Sequential)]
struct Point { public int x; public int y; }[DllImport("MyCppDll.dll")]
public static extern void PrintPoint(ref Point p); // 或 IntPtr// 调用
Point p = new Point { x = 10, y = 20 };
PrintPoint(ref p);
四、数组和缓冲区
1. 固定大小数组
- 在结构体中用
[MarshalAs(UnmanagedType.ByValArray, SizeConst=N)]
。
// C++ 结构体
struct Data { int values[3]; };
extern "C" __declspec(dllexport) void ProcessData(Data data);
// C# 结构体
[StructLayout(LayoutKind.Sequential)]
struct Data
{[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public int[] values;
}[DllImport("MyCppDll.dll")]
public static extern void ProcessData(Data data);
2. 动态数组
- 使用
IntPtr
+Marshal.Copy
手动转换。
// C++ 导出函数
extern "C" __declspec(dllexport) void ProcessArray(int* arr, int length);
// C# 声明
[DllImport("MyCppDll.dll")]
public static extern void ProcessArray(IntPtr arr, int length);// 调用
int[] managedArray = { 1, 2, 3 };
IntPtr unmanagedArray = Marshal.AllocHGlobal(managedArray.Length * sizeof(int));
Marshal.Copy(managedArray, 0, unmanagedArray, managedArray.Length);ProcessArray(unmanagedArray, managedArray.Length);// 释放内存
Marshal.FreeHGlobal(unmanagedArray);
五、函数指针(回调)
- 在 C# 中用
delegate
表示,需标记为UnmanagedFunctionPointer
。
// C++ 导出函数
typedef void (*Callback)(int result);
extern "C" __declspec(dllexport) void RegisterCallback(Callback cb);
// C# 声明
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void CallbackDelegate(int result);[DllImport("MyCppDll.dll")]
public static extern void RegisterCallback(CallbackDelegate cb);// 调用(需保持委托不被垃圾回收)
static CallbackDelegate callback = result => Console.WriteLine($"Result: {result}");
RegisterCallback(callback);
GC.KeepAlive(callback); // 防止 GC 回收
六、类对象(不推荐直接传递)
- C++ 类无法直接在 C# 中使用,需通过:
- C++/CLI 包装器(推荐)。
- 将类拆分为函数接口(如
Create/Destroy
+ 操作函数)。
// C++ 类和工厂函数
class MyClass { /*...*/ };
extern "C" __declspec(dllexport) MyClass* CreateInstance();
extern "C" __declspec(dllexport) void DisposeInstance(MyClass* ptr);
// C# 声明
[DllImport("MyCppDll.dll")]
public static extern IntPtr CreateInstance();[DllImport("MyCppDll.dll")]
public static extern void DisposeInstance(IntPtr ptr);// 调用
IntPtr instance = CreateInstance();
// ... 通过其他 P/Invoke 函数操作实例
DisposeInstance(instance);
七、常见问题与解决
-
Marshal.SizeOf
错误- 确保结构体定义与 C++ 完全一致(包括对齐方式
Pack
)。
- 确保结构体定义与 C++ 完全一致(包括对齐方式
-
字符串乱码
- 明确指定
CharSet
(如CharSet = CharSet.Unicode
)。
- 明确指定
-
内存泄漏
- 释放
Marshal.AllocHGlobal
分配的内存。 - 对 C++ 分配的内存提供对应的释放函数。
- 释放
-
调用约定不匹配
- 在
DllImport
中显式指定CallingConvention.Cdecl
或StdCall
。
- 在
八、总结
- 简单类型(
int
、float
):直接映射。 - 字符串:用
StringBuilder
+MarshalAs
。 - 指针/数组:
IntPtr
+Marshal.Copy
。 - 回调函数:
delegate
+UnmanagedFunctionPointer
。 - 复杂类:通过 C++/CLI 包装或拆解为 C 风格接口。
通过正确处理类型转换,可以安全高效地在 C# 中调用 C++ 代码。
注:内容由AI生成