当前位置: 首页 > news >正文

【在 C# 中通过 P/Invoke 调用 C++ DLL 时的数据类型转换】

在 C# 中通过 P/Invoke 调用 C++ DLL 时,数据类型需要手动转换,因为 C# 和 C++ 的底层内存表示不同。以下是常见数据类型的转换方法及注意事项:


一、基本数据类型

C++ 类型C# 对应类型备注
intint32 位整数
longint (Windows)Windows 下 long 为 32 位
long longlong64 位整数
floatfloat32 位浮点数
doubledouble64 位浮点数
charbytesbyte取决于 char 是否签名
bool[MarshalAs(UnmanagedType.I1)] boolC++ 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] StringBuilderUnmanagedType.LPStr
char* (可修改)[Out] StringBuilderUnmanagedType.LPStr
const wchar_t*string[In] StringBuilderUnmanagedType.LPWStr
wchar_t* (可修改)[Out] StringBuilderUnmanagedType.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# 中使用,需通过:
    1. C++/CLI 包装器(推荐)。
    2. 将类拆分为函数接口(如 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);

七、常见问题与解决

  1. Marshal.SizeOf 错误

    • 确保结构体定义与 C++ 完全一致(包括对齐方式 Pack)。
  2. 字符串乱码

    • 明确指定 CharSet(如 CharSet = CharSet.Unicode)。
  3. 内存泄漏

    • 释放 Marshal.AllocHGlobal 分配的内存。
    • 对 C++ 分配的内存提供对应的释放函数。
  4. 调用约定不匹配

    • DllImport 中显式指定 CallingConvention.CdeclStdCall

八、总结

  • 简单类型intfloat):直接映射。
  • 字符串:用 StringBuilder + MarshalAs
  • 指针/数组IntPtr + Marshal.Copy
  • 回调函数delegate + UnmanagedFunctionPointer
  • 复杂类:通过 C++/CLI 包装或拆解为 C 风格接口。

通过正确处理类型转换,可以安全高效地在 C# 中调用 C++ 代码。

注:内容由AI生成

http://www.dtcms.com/a/264889.html

相关文章:

  • 第二章-AIGC入门-文本生成:开启内容创作新纪元(4/36)
  • 字典课后练习讲解|5类数据容器的总结对比
  • 存储过程封装:复杂业务逻辑的性能优化
  • AntV L7 之LarkMap 地图
  • A模块 系统与网络安全 第三门课 网络通信原理-4
  • Notion 创始人 Ivan Zhao:传统软件开发是造桥,AI 开发更像酿酒,提供环境让 AI 自行发展
  • 机器学习在智能制造业中的应用:质量检测与设备故障预测
  • 使用v-bind指令绑定属性
  • VUE admin-element 后台管理系统三级菜单实现缓存
  • flutter更改第三方库pub get的缓存目录;更改.gradle文件夹存放目录
  • BERT Score是干啥的?
  • 【python】pdf拆成图片,加中文,再合成pdf
  • 网络协议传输层UDP协议
  • 【NLP第一期 语料处理:从获取到预处理的完整链路解析】
  • 非接触式DIC测量系统:助力汽车研发与测试的创新技术应用
  • 从UI设计到数字孪生实战部署:构建智慧农业的智能灌溉系统
  • 数据结构学习之栈
  • Rust实现黑客帝国数字雨特效
  • 软件开发早期阶段,使用存储过程的优势探讨:敏捷开发下的利器
  • Spark从入门到熟悉(篇二)
  • Xbox One 控制器转换为 macOS HID 设备的工作原理分析
  • Ubuntu云服务器上部署发布Vite项目
  • 阿里云实时语音识别
  • 无线网络标准信道宽度参数速查
  • 人体属性识别+跌倒检测:儿童行为监测与安全升级
  • 【构造】P8976 「DTOI-4」排列|普及+
  • 2025最新全球AI大模型排名 国内外模型动态洗牌
  • 【Linux】不小心又创建了一个root权限账户,怎么将它删除?!
  • Linux 后台启动java jar 程序 nohup java -jar
  • PHP Yii2 安装SQL Server扩展-MAC M4 Pro芯片