c++编写的程序为非托管代码,c#写的为托管代码。CLR提供了一些机制,允许应用程序同时包含托管与非托管代码。具体可以分为以下3种:
托管代码能调用DLL中的非托管函数。通过P/Invoke(Platform Invoke)机制调用DLL中的函数,如Kernel32.dll等。
托管代码可以使用现有COM组件(服务器)。许多公司都已经实现了大量非托管COM组件。利用来自这些组件的类型库,可创建一个托管程序集来描述COM组件。托管代码可像访问其他任何类型一样访问托管程序集中的类型。
非托管代码可以使用托管类型(服务器)。许多现有的非托管代码要求提供COM组件来确保代码正确工作。使用托管代码可以更简单地实现这些组件,避免所有代码都不得不和引用计数和接口打交道。比如C++调用C#开发的dll。
对于c++/cli,网上比较常见的是用它来包装非托管代码来供托管代码调用。但目前遇到一个需要在c++中调用c#写的dll的问题,需要用到c++/cli作为桥梁,在此做个记录。
具体的代码没有必要贴出来,以一个函数声明为例子。这个函数将在c++/cli中被包装为c可以调用的函数。而在c++/cli中的包装,最主要的就是托管数据类型和非托管数据类型的一个转换。
public T caget<T>(string pvname, int setGateWay, string epicsCaAddrList)
我们可以看一下这个函数。它的参数有system::string,和int基本类型。返回是一个泛型。int基本类型不需要转换。system::string需要转换成非托管。对于c++调用的dll能不能有泛型接口,我其实不确定。但感觉泛型是在调用时确定类型,产生的执行代码是有明确类型的。而动态链接库它是可以复用的可执行代码,对应的类型应该明确。我的判断是导出函数不能有泛型接口,而且经过网上搜索也没有找到可以的例子。(如果有找到可以使用的方法,欢迎补充)。因而T的转换我采用的是switch case,因为此处T的类型根据需求已经确定只有有限的几种,所以可以采用这种一一匹配的方式。
可以在c#dll同一解决方案下创建clr class library项目(依次选择 visual c++ → clr → clr class library)。然后对clr项目添加dll的引用。 然后进行对c#dll的封装。具体的语法可以在网上查找教程。在这里主要记录一下类型转换,也是我觉得c++/cli封装最主要的部分。
extern "C" __declspec(dllexport) int caget(const char* pvname, void* pdata, DATA_TYPE dataType, int setGateWay, const char* epicsCaAddrList)
这是我对caget函数的封装声明。system::string我转换成了char*(原本想转换为std::string,后来调用时发现会报错)。泛型类型的返回被改成了通过改变void*的值来返回。 这样函数参数就已经转换成了非托管代码。在这个封装函数内部我们需要调用c#对应的caget。所以这里需要一个步骤,即将非托管转换成托管来作为caget的参数。
String^ strPvname = gcnew String(pvname);
上述代码就是把char转换成了system::string。 而将system::string转换成char可以看这个:
using namespace System::Runtime::InteropServices;
char* ManagedString2UnmanagedStringA(String^ strIn)
{
IntPtr ip = Marshal::StringToHGlobalAnsi(strIn);
const char* pTemp = static_cast<const char*>(ip.ToPointer());
char *pOut = new char[strlen(pTemp)+1];
strcpy_s(pOut, strlen(pTemp)+1, pTemp);
Marshal::FreeHGlobal(ip);
return pOut;
}
我的代码里还涉及到了如何将int[]转换成int*。
cli::array<int>^ managedIntListResult = generator->caget<cli::array<int>^>(strPvname, setGateWay, strEpicsCaAddrList);
cli::pin_ptr<int> pIntArrayElement = &managedIntListResult[0];
pIntArrayElement就可以当普通的指针用。这个转换同样适用于各种数组转换成指针。
将c++/cli工程中编译生成的debug文件夹里的文件拷贝到调用工程的exe同目录下。也可以在dll工程中直接把输出路径设置成调用工程的debug目录,方便调试。具体代码如下:
#include "pch.h"
#include <iostream>
#include "tchar.h"
#include <string>
#include <windows.h>
using namespace std;
typedef enum
{
TYPE_STRING,
TYPE_INT_LIST,
TYPE_DOUBLE_LIST,
TYPE_FLOAT_LIST,
TYPE_SHORT_LIST,
TYPE_BYTE_LIST
}DATA_TYPE;
// 设置函数指针,对应caget和caput
//char* caget(char* pvname, int setGateWay, char* epicsCaAddrList)
//void caput(char* pvname, char* newvalue, int setGateWay, char* epicsCaAddrList)
typedef void(*pCAget)(string, void*, DATA_TYPE,int,string);
//typedef void(*pCAput)(char*, char*, int, char*);
void main()
{
//获取dll
HMODULE hModule = LoadLibrary(_T("catoolwrap.dll"));
if (hModule)
{
pCAget caget = (pCAget)GetProcAddress(hModule, "caget");
DWORD err = GetLastError();
// pCAput caput = (pCAput)GetProcAddress(hModule, "caput");
//err = GetLastError();
if (caget)
{
string pvname = "10293:aiExample";
//char value[50] = "3";
char result[1000] = {};
//caput(pvname, value, 0, NULL);
caget(pvname, result, TYPE_STRING,0,NULL);
}
else
{
::MessageBoxA(NULL, "Get function fail.", "Fail", MB_OK);
}
getchar();
//free library
FreeLibrary(hModule);
hModule = nullptr;
}
else
{
::MessageBoxA(NULL, "Load dll fail.", "Fail", MB_OK);
}
}
本文章使用limfx的vsocde插件快速发布