用C#掉用C++的dll直接import就可以之前有不同的类型对应,当要传递结构体的时候就有点麻烦了,这里有一个结构体里边有char*类型,这个类型在C#中调用没法声明,传string是不行的默认string是对应const char*,传stringbuilder得指定大小,不然是没法传的,
查了好久,最后只能用unsafe代码来实现了
用C/C++写一个标准的动态链接库:
头文件,定义了三个接口函数,
#pragma once
#define TESTDLL _declspec(dllexport)
#ifndef PKI_DATA_ST
#define PKI_DATA_ST
typedef struct PKI_DATA_st {
int size;
char *value;
}PKI_DATA, *PKI_PDATA;
#endif
extern "C"
{
TESTDLL double Add(double a, double b);
TESTDLL double Subtract(double a, double b);
TESTDLL int TestDn(PKI_DATA cert, PKI_DATA* dn);
}
简单实现:
#include "stdafx.h"
#include "TestClass.h"
double Add(double a, double b)
{
printf("Add \n");
return a + b;
}
double Subtract(double a, double b)
{
printf("Subtract");
return a - b;
}
int TestDn(PKI_DATA cert, PKI_DATA* dn)
{
printf("TestDn cert.value: %s\n", cert.value);
dn->size = 10;
//dn->value = "helloworld"; //这个种写法需要转换
dn->value =(char*) TEXT("helloworld");//这种写法在选择unicode字符集时C#是可以直接接受的
return 1;
}
C#调用,路径可以用当前程序的相对路径,也可以是系统的环境变量中的路径,声明import
[DllImport("USBLoad.dll", EntryPoint = "Add", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
static extern double Add(double a,double b);
[DllImport("USBLoad.dll", EntryPoint = "TestDn", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
static extern int TestDn(PKI_DATA cert, ref PKI_DATA dn);
[DllImport("USBLoad.dll", EntryPoint = "Subtract", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
static extern double Subtract(double a, double b);
声明结构体:
internal unsafe struct PKI_DATA
{
internal int size;
internal char* value;
}
调用:记得属性中勾上允许不安全代码
[HandleProcessCorruptedStateExceptions]//这个可以捕获C++中的异常
[SecurityCritical]
static unsafe void TestUSBLoad()//加unsafe
{
try
{
var a = Add(10, 10);
w(a);
a = Subtract(15, 10);
w(a);
PKI_DATA cert = new PKI_DATA();
cert.size = 4;
char[] test = "test".ToCharArray();
string s = "";
fixed (char * v=test)//传值初始化。需要fixed关键字
{
cert.value = v;
PKI_DATA dn = new PKI_DATA();
TestDn(cert, ref dn);
var encode = Encoding.ASCII;
var c = dn.value;
List<char> list = new List<char>();
for (int i=0;i<dn.size;++i,++c)
{
list.Add(*c);
}
//兼容ansi编码,先转byte
var bytes = Encoding.Unicode.GetBytes(list.ToArray());
s = Encoding.ASCII.GetString(bytes);//转码
w(new string(dn.value));//设置unicode字符集的情况下可以直接这么写就能得到要字符
}
s = s.Replace("\0","");//转码的时候后边会有很多\0,移除
w(string.Format("[{0}]",s));
}
catch (Exception ex)
{
w(ex.Message);
}
}
顺便研究了下C++的动态调用、
如果是静态调用需要头文件和lib库,直接引用头文件,加入lib的链接路径,引用后可以直接用,编译就可以了
动态调用用loadlibrary,记得freelibrary,这些函数在windows.h中,这里建的是QT的项目所以引用的是qt_windows.h:
#include "qt_windows.h"
#define DLLPATH (LPCTSTR)"USBLoad.dll"
#ifndef PKI_DATA_ST
#define PKI_DATA_ST
typedef struct PKI_DATA_st {
int size;
char *value;
}PKI_DATA, *PKI_PDATA;
#endif
//声明入口函数
typedef double(*Add_Func)(double, double);
typedef int(*TestDn)(PKI_DATA, PKI_DATA*);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "hello world!";
HINSTANCE m_hModule = LoadLibrary(DLLPATH);//加载dll
if (m_hModule == NULL)
{
printf("LoadLibrary %s failed,err %d\n", DLLPATH, GetLastError());
goto END;
}
Add_Func add;
add = (Add_Func)GetProcAddress(m_hModule,"Add");//获取方法地址,后边调用
double c = add(10, 11);
printf("Add_Func: %lf \n", c);
TestDn test;
test = (TestDn)GetProcAddress(m_hModule, "TestDn");
PKI_DATA cert;
PKI_DATA dn;
cert.size = 4;
cert.value = (char*)"test";
int res = test(cert, &dn);
printf("TestDn:%s\n", dn.value);
END:
if (m_hModule != NULL)
{
FreeLibrary(m_hModule);
m_hModule = NULL;
printf("FreeLibrary\n");
}
return a.exec();
}
关于怎么把char*转码到正确的编码格式下的C#string,
char* cchar=data
IntPtr value = new IntPtr(cchar);
var s = Marshal.PtrToStringAnsi(value);
可以用上边的方法,看fcl的源码可以看到上边的方法实质是把char*强转为sbyte*,然后直接new string,这样可以避免C++和C#的byte差异
marshal还有touni的方法,只转unicode的方法,实质是直接用char* new string,
char*和sbyte*两种初始化参数string类型都接受。
来源:oschina
链接:https://my.oschina.net/u/4381733/blog/4055439