Loading

C++ 调用 C# - C++/CLI 方案

C++/CLI 方案 是通过托管 C++ 做中间层,来转发 C# 和 C++ 之间的调用和数据传递。

这个写法,C# 不用做任何特殊的处理,正常写就可以。

C++/CLI 层

新建一个 C++/CLI 项目,e.g. MedicalDbAccessWrapper,添加对 C# 项目的引用,
注意,输出目录最好就是原生 C++ 项目的输出目录,原生 C++ 项目可以直接调用。

托管 C++ 头文件,里面处理对 C# 的调用。

托管 C++ 中,引用类型后面会跟一个 ^,命名空间和静态方法的调用使用 ::

MedicalDbAccessWrapper.h

// MedicalDbAccessWrapper.h
#pragma once

// 里面有很多测试代码
using namespace System;
using namespace System::Collections::Generic;
using namespace MedicalDbAccess;
using namespace MedicalDbAccess::Models;
using namespace MedicalDbAccess::Wrapper;

namespace MedicalDbAccessWrapper {
    public ref class DbAccessWraper
    {
    public:
        String^ Combine(String^ str, int num)
        {
            return Class1::Run();
        }

        String^ Run()
        {
            return Class1::Run();
        }

        Patient^ GetPatient(String^ uid)
        {
            return PatientWrapper::GetPatient(uid);
        }

        IList<Patient^>^ GetPatients()
        {
            return PatientWrapper::GetPatients();
        }
    };
    }

MedicalDbAccessWrapper_Native.h

原生 C++ 头文件,用于处理 C++ 的导出

// MedicalDbAccessWrapper_Native.h
#pragma once

#ifdef MEDICALDBACCESSWRAPPER_EXPORTS
#define MEDICALDBACCESSWRAPPER_API __declspec(dllexport)
#else
#define MEDICALDBACCESSWRAPPER_API __declspec(dllimport)
#endif

#include <string>
#include <vector>

// CPP 端定义的业务数据结构,对应 C# 的数据定义
struct PatientDto
{
    std::string Uid;
    std::string Name;
    int Age;
};

MEDICALDBACCESSWRAPPER_API std::string RunAccess(std::string str, int value);
MEDICALDBACCESSWRAPPER_API PatientDto GetPatient(std::string uid);
MEDICALDBACCESSWRAPPER_API std::vector<PatientDto> GetPatients();

MedicalDbAccessWrapper.cpp

CPP 源文件,用于实现要导出的函数,里面完成中托管数据类型对原生 C++ 类型的转换

// MedicalDbAccessWrapper.cpp
#include "pch.h"

#define MEDICALDBACCESSWRAPPER_EXPORTS // 定义为导出逻辑,而不是导入

#include <msclr/marshal_cppstd.h> // 必须在自己的 Wrapper.h 之前定义

#include "MedicalDbAccessWrapper.h"
#include "MedicalDbAccessWrapper_Native.h"

PatientDto ConvertToNativePatient(Patient^ patient)
{
    PatientDto nativePatient;
    // nativePatient.Id = patient->Id;
    nativePatient.Uid = msclr::interop::marshal_as<std::string>(patient->Uid);
    nativePatient.Name = msclr::interop::marshal_as<std::string>(patient->Name);
    nativePatient.Age = patient->Age;
    return nativePatient;
}

std::vector<PatientDto> ConvertToNativePatientList(IList<Patient^>^ patientList)
{
    std::vector<PatientDto> nativePatients;

    // 遍历每个 Patient 并转换为 NativePatient
    for each (Patient ^ patient in patientList)
    {
        PatientDto nativePatient;
        // nativePatient.Id = patient->Id;
        nativePatient.Uid = msclr::interop::marshal_as<std::string>(patient->Uid);
        nativePatient.Name = msclr::interop::marshal_as<std::string>(patient->Name);
        nativePatient.Age = patient->Age;

        // 将转换后的对象添加到原生列表
        nativePatients.push_back(nativePatient);
    }

    return nativePatients;
}

std::string RunAccess(std::string str, int value)
{
    MedicalDbAccessWrapper::DbAccessWraper wrapper;
    String^ result = wrapper.Run();
    std::string stdstr = msclr::interop::marshal_as<std::string>(result);
    // return stdstr.c_str();
    return stdstr;
}

PatientDto GetPatient(std::string uid)
{
    MedicalDbAccessWrapper::DbAccessWraper wrapper;
    String^ uid2 = msclr::interop::marshal_as<String^>(uid);
    Patient^ patient = wrapper.GetPatient(uid2);
    return ConvertToNativePatient(patient);
}

std::vector<PatientDto> GetPatients()
{
    MedicalDbAccessWrapper::DbAccessWraper wrapper;
    auto list = wrapper.GetPatients();
    return ConvertToNativePatientList(list);
}

原生 C++ 端调用

原生 C++ 端,只要引用 MedicalDbAccessWrapper_Native.h 头文件,就可以直接调用里面导出的函数了。

为此,需要添加对 MedicalDbAccessWrapper 的引用,方式如下:

  • 项目->属性->配置属性->VC++ 目录-> 在 "包含目录" 里添加头文件 MedicalDbAccessWrapper_Native.h 所在的目录

  • 项目->属性->配置属性->VC++ 目录-> 在 "库目录" 里添加 MedicalDbAccessWrapper.lib 所在的目录

  • 项目->属性->配置属性->链接器->输入-> 在 "附加依赖项" 里添加 MedicalDbAccessWrapper.lib(若有多个 lib 则以空格隔开)

C语言调用C#函数 – Coding Life

调用就比较简单了,引入头文件之后,就可以直接调用了。

// VisitByCli.h
#pragma once
#include <windows.h>
#include <iostream>
#include "MedicalDbAccessWrapper_Native.h"

class VisitByCli
{
public:
    int run();
};
// VisitByCli.cpp
#include "VisitByCli.h"

int VisitByCli::run()
{
    std::cout << "Hello CLI!\n";
    auto result = RunAccess("", 1);
    auto patient = GetPatient("P_112827");
    auto patients = GetPatients();
    return EXIT_SUCCESS;
}

遗留问题

Wrapper 中间层和 C# 所有的 DLL,都要放在和原生 C++ 执行文件一个目录,dll 会显得非常混乱。
想把这堆 DLL,或者至少 C# 的所有 DLL,都一个放在一个子文件夹中,还没有找到方法。

当时,使用 LoadLibrary 动态加载的方式是可以的,但是这样就必须手动获取函数地址,然后还要定义函数签名。
不如直接引用 MedicalDbAccessWrapper_Native.h 头文件来得方便。

posted @ 2024-09-07 17:06  J.晒太阳的猫  阅读(33)  评论(0编辑  收藏  举报