快捷搜索:  汽车  科技

c和程序有什么区别(C与CC)

c和程序有什么区别(C与CC)PInvoke从功能上来说,只支持函数调用,在被导出的函数前面一定要添加extern "C"来指明导出函数的时候使用C语言方式编译和连接,这样保证函数定义的名字和导出的名字相同,否则如果默认按C 方式导出,那个函数的名字就会变得乱七八糟,我们的程序就无法找到入口点了。注意事项:第二种方法:简单的实现并不麻烦,只要添加DllImportAttribute特性即可以导入C/C 的函数,但是问题是PInvoke不能简单的实现对C 类的调用。在Warensoft3D中为了可以使用MONO实现跨平台(当然DirectX是不能跨平台的),所以使用了本方法,下面将对本方法展开详细的说明。测试平台:Windows7 64位,VS2010,.NET4.0

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 方式导出,那个函数的名字就会变得乱七八糟,我们的程序就无法找到入口点了。

本文将说明以下几点:

互调的基本原理

基本数据类型的传递

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

第二步:

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

c和程序有什么区别(C与CC)(1)

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

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

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

c和程序有什么区别(C与CC)(2)

第三步:

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

c和程序有什么区别(C与CC)(3)

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

c和程序有什么区别(C与CC)(4)

第四步:

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

c和程序有什么区别(C与CC)(5)

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#的项目属性,如下图所示:

c和程序有什么区别(C与CC)(6)

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

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

c和程序有什么区别(C与CC)(7)

第五步:

前面的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);

}

}

其运行效果如下图所示:

c和程序有什么区别(C与CC)(8)

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向量,包括X,Y,Z三个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);

基输出效果如下所示:

c和程序有什么区别(C与CC)(9)

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

#defineTESTCPPDLL_API __declspec(dllexport)

EXTERN_C TESTCPPDLL_APIint__stdcall Add(inta intb);

EXTERN_C TESTCPPDLL_APIvoid__stdcall WriteString(wchar_t*content);

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

EXTERN_C TESTCPPDLL_APIvoid__stdcall AddInt(int*i);

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

EXTERN_C TESTCPPDLL_APIvoid__stdcall AddIntArray(int*firstElement intarraylength);

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

EXTERN_C TESTCPPDLL_APIint* __stdcall GetArrayFromCPP();

//定义一个函数指针

typedefvoid(__stdcall *CPPCallback)(inttick);

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

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

EXTERN_C TESTCPPDLL_APIvoid__stdcall SetCallback(CPPCallback callback);

structVector3

{

floatX Y Z;

};

EXTERN_C TESTCPPDLL_APIvoid__stdcall SendStructFromCSToCPP(Vector3 vector);

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

#include"stdafx.h"

#include

#include"TestCPPDLL.h"

usingnamespacestd;

TESTCPPDLL_APIint__stdcall Add(inta intb)

{

returna b;

}

TESTCPPDLL_APIvoid__stdcall WriteString(wchar_t*content)

{

wprintf(content);

printf("\n");

}

TESTCPPDLL_APIvoid__stdcall AddInt(int*i)

{

(*i) ;

}

TESTCPPDLL_APIvoid__stdcall AddIntArray(int*firstElement intarrayLength)

{

int*currentPointer=firstElement;

for(inti =0; i < arrayLength; i )

{

cout<<*currentPointer;

currentPointer ;

}

cout<

}

int*arrPtr;

TESTCPPDLL_APIint* __stdcall GetArrayFromCPP()

{

arrPtr=newint[10];

for(inti =0; i <10; i )

{

arrPtr[i]=i;

}

returnarrPtr;

}

TESTCPPDLL_APIvoid__stdcall SetCallback(CPPCallback callback)

{

inttick=100;

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

callback(tick);

}

TESTCPPDLL_APIvoid__stdcall SendStructFromCSToCPP(Vector3 vector)

{

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

cout<

cout<<" Y:";

cout<

cout<<" Z:";

cout<

}

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

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Runtime.InteropServices;

usingSystem.Text;

namespaceConsoleApplication1

{

classProgram

{

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

externstaticintAdd(inta intb);

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

externunsafestaticvoidWriteString(char* c);

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

externunsafestaticvoidAddInt(int* i);

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

externunsafestaticvoidAddIntArray(int* firstElement intarraylength);

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

externunsafestaticint* GetArrayFromCPP();

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

publicdelegatevoidCSCallback(inttick);

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

//该方法会被C 所调用

staticvoidCSCallbackFunction(inttick)

{

Console.WriteLine(tick.ToString());

}

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

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

staticCSCallback callback;

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

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

externstaticvoidSetCallback(CSCallback callback);

[StructLayout(LayoutKind.Sequential)]

structVector3

{

publicfloatX Y Z;

}

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

externstaticvoidSendStructFromCSToCPP(Vector3 vector);

staticvoidMain(string[] args)

{

intc = Add(1 2);

Console.WriteLine(c);

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

unsafe

{

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

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

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

{

//调用方法

WriteString(p);

}

}

unsafe

{

// 调用C 中的AddInt方法

inti =10;

AddInt(&i);

Console.WriteLine(i);

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

int[] CSArray =newint[10];

for(intiArr =0; iArr <10; iArr )

{

CSArray[iArr] = iArr;

}

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

{

AddIntArray(pCSArray 10);

}

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

int* pArrayPointer =null;

pArrayPointer = GetArrayFromCPP();

for(intiArr =0; iArr <10; iArr )

{

Console.WriteLine(*pArrayPointer);

pArrayPointer ;

}

}

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

callback = CSCallbackFunction;

//将委托传递给C

SetCallback(callback);

//建立一个Vector3的实例

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

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

SendStructFromCSToCPP(vector);

Console.Read();

}

}

}

更多c/c linux免费视频资料获取 后台私信【架构】详情请看课程大纲由于页面问题需要清晰版课程大纲请后台私信我

c和程序有什么区别(C与CC)(10)

猜您喜欢: