云计算百科
云计算领域专业知识百科平台

C#与C++互操作示例

C# 与 C++ 互操作深度解析:四种关键技术实践

C# 和 C++ 的协同开发是现代软件工程中的常见需求——通过互操作技术,我们既能保留 C++ 的高性能与硬件级控制能力,又能享受 C# 的开发效率和强大的生态支持。下面介绍四种核心互操作技术及其代码实现:


​​一、平台调用 (P/Invoke) – 调用 C++ 原生函数​​

​​适用场景​​:直接调用现有 DLL 中的 C++ 函数

// C# 调用方
using System;
using System.Runtime.InteropServices;

class InteropDemo
{
// 声明 C++ 函数(约定使用 cdecl 调用规范)
[DllImport("NativeLib.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int AddNumbers(int a, int b);

[DllImport("NativeLib.dll", CharSet = CharSet.Ansi)]
private static extern void PrintMessage(string text);

public static void Main()
{
int sum = AddNumbers(15, 27); // 调用 C++ 加法函数
Console.WriteLine($"Sum from C++: {sum}");

PrintMessage("Hello from C#!"); // 传递字符串到 C++
}
}

对应的 C++ 实现 (NativeLib.cpp):

#include <iostream>

extern "C" __declspec(dllexport)
int AddNumbers(int a, int b) {
return a + b;
}

extern "C" __declspec(dllexport)
void PrintMessage(const char* text) {
std::cout << "C++ Received: " << text << std::endl;
}

关键技术点​​:

  • extern "C" 禁用 C++ 名称修饰(Name Mangling)
  • __declspec(dllexport) 显式导出函数
  • 数据类型映射(如 string→const char*)
  • 内存规则:C# 自动封送字符串内存

  • ​​二、C++/CLI 桥接 – 托管与非托管的双向交互​​

    ​​适用场景​​:需要在两种代码间进行复杂对象交互

    // C++/CLI 包装层 (.NET 程序集)
    #pragma once

    #include "NativeCalculator.h" // 原生 C++ 类

    public ref class ManagedCalculator
    {
    public:
    ManagedCalculator() : nativePtr(new NativeCalculator()) {}
    ~ManagedCalculator() { delete nativePtr; }

    double Calculate(double x)
    {
    // 托管代码调用非托管代码
    return nativePtr->Compute(x);
    }

    void SetCallback(Action<double>^ callback)
    {
    // 非托管代码回调托管委托
    nativePtr->SetCallback(
    gcnew NativeCallback([callback](double result) {
    callback(result);
    })
    );
    }

    private:
    NativeCalculator* nativePtr;

    // 非托管类到委托的桥接
    delegate void NativeCallback(double result);
    NativeCallback^ managedDelegate;
    };

    原生 C++ 类 (NativeCalculator.h):

    // 原生 C++ 类
    class NativeCalculator {
    public:
    using Callback = void(*)(double);

    void SetCallback(Callback cb) { callback = cb; }

    double Compute(double x) {
    double result = x * 2.5;
    if(callback) callback(result); // 执行回调
    return result;
    }

    private:
    Callback callback = nullptr;
    };

    C# 调用代码:

    var calc = new ManagedCalculator();
    calc.SetCallback(result =>
    Console.WriteLine($"C++ callback: {result}"));

    double output = calc.Calculate(4.2);
    Console.WriteLine($"Result: {output}");

    /* 输出:
    C++ callback: 10.5
    Result: 10.5
    */

    核心技术优势​​:

  • 无缝管理对象生命周期(托管析构函数释放非托管内存)
  • 类型安全转换(自动处理gcnew托管堆分配)
  • 支持复杂类型双向传递
  • 实现原生代码回调.NET委托

  • ​​三、COM 互操作 – 集成传统组件​​

    ​​适用场景​​:调用现有 COM 组件或暴露 .NET 类为 COM

    ​​调用 COM 组件:​

    // 引入 COM 类型库 (如 EXCEL.EXE)
    Type excelType = Type.GetTypeFromProgID("Excel.Application");
    dynamic excel = Activator.CreateInstance(excelType);

    excel.Visible = true;
    dynamic workbook = excel.Workbooks.Add();
    dynamic sheet = workbook.ActiveSheet;
    sheet.Cells[1, 1] = "Data from C#";

    Marshal.ReleaseComObject(sheet);
    Marshal.ReleaseComObject(workbook);
    excel.Quit();

    暴露 .NET 类给 COM:​

    [ComVisible(true)]
    [Guid("3AB1DF8C-71C1-4993-8CEF-FFDBFBB2A89E")]
    public interface ICalc
    {
    double ProcessData(double input);
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class DataProcessor : ICalc
    {
    public double ProcessData(double input) =>
    Math.Sqrt(input) * 0.87;
    }

    关键步骤​​:

  • 使用regasm /codebase注册 .NET 程序集
  • 生成类型库 (tlbexp 或 Visual Studio 嵌入设置)
  • COM 客户端通过 GUID 或 ProgID 创建实例

  • ​​四、内存映射文件 – 高性能数据交换​​

    ​​适用场景​​:进程间大数据交换(避免封送开销)

    ​​C++ 生产者:​

    #include <Windows.h>

    HANDLE hMapFile = CreateFileMapping(
    INVALID_HANDLE_VALUE,
    NULL,
    PAGE_READWRITE,
    0, 1024, // 1KB内存区
    L"Global\\\\SharedMemory");

    double* data = (double*)MapViewOfFile(
    hMapFile, FILE_MAP_WRITE, 0, 0, 1024);

    // 写入数据
    data[0] = 3.1415926;
    data[1] = 2.71828;

    C# 消费者:

    using var shm = MemoryMappedFile.OpenExisting(
    "SharedMemory", MemoryMappedFileRights.Read);
    using var accessor = shm.CreateViewAccessor(0, 1024);

    accessor.Read(0, out double pi);
    accessor.Read(8, out double e);

    Console.WriteLine($"PI={pi}, E={e}");

    性能关键​​:

  • 完全避免序列化/反序列化开销
  • 支持 GB 级数据交换(仅受虚拟内存限制)
  • 需要同步机制(Mutex/Semaphore)

  • ​​五、技术选型决策树​​

  • ​​简单函数调用​​ → P/Invoke
  • ​​复杂对象交互/双向调用​​ → C++/CLI
  • ​​继承现有 COM 系统​​ → COM Interop
  • ​​大数据/高频通信​​ → 内存映射 + 信号量
  • ​​跨平台需求​​ → P/Invoke (Unix/Linux 下使用libdl)

  • ​​关键注意事项​​

  • ​​类型系统陷阱​​:

    • C++ bool ≠ C# bool (前者是4字节,后者是1字节)
    • 使用[MarshalAs(UnmanagedType.I1)]明确指定布尔类型
  • ​​内存管理边界​​:

    // P/Invoke内存回收示例
    [DllImport("NativeLib.dll")]
    private static extern IntPtr CreateBuffer(int size);

    [DllImport("NativeLib.dll")]
    private static extern void FreeBuffer(IntPtr ptr);

  • ​​线程协同​​:

    • COM 需初始化单线程单元([STAThread])
    • C++/CLI 托管线程需附加到非托管环境
  • ​​异常处理​​:

    // C++ 端捕获托管异常
    try { /* 调用.NET方法 */ }
    catch (System::Exception^ ex) {
    std::cerr << MarshalString(ex->Message);
    }

  • 通过合理的架构设计和精准的技术选型,C# 与 C++ 的互操作将成为构建高性能混合系统的利剑,在保持开发效率的同时突破纯托管环境的性能限制。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » C#与C++互操作示例
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!