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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM