通过C++/CLR封装的方式使非托管的C++、VB6.0调用.Net托管代码
通常.Net的dll只能被加载到对应的虚拟机中运行和调用,而无法直接被低版本的.Net或C++和VB6.0等非托管代码调用。但是实际项目开发过程中我们为了兼容,不得不同时支持这些非托管代码或低版本的运行时。实际上微软提供了多种方式可以实现这种需求,如进程间通讯、COM/ActiveX、C++/CLR。本文主要介绍通过C++/CLR包装方式,实现非托管代码访问托管C#.Net调用。简单调用顺序如下图所示。
目标C#.Net类库
假设我们有一个目标C#.Net类库,提供以下两个api:
public bool CreateConnection() public string GetData(string key)
创建C++/CLR包装类库
该类库是一个可以跟C#.Net dll互通的公共语言类库,跟C#.Net dll的区别在于他是用c++编写的,对外暴露c语言类api。非托管代码通过P/Invoke的方式实现函数调用。
- 在Visual Studio新建一个C++/CLR类库
- 引用目标C#.Net类库
项目右击->属性->公共属性->引用->添加NetApi.dll进入引用
- 编写api的包装类ApiWrapper
这个包装类用于封装对C#.Net dll的调用,本例中维护了目标api类的一个实例,只是简单实现了功能,实际项目中应注意需要更多的异常处理。
NetApiClr.h
// NetApiClr.h #pragma once using namespace System; namespace NetApiClr { public ref class ApiWrapper { public: ApiWrapper(); bool CreateConnection(); String ^ GetData(String ^); private: NetApi::DataHelper ^ dataHelper = nullptr; }; }
NetApiClr.cpp
// This is the main DLL file. #include "stdafx.h" #include "NetApiClr.h" NetApiClr::ApiWrapper::ApiWrapper() { dataHelper = gcnew NetApi::DataHelper(); } bool NetApiClr::ApiWrapper::CreateConnection(){ return dataHelper->CreateConnection(); } String ^ NetApiClr::ApiWrapper::GetData(String ^ key) { return dataHelper->GetData(key); }
- 编写api入口
这个入库对外暴露了c语言类的api,以便三方程序可以像调用win32接口那样通过P/Invoke的方式调用这些api。
#include "stdafx.h" #include "NetApiClr.h" #include <msclr/gcroot.h> using namespace System; using namespace std; using namespace Runtime::InteropServices; ref class ManagedGlobals abstract sealed { public: static NetApiClr::ApiWrapper ^ apiWrapper = gcnew NetApiClr::ApiWrapper(); }; extern "C" _declspec(dllexport) bool _stdcall CreateConnection() { return ManagedGlobals::apiWrapper->CreateConnection(); } extern "C" _declspec(dllexport) char* _stdcall GetData(char* cKey) { String^ key = gcnew String(cKey); String^ strResult = ManagedGlobals::apiWrapper->GetData(key); char* cResult = (char*)(Marshal::StringToHGlobalAnsi(strResult)).ToPointer(); return cResult; }
- 编写并配置DEF文件
模块定义或 DEF 文件 (*.def) 是一种文本文件,其中包含一个或多个描述 DLL 各种属性的模块语句。
NetApiClr.def
LIBRARY "NetApiClr" EXPORTS ; Explicit exports can go here CreateConnection @1 GetData @2
配置DEF文件
项目右击->属性->配置属性->连接器->输入->在模块定义文件选择中填写你编写的描述文件
C++ demo
- 定义api函数指针
typedef bool (WINAPI *pCreateConnection)(); typedef char* (WINAPI *pGetData)(char*);
- 加载C++/CLR dll和函数
//load dll HINSTANCE hInst=LoadLibrary(_T("NetApiClr.dll"));
//import api pCreateConnection FuncCreateConnection = nullptr; pGetData FuncGetData = nullptr; FuncCreateConnection = (pCreateConnection)GetProcAddress(hInst, "CreateConnection"); FuncGetData = (pGetData)GetProcAddress(hInst, "GetData");
- 调用函数
if (FuncCreateConnection()) { ::MessageBoxA(NULL, FuncGetData("abcd"), "Demo Code", MB_OK); } else { ::MessageBoxA(NULL, "ERROR", "Demo Code", MB_OK); }
- 释放dll句柄
//free dll FreeLibrary(hInst);
VB6.0 demo
- 加载C++/CLR dll并定义api
Public Declare Function CreateConnection Lib "NetApiClr.dll" () As Boolean Public Declare Function GetData Lib "NetApiClr.dll" (ByVal message As String) As String
- 调用api
If CreateConnection Then MsgBox GetData("abcd") Else MsgBox "Error" End If
参考:
- https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke
- https://learn.microsoft.com/en-us/cpp/dotnet/walkthrough-compiling-a-cpp-program-that-targets-the-clr-in-visual-studio?view=msvc-170
- https://learn.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=msvc-170
Keep it simple!