`
byytj
  • 浏览: 48552 次
  • 来自: ...
文章分类
社区版块
存档分类
最新评论

转:c#类型调用转换

 
阅读更多

最近在编写Warensoft3D游戏引擎,并预计明年年初发布测试版本,底层引擎使用DirectX和MONO来编写,上层的逻辑使用C#来编写,因此编写了大量C#与C++互调的代码,现在经验写出来与大家分享,并希望后来者少走弯路。

C#与C++交互,总体来说可以有两种方法:

  • 利用C++/CLI作为代理中间层
  • 利用PInvoke实现直接调用

第一种方法:实现起来比较简单直观,并且可以实现C#调用C++所写的类,但是问题是MONO构架不支持C++/CLI功能,因此无法实现脱离Microsoft .NET Framework跨平台运行。

第二种方法:简单的实现并不麻烦,只要添加DllImportAttribute特性即可以导入C/C++的函数,但是问题是PInvoke不能简单的实现对C++类的调用。在Warensoft3D中为了可以使用MONO实现跨平台(当然DirectX是不能跨平台的),所以使用了本方法,下面将对本方法展开详细的说明。

测试平台:

Windows7 64位,VS2010,.NET4.0

注意事项:

PInvoke从功能上来说,只支持函数调用,在被导出的函数前面一定要添加extern "C"来指明导出函数的时候使用C语言方式编译和连接,这样保证函数定义的名字和导出的名字相同,否则如果默认按C++方式导出,那个函数的名字就会变得乱七八糟,我们的程序就无法找到入口点了。

本文将说明以下几点:

  • 互调的基本原理
  • 基本数据类型的传递
  • 指针的传递
  • 函数指针的传递
  • 结构体的传递

 

 

  1. 互调的基本原理

    首先,我们来看一个再常规不过的概念—"数据类型"

    我们知道在大多数的静态语言中定义变量的时候都要先指定其数据类型,所谓数据类型,都是人们强加的一个便于记忆的名称,究其本质就是指明了这个数据在内存里到底是占用了几个字节,程序在运行的时候,首先找到这个数据的地址,然后再按着该类型的长度,读取相对应的内存,然后再处理。

    了解了前面这个事儿,所有编程语言之间进行互调就有点门道儿了。对于不同语言之间的互调,只要将该数据的指针(内存地址)传递给另一个语言,在另一个语言中根据通信协议将指针所指向的数据存储入长度对应的数据类型即可,当然要满足以下几点:

    1. 对于像Java,.NET这样有运行时虚拟机编程语言来讲,由于虚拟机会让堆内存来回转移,因此,在进行互调的时候,要保证正在被互调的数据所在的内存一定要固定,不能被转移。
    2. 有一些编程语言支持指针,有一些语言不支持指针(如Java),这个问题并不重要,所谓指针,其实就是一个内存地址,对于32位OS的指针是一个32位整数,而对于64位机OS的指针是一个64位整数。因为大多数语言中都有整型数,所以可以利用整型来接收指针。
  2. 基本数据类型的传递

互调过程中,最基本要传递的无非是数值和字符,即:int,long,float,char等等,但是此类型非彼类型,C/C++C#中有一些数据类型长度是不一样的,下表中列出常见数据类型的异同:

C/C++ 

C# 

长度

short 

short 

2Bytes

int

int 

4Bytes 

long(该类型在传递的时候常常会弄混)

int 

4Bytes 

bool 

bool 

1Byte 

char(Ascii码字符)

byte 

1Byte 

wchar_tUnicode字符,该类型与C#中的Char兼容)

char 

2Bytes 

float

float

4Bytes

double 

double 

8Bytes 

最容易弄混的是就是long,char两个类型,在C/C++longint都是4个字节,都对应着C#中的int类型,而C/C++中的char类型占一个字节,用来表示一个ASCII码字符,在C#中能够表示一个字节的是byte类型。与C#char类型对应的应该是C/C++中的wchar_t类型,对应的是一个2字节的Unicode字符。

下面通过实例来说明调用过程:

第一步:

建立一个C++Win32DLL,如下图所示:

这里要注意选择"Export symbols"导出符号。点击完成。

第二步:

由于项目的名称是"TestCPPDLL",因此,会自动生成TestCPPDLL.h和TestCPPDLL.cpp两个文件,.h文件是要导出内容的声明文件,为了能清楚的说明问题,我们将TestCPPDLL.h和TestCPPDLL.cpp两个文件中的所有内容都删除,然后在TestCPPDLL.h中添加如下内容:

第一行代码中定义了一个名为"TESTCPPDLL_API"的宏,该宏对应的内容是"__declspec(dllexport)"意思是将后面修饰的内容定义为DLL中要导出的内容。当然你也可以不使用这个宏,可以直接将"__declspec(dllexport)"写在要导出的函数前面。

第二行中的"EXTERN_C",是在"winnt.h"中定义的宏,在函数前面添加"EXTERN_C"等同于在函数前面添加extern "C",意思是该函数在编译和连接时使用C语言的方式,以保证函数名字不变。

第二行的代码是一个函数的声明,说明该函数可以被模块外部调用,其定义实现在TestCPPDLL.cpp中,TestCPPDLL.cpp的代码如下所示:

第三步:

在编译C++DLL之前,需要做以下配置,在项目属性对话框中选择"C/C++"|"Advanced",将Compile AS 选项的值改为"C++"。然后确定,并编译。

生成的DLL文件如下图所示:

第四步:

首先,添加一个C#的应用程序,如果要在C#中调用C++的DLL文件,先要在C#的类中添加一个静态方法,并且使用DllImportAttribute对该方法进行修饰,代码如下所示:

DllImport中的第一个参数是指明DLL文件的位置,第二个参数"EntryPoint"用来指明对应的C/C++中的函数名称是什么。"extern"关键字表明该处声明的这个Add方法是一个外部调用。

该方法声明完毕之后,就可以像调用一个普通的静态方法一样去使用了。

下面是示例程序:

class Program

{

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "Add")]

extern static int Add(int a, int b);

static void Main(string[] args)

{

int c = Add(1,2);

Console.WriteLine(c);

Console.Read();

}

}

在运行C#程序之前,先要修改C#的项目属性,如下图所示:

将platform target设置为x86,并且允许非安全代码(后面有用)。

然后运行该C#程序,其结果如下图所示:

第五步:

前面的Add方法中传递的是数值类型(int),其他的数据类型,如float,double,和bool类型的传递方式是一样的,下面演示如何传递字符串。

在TestCPPDLL.h中添加一个新的函数声明,代码如下:

EXTERN_C TESTCPPDLL_API void __stdcall WriteString(wchar_t*content);

这里的参数是wchar_t类型的指针,对应着C#中的char类型。TestCPPDLL.cpp中添加如下代码:

TESTCPPDLL_API void __stdcall WriteString(wchar_t*content)

{

    cout<<content;

}

该代码的功能就是将输入的字符串通过C++在控制台上输出。下面是在C#中的声明:

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")]

extern unsafe static void WriteString(char*c);

调用过程如下所示:

//因为使用指针,因为要声明非安全域

unsafe

{

//在传递字符串时,将字符所在的内存固化,

//并取出字符数组的指针

fixed (char* p = &("hello".ToCharArray()[0]))

{

//调用方法

WriteString(p);

}

}

 

其运行效果如下图所示:

 3. 指针的传递

根据前面介绍的数据类型对照表,我们可以直接在方法中传递指针,但是要注意的是我们常常需要将数组的指针(数据入口地址,第一个元素的地址),数据从C/C++到C#时问题不大,但是如果从C#到C/C++时一定要将数组先固化,然后再传递处理。

下面演示如何传递指针,首先在TestCPPDLL.h中添加下列声明:

//传入一个整型指针,将其所指向的内容加1

EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i);

//传入一个整型数组的指针以及数组长度,遍历每一个元素并且输出

EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength);

//C++中生成一个整型数组,并且数组指针返回给C#

EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();

其实现写在TestCPPDLL.cpp中,代码如下所示:

TESTCPPDLL_API void __stdcall AddInt(int *i)

{

    (*i)++;

}

TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arrayLength)

{

    int*currentPointer=firstElement;

    for (int i = 0; i < arrayLength; i++)

    {

        cout<<*currentPointer;

        currentPointer++;

    }

    cout<<endl;

}

 

int *arrPtr;

TESTCPPDLL_API int* __stdcall GetArrayFromCPP()

{

    arrPtr=new int[10];

    

    for (int i = 0; i < 10; i++)

    {

        arrPtr[i]=i;

    }

    

    return arrPtr;

}

 

对应调用的C#代码如下所示:

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")]

extern unsafe static void AddInt(int* i);

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")]

extern unsafe static void AddIntArray(int* firstElement, int arraylength);

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")]

extern unsafe static int* GetArrayFromCPP();

调用过程如下所示:

unsafe

{

// 调用C++中的AddInt方法

int i = 10;

 

AddInt(&i);

Console.WriteLine(i);

 

//调用C++中的AddIntArray方法将C#中的数据传递到C++中,并在C++中输出

int[] CSArray = new int[10];

for (int iArr = 0; iArr < 10; iArr++)

{

CSArray[iArr] = iArr;

}

fixed (int* pCSArray = &CSArray[0])

{

AddIntArray(pCSArray, 10);

}

//调用C++中的GetArrayFromCPP方法获取一个C++中建立的数组

int* pArrayPointer = null;

pArrayPointer = GetArrayFromCPP();

for (int iArr = 0; iArr < 10; iArr++)

{

Console.WriteLine(*pArrayPointer);

pArrayPointer++;

}

}

4. 函数指针的传递

前面说明的都是简单数据类型的及其指针的传递,利用PInvoke我们也可以实现函数指针的传递,C#中并没有函数指针的概念,但是可以使用委托(delegate)来代替函数指针,关于C#中委托的说明,可以参考笔者前面的一个文章:《C#委托及事件》

大家可能会问,为什么要传递函数指针呢?利用PInvoke可以实现C#C/C++函数的调用,反过来,我们能不能在C/C++程序运行的某一时刻,来调用一个C#对应的函数呢?(例如在C++中存在一个独立线程,该线程可能在任意时刻触发一个事件,并且需要通知C#)。这个时候,我们就有必要将一个C#中已经指向某一个函数的函数指针(委托)传递给C++

想要传递函数指针,首先要在C#中定义一个委托,并且在C++中定义一个函数指针,同时要保证委托和函数指针具备相同的函数原型,我们首先编写C#的代码,如下所示:

//定义一个委托,返回值为空,存在一个整型参数

public delegate void CSCallback(int tick);

//定义一个用于回调的方法,与前面定义的委托的原型一样

//该方法会被C++所调用

static void CSCallbackFunction(int tick)

{

Console.WriteLine(tick.ToString ());

}

//定义一个委托类型的实例,

//在主程序中该委托实例将指向前面定义的CSCallbackFunction方法

static CSCallback callback;

 

CS的主程序中让callback指向CSCallbackFunction方法,代码如下所示:

//调用委托所指向的方法

callback = CSCallbackFunction;

 

 

然后在C/C++中定义一个函数指针,并且添加一个用于设置函数指针的函数,TestCPPDLL.h中的代码如下所示:

//定义一个函数指针

typedef void (__stdcall *CPPCallback)(int tick);

//定义一个用于设置函数指针的方法,

//并在该函数中调用C#中传递过来的委托

EXTERN_C TESTCPPDLL_API void SetCallback(CPPCallback callback);

SetCallback函数的实现在TestCPPDLL.cpp中,代码如下所示:

TESTCPPDLL_API void SetCallback(CPPCallback callback)

{

    int tick=rand();

    //下面的代码是对C#中委托进行调用

    callback(tick);

}

C#中添加SetCallback函数的声明,代码如下所示:

//这里使用CSCallback委托类型来兼容C++里的CPPCallback函数指针

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")]

extern static void SetCallback(CSCallback callback);

C#中的调用过程如下所示:

//让委托指向将被回调的方法

callback = CSCallbackFunction;

//将委托传递给C++

SetCallback(callback);

SetCallback方法被执行后,在C#中定义的CSCallbackFunction就会被C++所调用。

5. 结构体的传递

传递结构体的想法和传递一个int类型数据类似,struct中的数据是在内存中顺序排列的,只要保证保证以下几点,就可以直接传递结构体,甚至是结构体的指针:

  • 要传递的成员为公有的值类型字段
  • C#中结构体字段类型与C++结构体中的字段类型相兼容
  • C#结构中的字段顺序与C++结构体中的字段顺序相同,要保证该功能,需要将C#结构体标记为[StructLayout( LayoutKind.Sequential)]

下面通过代码进行说明,首先在C#中添加一个结构体,代码如下所示:

[StructLayout( LayoutKind.Sequential)]

struct Vector3

{

public float X, Y, Z;

}

该结构体表示一个3D向量,包括XYZ三个float类型的分量。

然后在TestCPPDLL.h中也定义一个相同结构的结构体,代码如下所示:

struct Vector3

{

    float X,Y,Z;

};

TestCPPDLL.h中声明一个用于传递Vector3结构体的一个函数,代码如下所示:

EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);

TestCPPDLL.cpp中将其实现,代码如下所示:

TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector)

{

    cout<<"got vector3 in cpp,x:";

    cout<<vector.X;

    cout<<",Y:";

    cout<<vector.Y;

    cout<<",Z:";

    cout<<vector.Z;

}

C#中添加对SendStructFromCSToCPP函数的声明,代码如下所示:

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")]

extern static void SendStructFromCSToCPP(Vector3 vector);

C#中的调用过程如下所示:

//建立一个Vector3的实例

Vector3 vector = new Vector3() { X =10,Y=20,Z=30 };

//vector传递给C++并在C++中输出

SendStructFromCSToCPP(vector);

基输出效果如下所示:

 

完整的TestCPPDLL.h代码如下所示:

 

复制代码
#define TESTCPPDLL_API __declspec(dllexport)

EXTERN_C TESTCPPDLL_API int __stdcall Add(int a,int b);

EXTERN_C TESTCPPDLL_API void __stdcall WriteString(wchar_t*content);

//传入一个整型指针,将其所指向的内容加1

EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i);

//传入一个整型数组的指针以及数组长度,遍历每一个元素并且输出

EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength);

//在C++中生成一个整型数组,并且数组指针返回给C#

EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();

 

//定义一个函数指针

typedef void (__stdcall *CPPCallback)(int tick);

//定义一个用于设置函数指针的方法,

//并在该函数中调用C#中传递过来的委托

EXTERN_C TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback);

 

struct Vector3

{

    float X,Y,Z;

};

EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);
复制代码



 

 

完整的TestCPPDLL.CPP代码如下所示:

 

复制代码
#include "stdafx.h"

#include <iostream>

#include "TestCPPDLL.h"

using namespace std;

TESTCPPDLL_API int __stdcall Add(int a,int b)

{

    return a+b;

}

TESTCPPDLL_API void __stdcall WriteString(wchar_t*content)

{

    wprintf(content);

    printf("\n");

}

 

TESTCPPDLL_API void __stdcall AddInt(int *i)

{

    (*i)++;

}

 

TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arrayLength)

{

    int*currentPointer=firstElement;

    for (int i = 0; i < arrayLength; i++)

    {

        cout<<*currentPointer;

        currentPointer++;

    }

    cout<<endl;

}

int *arrPtr;

TESTCPPDLL_API int* __stdcall GetArrayFromCPP()

{

    arrPtr=new int[10];

    

    for (int i = 0; i < 10; i++)

    {

        arrPtr[i]=i;

    }

    

    return arrPtr;

}

 

TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback)

{

    int tick=100;

    //下面的代码是对C#中委托进行调用

    callback(tick);

}

 

TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector)

{

    cout<<"got vector3 in cpp,x:";

    cout<<vector.X;

    cout<<",Y:";

    cout<<vector.Y;

    cout<<",Z:";

    cout<<vector.Z;

}
复制代码



 

 

 

完整的C#代码如下所示:

 

复制代码
using System;

using System.Collections.Generic;

using System.Runtime.InteropServices;

using System.Text;

 

namespace ConsoleApplication1

{

class Program

{

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "Add")]

extern static int Add(int a, int b);

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")]

extern unsafe static void WriteString(char* c);

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")]

extern unsafe static void AddInt(int* i);

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")]

extern unsafe static void AddIntArray(int* firstElement, int arraylength);

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")]

extern unsafe static int* GetArrayFromCPP();

 

 

 

//定义一个委托,返回值为空,存在一个整型参数

public delegate void CSCallback(int tick);

//定义一个用于回调的方法,与前面定义的委托的原型一样

//该方法会被C++所调用

static void CSCallbackFunction(int tick)

{

Console.WriteLine(tick.ToString());

 

}

//定义一个委托类型的实例,

//在主程序中该委托实例将指向前面定义的CSCallbackFunction方法

static CSCallback callback;

 

 

//这里使用CSCallback委托类型来兼容C++里的CPPCallback函数指针

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")]

extern static void SetCallback(CSCallback callback);

 

[StructLayout(LayoutKind.Sequential)]

struct Vector3

{

public float X, Y, Z;

}

 

[DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")]

extern static void SendStructFromCSToCPP(Vector3 vector);

 

static void Main(string[] args)

{

int c = Add(1, 2);

Console.WriteLine(c);

//因为使用指针,因为要声明非安全域

unsafe

{

//在传递字符串时,将字符所在的内存固化,

//并取出字符数组的指针

fixed (char* p = &("hello".ToCharArray()[0]))

{

//调用方法

WriteString(p);

}

 

}

unsafe

{

// 调用C++中的AddInt方法

int i = 10;

 

AddInt(&i);

Console.WriteLine(i);

 

//调用C++中的AddIntArray方法将C#中的数据传递到C++中,并在C++中输出

int[] CSArray = new int[10];

for (int iArr = 0; iArr < 10; iArr++)

{

CSArray[iArr] = iArr;

}

fixed (int* pCSArray = &CSArray[0])

{

AddIntArray(pCSArray, 10);

}

//调用C++中的GetArrayFromCPP方法获取一个C++中建立的数组

int* pArrayPointer = null;

pArrayPointer = GetArrayFromCPP();

for (int iArr = 0; iArr < 10; iArr++)

{

Console.WriteLine(*pArrayPointer);

pArrayPointer++;

}

}

 

 

 

 

//让委托指向将被回调的方法

callback = CSCallbackFunction;

//将委托传递给C++

SetCallback(callback);

 

//建立一个Vector3的实例

Vector3 vector = new Vector3() { X = 10, Y = 20, Z = 30 };

//将vector传递给C++并在C++中输出

SendStructFromCSToCPP(vector);

 

 

Console.Read();

}

}

}
 

可以说新手使用P-INVOKE最开始的头疼就是C#和C++的字符串传递,因为这里涉及到两个问题。

第一:C#的string和C++的字符串首指针如何对应。

第二:字符串还有ANSI和UNICODE(宽字符串)之分。

 

本文分三部分阐述:

第一:字符串指针当输入参数,

第二:字符串指针作为返回值,

第三:字符串指针作为输入输出参数。

 

C++部分的测试代码很简单这里就全部贴出来了:

 

1#include "stdafx.h"
2#include "TestDll.h"
3#include <stdio.h>
4#include <string.h>
5#include <tchar.h>
6
7
8 staticchar* _hello ="Hello,World!!";
9 static TCHAR * _helloW = TEXT("Hello,World!!");
10
11 void __stdcall PrintString(char* hello)
12{
13 printf("%s\n",hello);
14}
15
16 void __stdcall PrintStringW(TCHAR * hello)
17{
18 _tprintf(TEXT("%s\n"),hello);
19}
20
21
22 char* __stdcall GetStringReturn()
23{
24return _hello;
25}
26
27TCHAR * __stdcall GetStringReturnW()
28{
29return _helloW;
30}
31
32
33 void __stdcall GetStringParam(char* outHello,int len)
34{ //output "aaaaaaaa"
35 for(int i=0; i< len -1 ;i++) outHello[i] ='a';
36 outHello[len -1] ='\0';
37}
38
39 void __stdcall GetStringParamW(TCHAR * outHello,int len)
40{ //output "aaaaaaaa" unicode version.
41 for(int i=0; i< len -1 ;i++) outHello[i] = TEXT('a');
42 outHello[len -1] = TEXT('\0');
43}

 

 

 

下面看C#如何调用。

 

第一:字符串指针作为输入参数,可以使用byte[] 和MarshalAs来解决。(注意四个P-INVOKE,两个ANSI版本,和两个UNICODE版本),推荐使用MarshalAs方法简单明了。

 

1 [DllImport("TestDll", EntryPoint ="PrintString")]
2publicstaticexternvoid PrintStringByBytes(byte[] hello);
3
4 [DllImport("TestDll", EntryPoint ="PrintString")]
5publicstaticexternvoid PrintStringByMarshal([MarshalAs(UnmanagedType.LPStr)]string hello);
6
7 [DllImport("TestDll", EntryPoint ="PrintStringW")]
8publicstaticexternvoid PrintStringByBytesW(byte[] hello);
9
10 [DllImport("TestDll", EntryPoint ="PrintStringW")]
11publicstaticexternvoid PrintStringByMarshalW([MarshalAs(UnmanagedType.LPWStr)]string hello);
12
13
14publicvoid Run()
15 {
16 PrintStringByBytes(Encoding.ASCII.GetBytes("use byte[]"));
17 PrintStringByMarshal("use MarshalAs");
18 PrintStringByBytesW(Encoding.Unicode.GetBytes("use byte[]"));
19 PrintStringByMarshalW("use MarshalAs");
20}

 

 

 

第二:字符串指针作为返回值,和上面一样也有两种声明方法,同样也包含两个版本。注意:Marshal.PtrToStringAnsi()函数的使用,把字符串指针转变为C#的string.推荐使用MarshalAs方法简单明了。

 

1 [DllImport("TestDll", EntryPoint ="GetStringReturn")]
2publicstaticextern IntPtr GetStringReturnByBytes();
3
4 [DllImport("TestDll", EntryPoint ="GetStringReturn")]
5 [return:MarshalAs(UnmanagedType.LPStr)]
6publicstaticexternstring GetStringReturnByMarshal();
7
8 [DllImport("TestDll", EntryPoint ="GetStringReturnW")]
9publicstaticextern IntPtr GetStringReturnByBytesW();
10
11 [DllImport("TestDll", EntryPoint ="GetStringReturnW")]
12 [return: MarshalAs(UnmanagedType.LPWStr)]
13publicstaticexternstring GetStringReturnByMarshalW();
14
15
16publicvoid Run()
17 { //Marshal.PtrToStringAuto(GetStringReturnByBytes()); 自动判断类型不错。
18  Console.WriteLine(Marshal.PtrToStringAnsi(GetStringReturnByBytes()));
19 Console.WriteLine(GetStringReturnByMarshal());
20 Console.WriteLine(Marshal.PtrToStringUni(GetStringReturnByBytesW()));
21 Console.WriteLine(GetStringReturnByMarshalW());
22}

 

 

 

第三:字符串指针作为输入输出参数时,因为要求有固定的容量,所以这里使用的是StringBuilder,大家仔细看了,当然也有byte[]版本。这个看大家喜欢那个版本就是用那个.

 

1 [DllImport("TestDll", EntryPoint ="GetStringParam")]
2publicstaticexternvoid GetStringParamByBytes(byte[] outHello, int len);
3
4 [DllImport("TestDll", EntryPoint ="GetStringParam")]
5publicstaticexternvoid GetStringParamByMarshal([Out, MarshalAs(UnmanagedType.LPStr)]StringBuilder outHello, int len);
6
7 [DllImport("TestDll", EntryPoint ="GetStringParamW")]
8publicstaticexternvoid GetStringParamByBytesW(byte[] outHello, int len);
9
10 [DllImport("TestDll", EntryPoint ="GetStringParamW")]
11publicstaticexternvoid GetStringParamByMarshalW([Out, MarshalAs(UnmanagedType.LPWStr)]StringBuilder outHello, int len);
12
13
14publicbyte[] _outHello =newbyte[10];
15publicbyte[] _outHelloW =newbyte[20];
16public StringBuilder _builder =new StringBuilder(10); //很重要设定string的容量。
17 
18publicvoid Run()
19 {
20//
21  GetStringParamByBytes(_outHello, _outHello.Length);
22 GetStringParamByMarshal(_builder, _builder.Capacity);
23 GetStringParamByBytesW(_outHelloW, _outHelloW.Length /2);
24 GetStringParamByMarshalW(_builder, _builder.Capacity);
25
26//
27  Console.WriteLine(Encoding.ASCII.GetString(_outHello));
28 Console.WriteLine(_builder.ToString());
29 Console.WriteLine(Encoding.Unicode.GetString(_outHelloW));
30 Console.WriteLine(_builder.ToString());
31}

由于C#存在易反编译的弊端,虽然有一些工具和方法,混淆,重命名,加密,但是不是很方便,有些情况下混淆后不能成功运行了。很多人都想把核心的代码封装成C++的DLL就没那么简单被反编译了,当然没有绝对的。我个人认为这是比较好的方法 把最核心的功能封装在c++dll中然后用C#做界面或其他。

下面是一篇简单介绍C#如何调用C++DLL

 

 

转自http://blog.csdn.net/xiaochongchong1248/archive/2010/01/13/5181345.aspx

 

 

这里具体讲一下常用的字符串吧

 

我们在C#的框架中实现实时要求很强的功能时,通常会调用C++编写的动态链接库,常常我们会希望能将字符串传递给C#搭建的框架,譬如很典型的,我们想传递一个文件名,C#中没有CString类,咋办。这时我们可以借助char[] XXX作为中间媒介。下面举个例子:

C#框架中:

    [DllImport("TestDll.dll"(可以是你自己的任意名字), CharSet = CharSet.Ansi)]
    static extern bool Function(char[] fileName);//Function(任意被调用函数名)

    OpenFileDialog  openFileDialog1 = new OpenFileDialog();
    if (openFileDialog1.ShowDialog() == DialogResult.OK)
    {

        Function(openFileDialog1.FileName.ToCharArray());
    }

C++编写的库中:

    DLL1_API bool Function(char* fileName)//DLL1_API为任意你定义的名字
    {
        CString DllfileName;
        DllfileName=(char*)fileName;
        FunctionDLLOpenFile(DllfileName);//FunctionDLL为任意功能函数

        return true;
    }

因为Ansi码表示字符串结尾都是0('/n'),CString类可以自动找到filename后的第一个0值。

本篇转自http://blog.csdn.net/soul4past/archive/2009/05/07/4158368.aspx

但还有问题,这样的方法作为返回值时还存在问题,返回值的解决方案是利用StringBuild。另外我们能不能通过参数传递值呢?

C#调用C++的DLL时,参数传递便成了一个问题。今天我碰到的一个问题是,在C++中导出的函数的参数是string类型的,在C#中通过string的参数调用时,便会出现该内存已损坏或不能读取的异常信息。后来我把C++的导出函数的参数由string改为LPTSTR类型,也即char*类型,然后在C#中对应的参数改为StringBuilder类型,既解决了传进去的参数问题,又解决了传出参数的问题。

 C#调用C++动态库中如何传递字符串/char转CString/string转char/string转CString

 C#调用dll时的类型转换总结


C++(Win 32)

C#

char**

作为输入参数转为char[]通过Encoding类对这个string[]进行编码后得到的一个char[]

作为输出参数转为byte[]通过Encoding类对这个byte[]进行解码,得到字符串

C++ Dll接口:

void CplusplusToCsharp(in char** AgentID, out char** AgentIP);

C#中的声明:

[DllImport("Example.dll")]

public static extern void CplusplusToCsharp(char[] AgentID, byte[] AgentIP);

C#中的调用:

Encoding encode = Encoding.Default;

byte[] tAgentID;

byte[] tAgentIP;

string[] AgentIP;

tAgentID = new byte[100];

tAgentIP = new byte[100];

CplusplusToCsharp(encode.GetChars(tAgentID), tAgentIP);

AgentIP[i] = encode.GetString(tAgentIP,i*Length,Length);

Handle

IntPtr

Hwnd

IntPtr

int*

ref int

int&

ref int

void*

IntPtr

unsigned char*

ref byte

BOOL

bool

DWORD

int  uintint 更常用一些)

枚举类型

Win32

BOOL MessageBeep(UINT uType // 声音类型); 其中的声音类型为枚举类型中的某一值。

C#

用户需要自己定义一个枚举类型:

public enum BeepType

{

  SimpleBeep = -1,

  IconAsterisk = 0x00000040,

  IconExclamation = 0x00000030,

  IconHand = 0x00000010,

  IconQuestion = 0x00000020,

  Ok = 0x00000000,

}

C#中导入该函数:

[DllImport("user32.dll")]

public static extern bool MessageBeep(BeepType beepType);

C#中调用该函数:

MessageBeep(BeepType.IconQuestion);

结构类型

Win32

使用结构指针作为参数的函数:

BOOL GetSystemPowerStatus(

 LPSYSTEM_POWER_STATUS lpSystemPowerStatus

);

Win32中该结构体的定义:

typedef struct _SYSTEM_POWER_STATUS {

BYTE  ACLineStatus;

BYTE  BatteryFlag;

BYTE  BatteryLifePercent;

BYTE  Reserved1;

DWORD BatteryLifeTime;

DWORD BatteryFullLifeTime;

} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS;

C#

用户自定义相应的结构体:

struct SystemPowerStatus

{

  byte ACLineStatus;

  byte batteryFlag;

  byte batteryLifePercent;

  byte reserved1;

  int batteryLifeTime;

  int batteryFullLifeTime;

}

C#中导入该函数:

[DllImport("kernel32.dll")]

public static extern bool GetSystemPowerStatus(

  ref SystemPowerStatus systemPowerStatus);

C#中调用该函数:

SystemPowerStatus sps;

….sps初始化赋值……

GetSystemPowerStatus(ref sps);

字符串

对于字符串的处理分为以下几种情况:

1、  字符串常量指针的处理(LPCTSTR),也适应于字符串常量的处理,.net中的string类型是不可变的类型。

2、  字符串缓冲区的处理(char*),即对于变长字符串的处理,.netStringBuilder可用作缓冲区

Win32

BOOL GetFile(LPCTSTR lpRootPathName);

C#

函数声明:

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]

static extern bool GetFile (

 [MarshalAs(UnmanagedType.LPTStr)]

 string rootPathName);

函数调用:

string pathname;

GetFile(pathname);

备注:

DllImport中的CharSet是为了说明自动地调用该函数相关的Ansi版本或者Unicode版本

 

变长字符串处理:

C#

函数声明:

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]

public static extern int GetShortPathName(

  [MarshalAs(UnmanagedType.LPTStr)]

  string path,

  [MarshalAs(UnmanagedType.LPTStr)]

  StringBuilder shortPath,

  int shortPathLength);

函数调用:

StringBuilder shortPath = new StringBuilder(80);

int result = GetShortPathName(

@"d:/test.jpg", shortPath, shortPath.Capacity);

string s = shortPath.ToString();

struct

具有内嵌字符数组的结构:

Win32

typedef struct _TIME_ZONE_INFORMATION {

  LONG    Bias;

  WCHAR   StandardName[ 32 ];

  SYSTEMTIME StandardDate;

  LONG    StandardBias;

  WCHAR   DaylightName[ 32 ];

  SYSTEMTIME DaylightDate;

  LONG    DaylightBias;

} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION;

C#

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

struct TimeZoneInformation

{

  public int bias;

  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

  public string standardName;

  SystemTime standardDate;

  public int standardBias;

  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

  public string daylightName;

  SystemTime daylightDate;

  public int daylightBias;

}

具有回调的函数

Win32

BOOL EnumDesktops(

 HWINSTA hwinsta,       // 窗口实例的句柄

 DESKTOPENUMPROC lpEnumFunc, // 回调函数

 LPARAM lParam        // 用于回调函数的值

);

回调函数DESKTOPENUMPROC的声明:

BOOL CALLBACK EnumDesktopProc(

 LPTSTR lpszDesktop, // 桌面名称

 LPARAM lParam    // 用户定义的值

);

C#

将回调函数的声明转化为委托:

delegate bool EnumDesktopProc(

 [MarshalAs(UnmanagedType.LPTStr)]

  string desktopName,

  int lParam);

该函数在C#中的声明:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool EnumDesktops(
  IntPtr windowStation,
  EnumDesktopProc callback,
  int lParam);

该表对C#中调用win32函数,以及c++编写的dll时参数及返回值的转换做了一个小的总结,如果想进一步了解这方面内容的话,可以参照msdn中“互操作封送处理”一节。

C#语言使用方便,入门门槛较代,上手容易,并且语法与C,java有很类似的地方,IDE做的也好,通用性好,是MS下一代开发的主要力量.但是其开源代码较少,类库不是十分完美,在架构方面还有一些需要做的工作.

C++写的程序占用内存较小,直接对内存或者文件操作,因此一些关键的步骤或者一些最内层的循环在一定程序上还需要依赖C++.

下面我做一些简单的例子

第一步,用C++做一个可以导出函数的dll(不采用def文件)

cxyMath.h
//在这里定义导出哪一些函数

class MyMathFuncs
{
public:
    
// Returns a + b
    static __declspec(dllexport) double Add(double a, double b);

    
// Returns a - b
    static __declspec(dllexport) double Subtract(double a, double b);

    
// Returns a * b
    static __declspec(dllexport) double Multiply(double a, double b);

    
// Returns a / b
    
// Throws DivideByZeroException if b is 0
    static __declspec(dllexport) double Divide(double a, double b);
}
;

cxyMath.cpp的实现就很简单了,代码附在上传的文件中,在这里就不贴代码了,编译成dll后,拷贝dll,lib文件到C#的工程中的debug的目录下(如果你写的是release版,请将dll,lib拷贝到relase文件夹下)

第二步:找出导出的函数名
写成如下形式,方便CS的调用
不采用def文件导出的函数名有些奇怪,但还是可以看出函数的层次,?函数名@类名@命名空间@@******,
找函数名可以使用ultraedit32,打开lib文件,就可看到了
另外,我们可以使用dllexp这个程序找出导出的函数(这个程序见附录)

[DllImport("cppdll.dll",EntryPoint="?Divide@MyMathFuncs@MathFuncs@@SANNN@Z",CharSet = CharSet.Auto)]
public static extern double Divide(double a,double b);

[DllImport(
"cppdll.dll",EntryPoint="?Multiply@MyMathFuncs@MathFuncs@@SANNN@Z",CharSet = CharSet.Auto)]
public static extern double Multiply(double a,double b);

 第三步,调用

private void button1_Click(object sender, System.EventArgs e)
{
    MessageBox.Show(Multiply(
12,13).ToString());
}

采用def 文件导出函数

第一种方式比较简单,但是找一个dll函数的入口地址,还是比较麻烦的,并且,入口地址没有太大的意义,不直观,不好记忆

一般情况下,我们可以选择使用def文件导出函数

第一步,新建一个win32 application然后在应用程序的设置中选择动态dll,然后选择导出符号,这样,vs2003就为我们生成了一个非常完整的架子,但是美中不足的是生成的dll导出的函数也是和第一中情况一样

第二步,添加一个def文件,生成def文件的同时,vs2003自动为我们添加了这样一行,
LIBRARY win32dll

我们只要在他的下面加上我们要导出的函数就可以了.
   GetAName   @1
   ShowMyName @2
   PerfTest   @3

这样经过编译我们使用dllexp查看,看到的就不再是一些没有意义的函数名了,而是我们在def中定义的文件函数名

第三步,拷贝lib,dll文件到CS工程中就可以了,
我们就不在这里一一叙述了

刚才我们写的dll同样也可以为C++的工程调用
第一步:新建一个console的C++ application
第二步:添加引用,引用C++ dll application
第三步:拷贝c++ dll的.h文件到console app的目录下,并添加到console app中
第四步:#include "cxyMath.h"
调用就可以了


详情见代码

C#调用C++生成的dll例子,文件中共包含四个工程文件:

cppdll是不使用def文件导出dll函数的示例
win32dll是使用def文件导出dll函数的示例
CsApp是C#一个工程文件,用来调用C++的dll
dlltest是C++调用C++dll的一个示例

代码:http://dl2.csdn.net/down4/20070725/25180037196.rar

dllexp:查看dll导出的一个小程序

http://www.nirsoft.net/utils/dll_export_viewer.html

看了这篇文章后,就知道如何去调用了,但还有一些问题,比如C#的一些类型和C++的类型是不同的

比如C++中的string和C#中的string是不一样的

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics