利用C++/CLI 封装Native C++ 提升.NET web game性能
这段时间在做新项目的技术论证,其基本想法就是利用Flex调用远程WCF以及发送Socket请求,来实现远程通信,WCF负责实时性要求不高的操作,如聊天,邮件,交易,买卖等,而Socket的目的是提供实时战斗的服务。
无论是WCF,还是Socket,都会提供不少的游戏逻辑,当然,大家心里都有数,与C++相比C#在纯数值运算上慢的太多,但是提到语法简洁性,MS的数据访问组件支持,IDE的智能感知VC又相形见绌。
虽然C++/CLI不能够满足上面的各种要求,但是它却可以Native C++和C#的Adapter,来保证程序的优质性。本文的目的有两点:
如何利用C++/CLI封装Native C++,并被C#调用
被封装过的Native C++代码与C#代码在性能方面的对比
-
C++/CLI封装Native C++,并被C#调用
为了方便团队开发,我们通常要将C++代码编译成DLL,以方便.NET开发人员调用,因此,我们首先来编写一个Native C++的类,并编译成DLL。过程如下:
-
在VS2008的VC++项目模板中,选择Win32项目中的 Win32 Project,并命名为NativeCPPDLL,如下图所示:
-
选择将其编译成DLL,如下图所示:
-
在项目中添加一个名为NativeCPP的类,基类结构如下图所示:
-
NativeCPP的头文件(NativeCPP.h)代码如下:
#pragma once
#include "math.h"
#ifndef GoWin_DLL_CLASS_EXPORTS
//该类可导出
#define GoWin_DLL_CLASS __declspec(dllexport)
#else
//该类可导入
#define GoWin_DLL_CLASS __declspec(dllimport)
#endif
class GoWin_DLL_CLASS NativeCPP
{
public:
NativeCPP(void);
~NativeCPP(void);
void Compute1();
void Compute2();
}
-
NativeCPP的CPP文件(NativeCPP.cpp)的代码如下所示:
#include "StdAfx.h"
#include "NativeCPP.h"
NativeCPP::NativeCPP(void)
{
}
NativeCPP::~NativeCPP(void)
{
}
void NativeCPP::Compute1 ()
{
for(int i=0;i<100000;i++)
{
for(int j=0;j<i;j++)
{
}
}
}
void NativeCPP::Compute2 ()
{
for(int i=0;i< 30000000;i++)
{
pow(1.05,i);//pow函数是游戏里常用的数据增长公式
}
}
-
-
编译代码。
-
在解决方案中添加一个C++/CLI的类库,将命名为CPPWrapper如下图所示:
-
为CPPWrapper添加刚才编译好的DLL项目的引用,如下图所示,在该项目上点击右键,选择"Reference":
-
在弹出的对话框中选择Add New Reference,如下图所示:
-
在Projects里选择刚才建立的NativeCPPDLL项目。如下图所示:
-
在CPPWrapper里添加一个头文件,内容和名称都"NativeCPP.h"一样。
-
在CPPWrapper里添加一个名为NativeCPPWrapper的托管类,如下图所示:
该类的结构与NativeCPP中的类结构完全一样,但是要在类中定义一个从DLL导入的Native C++类的指针,.h文件中的代码如下所示:
#pragma once
#include "NativeCPP.h"
#define GoWin_DLL_CLASS_EXPORTS
public ref class NativeCPPWrapper
{
private:
NativeCPP*nativeCPP;//native c++类的指针
public:
NativeCPPWrapper(void);
public:
void Compute1();
void Compute2();
};
该类的CPP文件,完成就是对Native C++类的接口转换,其代码如下所示:
#include "StdAfx.h"
#include "NativeCPPWrapper.h"
NativeCPPWrapper::NativeCPPWrapper(void)
{
this->nativeCPP =new NativeCPP();
}
void NativeCPPWrapper::Compute1()
{
this->nativeCPP ->Compute1 ();
}
void NativeCPPWrapper::Compute2()
{
this->nativeCPP ->Compute2 ();
}
-
在C++/CLI的项目属性里,将Common Language Runtime Support设置为Common Language Runtime Support(/clr),否则的话,将无法使用非托管的Native C++。
-
现在可以建立一个C#的项目了,只要添加CPPWrapper的引用,就可以像调用C#写的DLL一样来调用C++/CLI写的类了,如下图所示:
-
如何你用的是64位的操作系统的话,直接调用会出错,这时要配置一个C#项目的编译选择,将编译的目标平台设置成"X86"。(请千万别把它读成"叉儿八六",你是专业计算机人员,应该说的专业点,它叫"艾克思八六")。如下图所示:
-
前面对如何利用C++/CLI封装原生C++代码,并被C#所调用作了一些说明,下面通过数据对比来说明这样做的意义。
-
实验说明:
-
该实验将进行如下几组测试
编号 |
说明 |
1 |
利用MFC调用NativeCPP 的DLL,并分别测试NativeCpp中Compute1()以及Compute2()两个函数所需要的时间 |
2 |
利用C# WinForm调用NativeCPPWrapper(C++/CLI)来间接访问NativeCPP类中的Compute1()以及Compute2()两个函数,并检测所需要的时间。 |
3 |
利用C# WinForm直接调用C#编写的相同功能的Compute1()以及Compute2()两个函数,并检测所需要的时间。 |
说明:Compute1()里只有循环,Compute2()里包括了一个pow函数。
-
代码略
-
测试环境
-
测试结果(Debug版本)
编号 |
操作 |
时间 |
1 |
MFC 调用NativeCPP::Compute1() |
14s |
2 |
MFC调用NativeCPP::Compute2() |
9s |
3 |
C#调用NativeCPPWrapper::Compute1() |
14s |
4 |
C#调用NativeCPPWrapper::Compute2() |
9s |
5 |
C#调用C#写的Compute1() |
19s |
6 |
C#调用C#写的Compute2() |
3s |
-
结论
通过1、2、3、4的数据,不难发现,利用C++/CLI封装过的Native C++在性能上基本没有什么损失(但是一定要在操作密集中的代码中封装,而不是调用密集型的代码)。1、3、5三组数据可说明,在单纯的循环上,C#要比C++慢很多。
在debug模式下大量使用Pow函数的时候.net framework里提供的Math.Pow,要远远快于math.h中的pow函数,实验结果中几乎差了三倍!如果改成Release模式,C++中两函数的操作时间基本上都为0,当然C#的速度也有明显提高,但是要远远慢于C++。