C#调用C++ DLL中返回接口类对象指针的函数

主要有2种方法,非托管和托管方式,2种都需要具备一定C++及DLL的基础:

1.通过一个间接层DLL来封装接口对象的方法调用

先来创建一个dll项目,用来生成一个给C#调用的dll:

 

 

 

 

 

项目结构如下:(部分文件是自行添加的如模块定义文件def)

 

 

 各个文件的内容如下:

// CppLibDll.h是接口定义头文件
#pragma  once
// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 CPPLIBDLL_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// CPPLIBDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef CPPLIBDLL_EXPORTS
#define CPPLIBDLL_API __declspec(dllexport)
#else
#define CPPLIBDLL_API __declspec(dllimport)
#endif

#include <string>

// 注释掉VS自动生成的示例代码
#if 0
// 此类是从 CppLibDll.dll 导出的
class CPPLIBDLL_API CCppLibDll {
public:
    CCppLibDll(void);
    // TODO:  在此添加您的方法。
};

extern CPPLIBDLL_API int nCppLibDll;

CPPLIBDLL_API int fnCppLibDll(void);
#endif

// 导出的接口类
class IExport
{
public:
    // 返回值:成功:0,失败:非0,失败信息保存在D:/Log.txt
    virtual int OnInit(std::string strSaveFilePath) = 0;

    // 返回值:成功:0,失败:非0,失败信息保存在D:/Log.txt
    virtual int OnTest() = 0;

    virtual ~IExport() {}
};

// 假设这是原来的DLL暴露的接口函数
// 这种返回接口类对象的指针的导出函数,对于C++来说没有什么问题,但是对于C#没办法直接用对象指针调用接口方法
extern "C" CPPLIBDLL_API IExport* __stdcall ExportObjectFactory();
extern "C" CPPLIBDLL_API void __stdcall DestroyExportObject(IExport* obj);



// 通过建立一个接口层,帮C#完成间接调用接口方法
// 这2个方法可以单独做成一个间接层dll(此处只是为了方便,一般情况也只能自己另外写一个dll,因为你不能修改别人的dll源码)
// 下面strSaveFilePath变量类型不要用string,C#中的string类型和C++的string不匹配
extern "C" CPPLIBDLL_API int __stdcall CallOnInit(IExport* obj, const char* strSaveFilePath); extern "C" CPPLIBDLL_API int __stdcall CallOnTest(IExport* obj);
// CppLibDll.cpp是接口实现头文件
// CppLibDll.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
#include "CppLibDll.h"
#include "ExportImpl.h" // 实现了接口类的具体子类

#if 0
// 这是导出变量的一个示例
CPPLIBDLL_API int nCppLibDll=0;

// 这是导出函数的一个示例。
CPPLIBDLL_API int fnCppLibDll(void)
{
    return 42;
}

// 这是已导出类的构造函数。
// 有关类定义的信息,请参阅 CppLibDll.h
CCppLibDll::CCppLibDll()
{
    return;
}
#endif


extern "C" CPPLIBDLL_API IExport* __stdcall ExportObjectFactory()
{
    return new ExportImpl();
}

extern "C" CPPLIBDLL_API void __stdcall DestroyExportObject(IExport* obj)
{
    if (obj)
    {
        delete obj;
        obj = nullptr;
    }
}

extern "C" CPPLIBDLL_API int __stdcall CallOnInit(IExport* obj, const char* strSaveFilePath)
{
    if (obj) {
        return obj->OnInit(strSaveFilePath);
    }
    else {
        return -1;
    }
}

extern "C" CPPLIBDLL_API int __stdcall CallOnTest(IExport* obj)
{
    if (obj) {
        return obj->OnTest();
    }
    else {
        return -1;
    }
}

Source.def是模块定义文件,用于导出dll接口函数名,并保证其不被重命名:

LIBRARY "CppLibDll"
EXPORTS
ExportObjectFactory @ 1
DestroyExportObject @ 2
CallOnInit @ 3
CallOnTest @ 4

以下2个文件是实现了接口的一个具体派生类:

// ExportImpl.h
#pragma once
#include "CppLibDll.h"


// 实现接口
class ExportImpl : public IExport
{
public:
    ExportImpl();
    ~ExportImpl();

    virtual int OnInit(std::string strSaveFilePath) override;
    virtual int OnTest() override;

    enum InfoType {
        InitError, InitInfo, TestError, TestInfo
    };

private:
    std::string m_strFilePath;

    void Log(InfoType info, std::string infoMessage);
};
// ExportImpl.cpp
#include "stdafx.h"
#include "ExportImpl.h"
#include <fstream>
#include <ctime>

const std::string logpath = "D:/Log.txt";

ExportImpl::ExportImpl()
{
    m_strFilePath = "";
}


ExportImpl::~ExportImpl()
{
    // 如有资源需要释放
}

int ExportImpl::OnInit(std::string strSaveFilePath)
{
    if (strSaveFilePath == "") {
        Log(InfoType::InitError, "The given save file path is empty!");
        return -1;
    }
    m_strFilePath = strSaveFilePath;
    Log(InfoType::InitInfo, "Init Ok!");

    return 0;
}

