• 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

  • 相关阅读:
    NET中的类型和装箱/拆箱原理
    转 C# 装箱和拆箱[整理]
    理解线程同步
    IsBackground的理解
    赛马会面试题
    FTP上传类
    FTPS加密上传
    转载WPF SDK研究 之 AppModel
    SQL Server查看错误日志存档编号及其详情
    Hive基础编程入门(一)
  • 原文地址:https://www.cnblogs.com/djh5520/p/14340517.html
Copyright © 2020-2023  润新知