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