int ExportImpl::OnTest()
{
    if (m_strFilePath == "")
    {
        Log(InfoType::TestError, "The save file path is empty!");
        return -1;
    }

    std::ofstream outFile(m_strFilePath, std::ios::app);
    if (!outFile) {
        Log(InfoType::TestError, "Open save file failed!");
        return -2;
    }

    Log(InfoType::TestInfo, "Start test!");
    Log(InfoType::TestInfo, "Testing...");
    Log(InfoType::TestInfo, "Testing Over!");
    Log(InfoType::TestInfo, "Result: Pass");

    return 0;
}

void ExportImpl::Log(InfoType info, std::string infoMessage)
{
    // 获取当前时间
    std::time_t rawtime;
    char buffer[64];
    std::time(&rawtime); // 获取系统时间
    std::tm *localTm = localtime(&rawtime); // 生成本地时间
    std::strftime(buffer, 64, "%Y-%m-%d %H:%M:%S", localTm);

    std::string strFilePath = logpath;
    std::string strInfo = "";
    switch (info)
    {
    case InfoType::InitError:
        strInfo = "Init Error";
        break;
    case InfoType::InitInfo:
        strInfo = "Init Info";
        strFilePath = m_strFilePath;
        break;
    case InfoType::TestError:
        strInfo = "Test Error";
        break;
    case InfoType::TestInfo:
        strInfo = "Test Info";
        strFilePath = m_strFilePath;
        break;
    default:
        strInfo = "Undefine";
        break;
    }

    std::ofstream of(strFilePath, std::ios::app);
    if (of) {
        of << "[" << strInfo << "]" << buffer << " :" << infoMessage << std::endl;
    }
    of.close();
}

编译生成后,先用一个C++的控制台项目测试以下这个dll是否有问题:

// LibTestByCpp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "lib/CppLibDll.h"
#pragma comment(lib, "lib/CppLibDll.lib") // 隐式调用

int main()
{
// C++很简单,直接通过工厂方法生成接口对象,然后调用接口中定义的虚方法即可 IExport
*p = ExportObjectFactory(); p->OnInit("D:/TestInfo.txt"); p->OnTest(); DestroyExportObject(p); system("pause"); return 0; }

 

 

 下面是C#封装并调用这个dll的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Disposable
{
    class Program
    {
        static void Main(string[] args)
        {
            // CUseCppInterfaceObject obj = new CUseCppInterfaceObject();
            // obj.OnInit(@"D:\TestInfo.txt");
            // obj.OnTest();
            // obj.Dispose();
        

        using (CUseCppInterfaceObject obj = new CUseCppInterfaceObject())
        {
          obj.OnInit(@"D:\TestInfo.txt");
          obj.OnTest();
          //obj.Dispose();
        }

            Console.ReadLine();
        }
    }

    public class CUseCppInterfaceObject : IDisposable
    {
        #region PInvokes
        // DLL内部函数
        [DllImport("CppLibDll.dll")]
        static private extern IntPtr ExportObjectFactory();

        [DllImport("CppLibDll.dll")]
        static private extern void DestroyExportObject(IntPtr pObj);

        [DllImport("CppLibDll.dll")]
        static private extern int CallOnInit(IntPtr pObj, string strSaveFilePath);

        [DllImport("CppLibDll.dll")]
        static private extern int CallOnTest(IntPtr pObj);
        #endregion PInvokes

        #region Members
        private IntPtr m_pNativeObject; // 保存创建的C++接口对象的指针
        #endregion Members

        public CUseCppInterfaceObject()
        {
            // 通过dll导出接口创建C++接口对象实例
            this.m_pNativeObject = ExportObjectFactory();
        }

        // Finalizer is called when Garbage collection occurs, but only if
        // the IDisposable.Dispose method wasn't already called.
        ~CUseCppInterfaceObject()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool bDisposing)
        {
            if (this.m_pNativeObject != IntPtr.Zero)
            {
                // 非空指针,调用dll的接口销毁创建的接口对象
                DestroyExportObject(this.m_pNativeObject);
                this.m_pNativeObject = IntPtr.Zero;
            }

            if (bDisposing)
            {
                // 已经清理非托管内存,无需再调用终结器
                GC.SuppressFinalize(this);
            }
        }

        #region Wrapper
        public int OnInit(string strSaveFilePath)
        {
            return CallOnInit(this.m_pNativeObject, strSaveFilePath);
        }

        public int OnTest()
        {
            return CallOnTest(m_pNativeObject);
        }
        #endregion Wrapper
    }
}

编译运行这个控制台程序,最终结果如下,成功调用了dll:

 

 

 

参考:

https://www.codeproject.com/Articles/18032/How-to-Marshal-a-C-Class

https://stackoverflow.com/questions/9211128/p-invoke-how-to-call-unmanaged-method-with-marshalling-from-c

posted @ 2021-01-28 16:25  碎银三二两  阅读(3576)  评论(0编辑  收藏  举报