目录

使用 C++/CLI 连接 C++ 与 C#

在软件开发中,我们经常需要结合 C# 的开发效率和 C++ 的运行性能。C++/CLI 是微软提供的一种技术,充当两者之间的桥梁(Wrapper)。

架构概览

整个解决方案通常包含三个部分(三个项目):

  1. Native C++ (后端核心): 纯 C++ 代码,负责核心算法或底层操作。不依赖 .NET。
  2. C++/CLI (中间层/包装器): 编译为 DLL。内部调用 Native C++,外部向 C# 暴露 .NET 类。
  3. C# (前端应用): 引用 C++/CLI 生成的 DLL,像调用普通 C# 库一样使用。

步骤 1: 创建 Native C++ 核心库

首先,我们需要一个标准的 C++ 类。假设我们创建一个简单的数学库。

Core.h

#pragma once
#include <string>
 
namespace NativeCore {
    class Calculator {
    public:
        Calculator();
        ~Calculator();
 
        double Add(double a, double b);
        std::string SayHello(const std::string& name);
    };
}

Core.cpp

#include "Core.h"
#include <iostream>
 
namespace NativeCore {
    Calculator::Calculator() {}
    Calculator::~Calculator() {}
 
    double Calculator::Add(double a, double b) {
        return a + b;
    }
 
    std::string Calculator::SayHello(const std::string& name) {
        return "Hello " + name + " from Native C++!";
    }
}

步骤 2: 创建 C++/CLI 包装器 (Wrapper)

这是最关键的一步。你需要创建一个 C++ 动态链接库 (DLL) 项目,并开启 CLR 支持。

项目配置要点:

CliWrapper.h

#pragma once
#include "Core.h" // 包含 Native C++ 头文件
#include <msclr/marshal_cppstd.h> // 用于字符串转换
 
using namespace System;
 
namespace CliLibrary {
    // 使用 'public ref class' 定义一个托管类,这样 C# 才能看到它
    public ref class ManagedCalculator {
    private:
        // 指向 Native C++ 对象的指针
        NativeCore::Calculator* m_impl;
 
    public:
        // 构造函数:分配 Native 资源
        ManagedCalculator() {
            m_impl = new NativeCore::Calculator();
        }
 
        // 析构函数 (!ClassName):用于确定性销毁 (Dispose)
        !ManagedCalculator() {
            if (m_impl != nullptr) {
                delete m_impl;
                m_impl = nullptr;
            }
        }
 
        // 终结器 (~ClassName):用于垃圾回收 (Finalize)
        ~ManagedCalculator() {
            this->!ManagedCalculator();
        }
 
        // 包装 Add 方法
        double Add(double a, double b) {
            return m_impl->Add(a, b);
        }
 
        // 包装字符串方法 (涉及数据封送/Marshaling)
        String^ SayHello(String^ name) {
            // 1. 将 C# String (System::String) 转换为 C++ std::string
            msclr::interop::marshal_context context;
            std::string nativeName = context.marshal_as<std::string>(name);
 
            // 2. 调用 Native 方法
            std::string nativeResult = m_impl->SayHello(nativeName);
 
            // 3. 将 C++ std::string 转换回 C# String
            return context.marshal_as<String^>(nativeResult);
        }
    };
}

CliWrapper.cpp

#include "CliWrapper.h"
 
// 如果逻辑很简单,代码可以直接写在 .h 文件中。
// 复杂的实现建议放在这里。

步骤 3: C# 调用

在 C# 项目中:

  1. 右键 依赖项 (Dependencies)添加项目引用 (Add Project Reference)
  2. 勾选上面的 C++/CLI 项目

Program.cs

using System;
using CliLibrary; // 引用 C++/CLI 定义的命名空间
 
namespace CSharpApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("正在初始化 C++ 包装器...");
 
            // 使用 using 语句确保非托管资源被及时释放 (调用 C++/CLI 的析构函数)
            using (ManagedCalculator calc = new ManagedCalculator())
            {
                // 1. 测试数值计算
                double result = calc.Add(10.5, 20.3);
                Console.WriteLine($"C++ Add Result: {result}");
 
                // 2. 测试字符串传递
                String msg = calc.SayHello("C# User");
                Console.WriteLine($"C++ Message: {msg}");
            }
 
            Console.WriteLine("完成。");
            Console.ReadKey();
        }
    }
}

关键概念解析

1. 内存管理

在 C++/CLI 中,我们混合了两种内存模型:

最佳实践: 在 C++/CLI 类中持有一个指向 Native 对象的指针。在构造函数中 `new`,在析构函数中 `delete`。

2. 数据封送 (Marshaling)

C# 的数据类型和 C++ 不完全通用,需要转换:

3. 编译模式 (/clr)

开启 `/clr` 后,编译器会生成 MSIL (微软中间语言) 和 Native Code 的混合体。这使得生成的 DLL 既是一个标准的 .NET 程序集,又能直接执行机器码。

常见问题排查