在 C# 中引用 C++ 程序集(DLL)主要通过 P/Invoke(平台调用) 和 C++/CLI 包装器 两种方式实现。由于 C# 和 C++ 的底层机制不同(如内存管理、异常处理),直接调用 C++ DLL 需要处理类型转换、调用约定等细节。以下是详细指南:
适用场景:调用 C++ 导出的 纯函数(非类成员函数),通常通过 extern "C"
和 __declspec(dllexport)
暴露接口。
特点:
// MathLibrary.h
#pragma once
#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif
extern "C" {
// 导出纯函数(避免名称修饰)
MATHLIBRARY_API int Add(int a, int b);
MATHLIBRARY_API double Multiply(double a, double b);
}
// MathLibrary.cpp
#include "MathLibrary.h"
extern "C" {
MATHLIBRARY_API int Add(int a, int b) { return a + b; }
MATHLIBRARY_API double Multiply(double a, double b) { return a * b; }
}
using System;
using System.Runtime.InteropServices;
class Program
{
// 声明 DLL 导入(需指定路径或放在输出目录)
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Add(int a, int b);
[DllImport("MathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern double Multiply(double a, double b);
static void Main()
{
int sum = Add(3, 5);
double product = Multiply(2.5, 4.0);
Console.WriteLine($"Sum: {sum}, Product: {product}");
}
}
调用约定:
__cdecl
或 __stdcall
,需在 DllImport
中通过 CallingConvention
显式指定(如 CallingConvention.Cdecl
)。数据类型映射:
int
→ int
,double
→ double
。char*
需用 [MarshalAs(UnmanagedType.LPStr)]
转换。[StructLayout(LayoutKind.Sequential)]
的对应结构。DLL 路径:
bin\Debug
),或指定绝对路径。SetDllDirectory
动态加载路径。适用场景:需要调用 C++ 类、STL 容器或复杂对象时,通过 C++/CLI 创建托管-非托管桥接层。
特点:
// MathWrapper.h
#pragma once
#include "../MathLibrary/MathLibrary.h" // 原始 C++ 头文件
namespace MathWrapper {
public ref class Calculator // 托管类
{
public:
int Add(int a, int b) { return ::Add(a, b); } // 调用原生 C++ 函数
double Multiply(double a, double b) { return ::Multiply(a, b); }
};
}
using MathWrapper;
class Program
{
static void Main()
{
Calculator calc = new Calculator();
int sum = calc.Add(3, 5);
double product = calc.Multiply(2.5, 4.0);
Console.WriteLine($"Sum: {sum}, Product: {product}");
}
}
方式 | P/Invoke | C++/CLI 包装器 |
---|---|---|
适用性 | 简单函数 | 复杂类、STL、面向对象调用 |
性能 | 较高(直接调用) | 较低(需托管/非托管转换) |
开发复杂度 | 需手动处理类型和调用约定 | 自动类型转换,代码更简洁 |
内存管理 | 需手动处理(如 IntPtr ) |
自动托管内存 |
C++ 结构体:
// C++ 端
struct Point { int x; int y; };
extern "C" MATHLIBRARY_API int GetDistance(Point p1, Point p2);
C# 端:
[StructLayout(LayoutKind.Sequential)]
struct Point { public int x; public int y; }
[DllImport("MathLibrary.dll")]
public static extern int GetDistance(Point p1, Point p2);
C++ 端:
typedef void (*Callback)(int result);
extern "C" MATHLIBRARY_API void RunAsync(Callback cb);
C# 端:
delegate void CallbackDelegate(int result);
[DllImport("MathLibrary.dll")]
public static extern void RunAsync(CallbackDelegate cb);
// 调用时需保持委托不被垃圾回收
static void Main()
{
CallbackDelegate callback = result => Console.WriteLine($"Result: {result}");
RunAsync(callback);
Console.ReadLine(); // 防止主线程退出
}
char*
)需在 C++ 端提供释放函数,或在 C# 中用 Marshal.FreeHGlobal
释放。DllNotFoundException
PATH
环境变量)中。Dependency Walker
确认 C++ DLL 的依赖项是否完整。EntryPointNotFoundException
extern "C"
禁用名称修饰)。数据损坏
[StructLayout(LayoutKind.Sequential, Pack=1)]
指定对齐方式。LPStr
(ANSI)或 LPWStr
(Unicode)。Marshal.PtrToStringAnsi
等工具检查内存数据。通过合理选择方式,可以高效地在 C# 中复用 C++ 代码,平衡性能与开发效率。
注:内容由AI生成