在 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生成