C# 调用C/C++动态链接库,结构体中的char*类型
用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类型都接受。