2021-03-06

该博客详细介绍了如何在C#中调用C++动态链接库(DLL)中返回接口对象指针的函数。通过创建间接层DLL,实现了C#对C++接口方法的调用。内容包括DLL接口定义、C++接口实现、C#的PInvoke调用以及测试示例。
摘要由CSDN通过智能技术生成

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;
}

在这里插入图片描述

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.cnblogs.com/djh5520/p/14340517.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值