一种用 C++动态扩展 C# 程序的方法
摘要: 提出一种用非托管 C++( 以下简称 C++) 动态扩展 C# 程序的方法。利用托管 C++ 作为适配器, 由 C++ 类继承 C# 基类, 并且获取 C# 程序提供的服务; 将 C++ 类利用托管 C++ 作为适配器, 通过 C# 基类的派生类提供给 C# 程序动态加载。实例表明该方法能够使 C++ 编写的类继承 C# 程序中的类, 获取 C# 程序提供的服务; 并且使 C# 程序能够动态创建并调用 C++ 类对象。该方法能够为 C++ 源代码的重用、C++ 源代码与.NET 平台语言的混合编程等提供解决方案。
为了能帮助学习C/C++的伙伴,博主推荐一个学习交流群 710 -520- 381 编号 灵狐 有免费资料可以领取
1 引言
C++是国内外广泛使用的现代计算机语言, 它既支持面向过程的程序设计,也支持基于对象和面向对象的程序设计。经过多年的积累, 已经形成了大量 C++编写的源代码或开源类库且应用广泛。
C# 是基于微软公司.NETFramework的面向对象程序设计语言。它在保持了 C++中大部分语法的同时,添加了大量的高效代码和完全面向对象特性,以及更高的可靠性和安全性。它不仅能用于 Web服务程序的开发,并且还能支持系统级程序开发和界面开发。
为了提高开发效率,综合利用两种语言的优势,在一些应用软件中,需要用 C++动态扩展 C# 程序,主要包括 C++类获得 C# 程序所提供的服务,以及 C# 程序动态创建并调用 C++类对象两个方面。目前用 C++动态扩展 C# 程序主要有基于源代码转换的方法、基于动态链接库和基于 COM组件等方法,其中基于源代码转换的方法目前还不完善, 对于 C++的指针等特殊语法的转换还不能满足实际需求。而基于动态链接库和基于 COM组件的方法虽然能完成一定的互相调用, 但是这些方法都没有很好的支持 C++类继承 C# 类,以及 C# 程序动态创建并调用 C++ 类对象等功能。
提出了一种 C++类与 C# 程序的动态链接方法, 如图 1所示,C# 主程序 (CSharpProgram) 的应用框架建立在 C# 基类
(CSClass(Base))之上, 利用托管 C++(ManagedCpp)作为适配器提供给非托管(Unmanaged)C++编写的类(UMCppClass)继承;为了动态创建和调用 C# 基类派生的非托管 C++类对象,编写该基类的派生类(CSClass(Derived)),利用托管 C++作为适配器将 C++类提供给该 C# 派生类调用,C# 程序通过反射机制, 动态加载 C# 派生类的对象,从而实现对 C++类对象的动态创建和调用。
2 C++类继承 C# 程序中的类
非托管 C++类不能直接继承 C# 类,利用托管 C++作为适配器,将 C# 类提供给非托管 C++继承,并且获取 C# 程序提供的服务。
2.1C# 基类(CSClass)
以一个普通的 C# 基类为例, 该 C# 基类 CSClass具有一个
int型数据成员 m_data(初值为 0)及其读取函数 Getdata();一个普通成员函数 goAhead(),在该函数中 m_data递增;一个虚函数 run
(),提供给派生类覆盖,以实现不同方式的 run()。 CSClass 类的关键代码段如下:
//CSClass.cs
public class CSClass{
public int m_data;//数据成员
public CSClass() {m_data = 0;}
public int Getdata(){return m_data;} public void goAhead()//普通成员函数
{m_data ++;}
public virtual void run(){}//虚函数
……//其他函数或数据成员}
2.1C#基类的托管 C++适配器(ManagedCsharp)
托管 C++类 ManagedCsharp包含了 CSClass类的所有公有成员函数,并且包含一个 void类型的指针 p_Csharp,在其构造函数中创建一个 CSClass类对象, 并且生成一个指向该对象的指针 CSClass^p_temp,并且利用.NETFramwork 中 System. Run-
time.InteropServices命名空间中的 GCHandle结构完成 Void类型指针与指向 CSClass对象指针之间的转换,通过指向 CSClass 对象的指针调用 CSClass的成员函数。ManagedCsharp的关键代码段如下:
//ManagedCsharp.h class ManagedCsharp{ public:
ManagedCsharp ();
~ ManagedCsharp (); int Getdata();
virtual void run(); void goAhead(); void* p_Csharp;};
//ManagedCsharp.cpp
using namespace System::Runtime::InteropServices;
// 将 void 指针转换为指向 CSClass 类对象的指针inline CSClass^ void2CSClass(void *pHandle){……} ManagedCsharp::ManagedCsharp(){
p_Csharp = NULL;
CSClass^ p_temp = gcnew CSClass(); GCHandle handle = GCHandle::Alloc(p_temp);
p_Csharp=(GCHandle::operatorSystem::IntPtr(handle)).To- Pointer();}
ManagedCsharp::~ManagedCsharp(){ if (p_Csharp == NULL)return;
GCHandle handle = GCHandle::operator GCHandle (Sys- tem :: IntPtr(p_Csharp)) ;
handle.Free(); p_Csharp = NULL; }
void ManagedCsharp::run(){
CSClass^ p_temp = void2CSClass(p_Csharp); p_temp->run();}
void ManagedCsharp::goAhead(){
CSClass^ p_temp = void2CSClass(p_Csharp); p_temp->goAhead();}
int ManagedCsharp::Getdata(){
CSClass^ p_temp = void2CSClass(p_Csharp); return p_temp->Getdata();}
2.2C++编写的类(UMCppClass)
用 C++ 编写的类继承 ManagedCsharp类, 以两个不同的
C++类 UMCppClass1和 UMCppClass2为例,这两个类均为 Man-
agedCsharp的派生类,它们都覆盖了基类中的 run虚函数,但是提供了不同的实现, 其中 UMCPPClass1中的 run函数运行 1次goAhead(), 而 UMCPPClass2中的 run函数运行 2次 goAhead()。
这两个类的关键源代码如下:
//UMCppClass1.h
class UMCppClass1 :public ManagedCsharp{ public:
UMCppClass1 (void);
~UMCppClass1 (void); virtual void run();};
//UMCppCLass1.cpp
//UMCPPClass1 中的 run()运行 1 次 goAhead() void UMCppCLass1::run(){goAhead();}
//UMCppCLass2.cpp
//UMCPPClass1 中的 run()运行 2 次 goAhead() void UMCppCLass2::run(){goAhead();goAhead();}
2C#程序动态加载 C++类对象
C#程序不能直接通过反射机制动态加载 C++编写的类,利用托管 C++作为适配器, 提供给 CSsharp类的派生类 CSDerived-
Class类调用, 然后由 C#主程序通过反射机制动态加载 CS-
DerivedClass类的对象,从而实现对 C++类对象的动态创建和调用。
3.1C++类的托管 C++适配器(ManagedUMCpp)
托管 C++类 ManagedUMCpp类包含了 UMCppClass类的所有公有成员函数, 并且包含一个指向 UMCppClass类对象的指针 p_UMCpp,还包含了一个指向 CSClass类对象的指针
p_Csharp, 用于存储 UMCppCLass类对象中的数据成员
p_Csharp, 为 C#派生类 CSDerivedClass中数据成员的赋值做准备。由于 ManagedUMCpp1类与 ManagedUMCpp2类的源代码仅仅在类名上不同,以下仅列举 ManagedUMCpp1类的关键代码段:
//ManagedUMCpp1.h
public ref class ManagedUMCpp1{ public:
ManagedUMCpp1 ();
~ ManagedUMCpp1 (); void virtual run();
void goAhead(); int Getdata();
UMCppCLass1 * p_UMCpp; CSClass ^ p_Csharp;};
//ManagedUMCpp1.cpp ManagedUMCpp1:: ManagedUMCpp1(){
p_UMCpp = new UMCppClass1();
p_Csharp = GetImpObj(p_UMCpp ->p_Csharp);} ManagedUMCpp1::~ManagedUMCpp1(){delete p_UMCpp ;} void ManagedUMCpp1::run(){p_ UMCpp ->run();}
void ManagedUMCpp1::goAhead(){p_UMCpp1-> goAhead();} voidManagedUMCpp1::Getdata()
{return p_UMCpp1->Getdata();}
3.2C#派生类(CSDerivedClass)
C#派生类 CSDerivedClass继承 CSClass类。包含一个
ManagedUMCpp类的对象,由于 UMCppClass类的函数只能直接改变 CSClass类对象的数据成员 mumcpp,所以在 CSDerived-
Class中通过 mumcpp的 p_Csharp指针获取 UMCppClass中生成的 CSClass类对象的数据成员值,保持 CSDerivedClass对象数
据成员与 CSsharp 数据成员值的一致。为了演示不同 C++代码实现的不同的虚函数 run(), 在 CSDerivedClass 的 run 函数中输出了其数据成员 m_data 的值。该类的关键代码段如下:
//CSDerivedClass1.cs
public class CSDerivedClass1: CSClass{ public ManagedUMCpp1 mumcpp; public CSDerivedClass1()
{ mumcpp = new ManagedUMCpp1();} public override void run(){
mumcpp.run();
m_data = mumcpp. p_Csharp.Getdata();
Console.WriteLine("IamUMCpp1,Igo"+m_data.ToString () + "step");}
CSDerivedClass2 与 CSDerivedClass1 的差别仅仅在名称上。
//CSDerivedClass2.cs public override void run(){ mumcpp.run();
m_data = mumcpp. p_Csharp.Getdata();
Console.WriteLine("IamUMCpp2,Igo"+m_data.ToString () + "steps");}
3.1C#程序(CSharpProgram)
在 C#程序中, 生成一个 onject对象数组 objs,通过反射机制, 动态加载 CsharpDerived1类和 CsharpDerived2类的对象,并通过统一的代码((CSClass)objs[i]).run() 调用其 run函数, 运行结果如图 2所示, 其中调用了 UMCpp1类 run函数 (运行了 1次
goAhead函数)的 CsharpDerived1类对象输出了“IamUMCpp1, I go1step”,调用了 UMCpp2类 run函数(运行了 2次 goAhead函数) 的 CsharpDerived2类对象输出了 “IamUMCpp2, Igo2
steps”而可以发现,在不修改 C# 程序调用 run 函数的代码情况下,完成了动态加载不同 C++类的不同 run 函数实现,通过 C++ 动态扩展了 C# 程序。以下是 C# 程序的关键代码段:
//CSharpProgram.cs
static void Main(string[] args){ object[] objs = new object[2];
objs[0]=Assembly.Load("CsharpDerived1").CreateInstance ("CsharpDerived1.CSDerivedClass1");
objs[1]=Assembly.Load("CsharpDerived2").CreateInstance ("CsharpDerived2.CSDerivedClass2");
for(int i = 0; i<2; i++)
{((CSClass)objs[i]).run();}}
4实例分析
最后将该方法应用于基于.NET 的 Robocode 教学系统中,
Robocode 是美国 IBM 公司开发的机器人 (其图形为坦克的形状) 战斗仿真引擎。不同用户利用 Java 语言对机器人进行编程, 给机器人设计赋予不同“智能”指挥它的行动,仿真引擎图形化地显示战斗的过程和结果。可以使用户在娱乐中学习 java 编程。作者编写了一个.NET 平台下的 Robocode 教学系统,用于 C# 语言以及.NET 平台下的其他语言教学。
将该方法应用于该教学系统中, 可以将其扩展到非托管
C++语言的教学中。 C# 主程序(CSharpMain)的应用框架建立在
C# 基类(Tanks)上,利用托管 C++(ManagedCpp)作为适配器提供给用户编写的 C++类(MyTank)继承;利用托管 C++作为适配器将 MyTank 类提供给 Tanks 的派生类 VirtualTanks 调用,C# 主程序通过反射机制,动态加载 C# 派生类的对象,从而实现对 C++ 类对象的动态创建和调用。
图 3(a)为两个不同用户 C++ 类 MyTank1和 MyTank2的源代码片段, 它们在给出了基类中 run()、onHitWall()、OnBullethit-
Tank()等虚函数的不同实现, 分别表示了坦克运行时的基本动作,坦克在碰到墙后的动作以及坦克被子弹击中后的动作。图 3
(b)显示了主系统动态载入了两个“ 坦克”MyTank1和 MyTank2
后的战场界面,两个坦克分别有不同的运行策略,实现了 C++对
C# 程序的动态扩展。
5结论
通在一些应用软件中,需要利用非托管 C++动态扩展 C# 程序,而.NET Framework 没有提供 C# 程序与 C++之间直接的动态链接机制(包括类的继承、动态加载类对象等)。提出了一种用非托管 C++动态扩展 C# 程序的方法, 利用托管 C++作为适配器, 由 C++类继承 C# 基类,并且覆盖(override)基类中相应的虚函数, 获取 C# 程序提供的服务; 将 C++类利用托管 C++作为适配器, 通过 C# 基类的派生类提供给 C# 程序动态加载,从而使 C++编写的类继承 C# 程序中的类,并且使 C# 程序能够动态创建并调用 C++类的对象, 将该方法应用于基于.NET 的 Robocode 教学系统,实例表明提出的方法能够使 C++编写的类继承 C# 程序中的类,获取 C# 程序提供的服务;并且使 C# 程序能够动态创建并调用 C++类对象。能够实现动态载入不同 C++用户代码实现 C# 基类中相应虚函数的功能,为 C++源代码的重用、C++与.NET 平台语言的混合编程等提供有效的解决方案。
本文创新点: 提出一种用 C++动态扩展 C# 程序的方法。利用托管 C++作为适配器,由 C++类继承 C# 基类,并通过 C# 基类的派生类提供给 C# 程序动态加载。
如图 3 所示, 采用启发式选择策略能够获得更佳的系统传输延迟。并且,与随机选择策略相比,系统传输延迟由 2.5 秒降低至 1.7 秒,降低幅度大约为 30%。
另一方面,从图 3中可以看出,采用随机选择策略,当系统传输延迟逐渐趋于稳定后(250秒后),随着节点的加入,系统传输延迟逐渐升高,当节点停止加入后,系统传输延迟则基本保持不变; 而采用基于服务能力的启发式选择策略,在节点加入后期(1500 秒后),系统传输延迟不仅没有升高,而且还呈现下降的趋势。并且,当节点停止加入后,系统传输延迟仍然趋于下降,这是因为: 在运行过程中, 节点将根据自身的服务能力对其在系统中的位置进行自适应调整,服务能力高的节点将逐渐向数据源靠近,以降低层状结构的高度,从而获得更优的系统传输延迟。而调整过程是持续的, 直至节点的合作节点的服务能力均不低于自身的服务能力。
6总结
为了能帮助学习C/C++的伙伴,博主推荐一个学习交流群 710 -520- 381 编号 灵狐 有免费资料可以领取
针对 P2P 流媒体直播系统中的合作节点选择问题,本文提出了一种基于服务能力的启发式选择策略。仿真结果表明:该策略能够根据节点的服务能力对其在系统中的位置进行自适应调整,从而获得更佳的系统传输延迟。本文对 P2P 流媒体直播系统中的合作节点选择策略进行了初步探讨,复杂的 P2P 网络环境中存在很多影响因素,例如节点的高度动态性,进一步工作将会对这些因素的影响进行探讨。