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类型都接受。

 

posted @ 2018-03-08 13:11  季末的寂寞  阅读(3670)  评论(0编辑  收藏  举报