C#调用C++ DLL的两种实现方法:托管与非托管详解

Elena
Elena
发布时间:2026-10-13 | 3人浏览过
原创

C#作为托管语言,无法直接加载由C++编译的非托管原生DLL。要在项目中实现C#调用C++ DLL,通常有两种解决方案:一是创建C++/CLI中间件项目,将其编译为托管程序集供C#引用;二是利用P/Invoke技术,通过[DllImport]特性在非托管模式下直接调用动态链接库。本文将分别演示这两种方法的工程配置、代码实现及注意事项。

目标与准备:理解托管与非托管差异

C#属于.NET生态下的托管型代码,其内存由垃圾回收机制自动管理;而C++编写的DLL属于非托管代码,需要开发者手动管理资源,否则易引发内存溢出或崩溃。根据性能需求和使用场景的不同,可以选择以下两种主要集成路径:

  • 托管调用:适合业务逻辑复杂、需要在C#和C++之间频繁交换数据的情况,开发效率较高,但增加了打包体积。
  • 非托管调用:适合仅需调用底层高性能函数(如图形处理、硬件驱动),无需传递复杂对象,系统开销更小。

第1步:采用托管方式进行调用(C++/CLI)

此方法的核心是创建一个C++/CLI类库,充当C#与底层非托管C++代码之间的桥梁。编译器会自动将CLR类型转换为C++代码可识别的形式。

1.1 创建并配置C++/CLI项目

在Visual Studio中创建一个新的“C++/CLI”空项目(选择CLR支持)。首先定义头文件Function.h,使用ref class关键字声明一个受管类:

#pragma once
#include <string>

public ref class Function
{
public:
    Function(void);
    ~Function(void);
    int member;
    // 简单的数值相加方法
    int MemberFuncAdd(int a, int b);
    // 字符串处理方法
    System::String^ Say(System::String^ str);
};

接着在对应的.cpp文件中实现这些方法:

#include "Function.h"

Function::Function(void)
{
}
Function::~Function(void)
{
}

int Function::MemberFuncAdd(int a, int b)
{
   return a + b;
}
System::String^ Function::Say(System::String^ str)
{
   return str;
}

关键配置提醒:进入项目属性页 → C/C++ → 常规 → 公共语言运行时支持 (Common Language Runtime Support),必须设置为/clr,否则无法生成托管程序集。

1.2 在C#项目中引用

在C#解决方案中添加对上面生成的C++/CLI项目的引用,或者在“引用”对话框中添加项目引用。随后即可像普通C#类一样实例化:

使用示例代码如下:

// 在C#代码中直接调用
Function fun = new Function();
int result = fun.MemberFuncAdd(1, 2);
string text = fun.Say("Hello World");

1.3 运行环境与平台设置

由于C++/CLI生成的DLL依赖于.NET CLR环境,必须确保宿主进程的位宽与编译时设置一致。强烈建议将C#主项目和C++/CLI项目都设置为x86或x64,切忌混用x86与x64,否则会导致TypeLoadException。

正确配置后,程序可正常运行:

第2步:采用非托管方式进行调用(P/Invoke)

如果不希望引入CLR开销,可以直接让C#通过操作系统API读取原生C++导出的符号。这要求C++ DLL导出的是标准C接口,避免C++名称修饰(Name Mangling)造成的寻址问题。

2.1 配置原生C++ DLL

新建一个普通的Win32 Console Application项目(选择DLL类型),用于生成原生二进制文件。

在stdafx.h(或预处理头文件)中定义导出宏:

// stdafx.h
#pragma once
#include "targetver.h"

#ifdef A_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

// 强制C语言链接约定
extern "C" DLL_API void MessageBoxShow(); 

在dllmain.cpp中实现该函数:

#include "stdafx.h"

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

void MessageBoxShow()
{
    MessageBox(NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);
}

注意:虽然此处我们讨论的是非托管,但为了保持上下文一致性并确保项目在包含部分CLR组件时不冲突,部分旧版教程会提及启用运行时,但在纯非托管方案中实际上不需要勾选“公共语言运行时支持”。此处若原图有特定设置提示,请以实际构建需求为准,但标准原生DLL构建无需此选项。

2.2 在C#中声明P/Invoke入口

在C#项目中,使用System.Runtime.InteropServices.DllImport特性来映射非托管函数。假设生成的文件名为Native.dll:

using System.Runtime.InteropServices;

public class NativeHelper
{
    // 指定DLL文件名和调用约定
    [DllImport("Native.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void MessageBoxShow();
}

平台匹配警告:与托管方式类似,非托管方式的稳定性高度依赖进程架构。必须在“生成”选项中将平台工具集和目标架构统一设置为x86(针对32位DLL)或x64。

执行调用即可看到原生弹窗:

总结与常见问题排错

  • 找不到模块:检查DLL是否已复制到C#输出的根目录(Bin/Debug)。如果是非托管方式,且使用了相对路径,需确保工作目录正确。
  • 架构不匹配:如果报错“试图加载格式不正确的程序”或访问违规,绝大多数情况是因为C++ DLL是64位的,而C#应用强行以32位(Any CPU → x86)运行导致的。
  • 中文乱码:C++默认GBK编码,C#默认UTF-16,若传输中文参数需特别注意编码转换,或使用WideChar API。
以上就是C#调用C++ DLL的两种核心实现方法的详细内容,更多关于C#与C++混合编程的资料请关注本站其它相关文章!
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表