背景技术
二十世纪八十年代以来,软件编程技术有了很大的发展,其发展可以大致分为以下几个阶段:
面向对象编程,即通过对软件模块的封装,使其相对独立,从而使复杂的问题简单化。面向对象编程强调的是对象的封装,但模块(对象)之间的关系在编译的时候已被固定,模块之间的关系是静态的,这种关系在程序运行时不能改变;也就是说:在运行时不能换用模块中更小的功能单元。
面向构件编程,即为了使不同软件开发商提供的构件模块(软件对象)可以相互操作使用,构件之间的连接和调用通过标准的协议来实现。构件化编程模型强调协议标准,需要提供各厂商都能遵守的协议体系。就像公制螺丝的标准一样,所有符合标准的螺丝和螺母都可以相互装配。构件化编程模型建立在面向对象技术的基础之上,是完全面向对象的,提供了动态构造部件模块(在运行中可以构造部件)的机制。构件在运行时可以动态装入,是可以更换的。但是,现有的面向构件编程技术要求用户自行定义构件的非自描述接口,使得用户程序的开发依然繁复。
CAR(Component Assembly Runtime)构件技术是一种面向构件编程技术,它定义了一套网络编程时代的构件编程模型和编程规范,它规定了一组构件间相互调用的标准,使得二进制构件能够自描述,能够在运行时动态链接。CAR与微软的COM相比,删除了COM中过时的约定,禁止用户定义COM的非自描述接口;完备了构件及其接口的自描述功能,并且对用户界面进行了简化包装,易学易用。在一种嵌入式操作系统上,CAR构件技术由CAR语言(构件描述语言,描述构件的元数据信息)、CAR编译器、自动代码生成工具以及CAR构件基础库支持。CAR构件技术体现了网络编程时代的特性,编程界面简单。
在嵌入式操作系统软件开发工具包(SDK)的支持下,CAR技术使得原来高深难懂的构件编程技术很容易被C/C++程序员理解并掌握。用户只需要用脚本语言设计相关类和接口的关系以及其属性,并存为.car文件,通过CAR编译器的处理就可以自动生成相关的代码。
单例模式(Singleton模式)属于设计模式中的一种方法,它的目的是保证一个类仅有一个实例,并提供一个能够访问它的全局访问点。这种模式在实际开发中的应用也非常多,例如在嵌入式图形系统应用中,由于内存资源比较宝贵,很多模块都必须保证在整个过程中只能有一个实例。在实际编程设计中,如果要运用该模式,用户可以设计一个Singleton的基类,然后在其子类实例中进行各种扩展来配置应用,也可以直接设计一个不允许继承的Singleton类,里面包含了相关功能。无论采取哪种方法,用户都必须自行实现Singleton模式的相关代码。如果能找到一种在CAR构件编程中自动生成singleton模式代码的方法,将大大降低用户编程的复杂程度。
考虑一个银行帐户管理系统,其中有上百万个客户帐号,要访问每一个客户的帐号,需要对客户的权限进行验证。在这样的系统里,可以设计一个Singleton类叫做AccountManager,用来管理系统中的客户帐户,此类的实例在整个系统中只有一个,如果用C#实现的话,在多线程环境下,可以采用以下方法:
public sealed class AccountManager
{
这个类的其他功能实现代码......
以下是singleton模式的实现代码:
static AccountManager instance_=null;
static readonly object lock_=new Object();
private AccountManager()
{
}
public static AccountManager Instance
{
get
{
lock(lock_)
{
if(instance_==null)
{
instance_=new AccountManager();
}
return instance_;
}
}
}
}
以上是Singleton模式在C#中的基本实现,AccountManager类被设计成不可继承,当其静态成员变量instance_为空时才在堆中进行类的初始化,其他情况下直接返回该类的实例instance_的值,这样就保证了系统内始终只有一个该类的实例,整个过程通过加锁保证多线程下的一致性。可以看到,不同的用户和不同的语言对于Singleton模式的实现可能不相同,但是都必须自己进行Singleton类的编写,这样用户在实现类的功能的同时,还必须花精力去实现singleton模式的相关代码,这给用户的编程带来一定的复杂度。
具体实施方式
下面结合附图对本发明作进一步详细的说明。
按照本发明提供的方法,在CAR构件编程中,singleton模式的实现并非像上述java或者.Net通过继承一个singleton基类来获得相应属性,而是直接声明为全局变量并进行初始化,同时对其进行多线程同步管理,保证此全局变量只有一个实例。
当用户在.car文件中指定一个类为singleton属性并编译此.car文件后,CAR编译器会自动生成相关的处理代码,其处理过程如下:首先会声明几个相关的全局变量,除了有对应此对象的全局指针之外,还有一个状态值代表此对象当前的状态,以及一个全局锁进行同步。状态值分为三种:_SingletonObjState_Uninitialize,代表此对象尚未初始化;_SingletonObjState_Initializing,代表此对象正在进行初始化;_SingletonObjState_Initialized,代表此对象之前已初始化。
用户在判断以上三种状态值并进行相应的处理之前,首先会执行加锁的动作,因为状态值为全局变量,对其进行设置時必须要加锁以保证多线程下不会出现读写冲突。然后用户会根据当前不同的状态值进入相应的程序分支,如图1所示。
当对象状态为_SingletonObjState_Uninitialize時,表明对象还没有被别的线程初始化过,此时将进行初始化的工作,其过程为首先将对象的全局状态值设为_SingletonObjState_Initializing,表明当前对象正在进行初始化,由于进入分支之前已经加锁,所以当此状态值设置完毕后可以进行解锁,让其他线程也可以读写此状态值。接着在堆中分配一个新的car构件对象,并将此对象的引用计数加1(这个过程中如果分配失败,就会重新将对象状态设为_SingletonObjState_Uninitialize并返回,让其他线程进行初始化),然后调用该对象的_InitializeComponent()函数进行一些设置(这个过程如果失败,同样也会将对象状态设为_SingletonObjState_Uninitialize并返回),最后在返回前才将对象的状态值设为_SingletonObjState_Initialized,表明此构件对象已经被当前线程初始化完毕。上述过程如图2所示。
当对象状态为_SingletonObjState_Initializing時,则表明其他线程正在对此构件对象进行初始化,由于进入分支前已经加锁,此时可以解锁并切换至其他线程继续进行处理,然后重新加锁判断全局状态值,如果状态值为_SingletonObjState_Uninitialize,则表示其他线程可能初始化失败,此时解锁并返回过程开始处由本线程重新进行初始化;如果状态值仍然是_SingletonObjState_Initializing,则表明其他线程还没有把对象初始化完毕,此时应该返回到本分支开始处重新开始解锁以及唤醒其他线程再加锁判断状态值这些步骤,直到检测到对象状态值为_SingletonObjState_Initialized为止,此时退出此分支进入_SingletonObjState_Initialized分支。上述过程如图3所示。
当对象状态为_SingletonObjState_Initialized時,表明此时对象的实例已经被其他线程初始化过了,此时直接将对象的引用计数加1,并返回全局对象的相应值,最后解锁并返回。上述流程如图4所示。
下面通过一个实施例来说明本发明的技术效果。仍以前述银行帐户管理系统作为例子,对于AccountManager类,可以在.car文件中这样指定:
module
{
interface IAccountManager{
enter();
}
[singleton]//指定该类为singleton类
class CAccountManager{
interface IAccountManager;
}
}
CAR语言中的module描述表示一个构件模块,让带有singleton属性的CAccountManager类实现了IAccountManager接口,为了简单起见,IAccountManager接口中只包含了一个enter()方法。
使用CAR的编译器以及CAR构件的自动代码生成工具对该car文件进行处理就会生成构件的实现,CAR工具会生成CAccountManager接口函数的空的实现,而用户只需关心自己定义的接口函数的实现,只要填入自己的代码就可以了。
下面是生成的cpp文件和h文件:
CAccountManager.cpp:
#include<stdio.h>
#include″CAccountManager.h″
#include″_CAccountManager.cpp″
ECODE CAccountManager::enter()
{
m_cCount++;
printf(″CAccountManager enter %d times.\n″,m_cCount);
return NOERROR;
}
其中,CAccountManager.cpp是CAR工具生成的有关构件对象底层实现的代码。可以看到,在enter方法中,设立了一个成员变量m_cCount做为计数,当调用该方法时就将它的值打印出来,可以通过它可以判断该类是否在全局中只有一个实例。
CAccountManager.h:
#ifndef__CACCOUNTMANAGER_H__
#define__CACCOUNTMANAGER_H__
#include″_CAccountManager.h″
class CAccountManager:public_CAccountManager
{
public:
CARAPI enter();
CAccountManager():m_cCount(0){}
~CAccountManager(){puts(″CAccountManager dtor.″);}
private:
int m_cCount;
};
可以看到成员变量m_cCount在构造函数初始化时会设置成0。
以下是客户端调用此构件方法的一个过程:
#include<stdio.h>
#define_SMARTCLASS
$using AccountManager.dll;//引用AccountManager.dll构件
int main()
{
ECODE ec;
CAccountManagerRef s1,s2;
//初始化两个AccountManager类的实例
ec=s1.ObjInstantiate();
if(FAILED(ec))goto ErrorExit;
ec=s2.ObjInstantiate();
if(FAILED(ec))goto ErrorExit;
//分别调用两个AccountManager类的enter方法
for(int n=0;n<3;n++){
s1.enter();
s2.enter();
}
return 0;
ErrorExit:
printf(″Error,ec=%x\n″,ec);
return 1;
}
以下是运行的结果:
CAccountManager enter 1 times.
CAccountManager enter 2 times.
CAccountManager enter 3 times.
CAccountManager enter 4 times.
CAccountManager enter 5 times.
CAccountManager enter 6 times.
CAccountManager dtor.
可以看到,虽然调用的是不同的对象的enter方法,但是最终调用的都是同一方法,而且最后调用该类析构函数时只调用了一次。
比较一下当没有为AccountManager对象指定singleton属性时的运行结果:
CAccountManager enter 1 times.
CAccountManager enter 1 times.
CAccountManager enter 1 times.
CAccountManager enter 2 times.
CAccountManager enter 1 times.
CAccountManager enter 2 times.
CAccountManager enter 2 times.
CAccountManager enter 3 times.
CAccountManager enter 2 times.
CAccountManager enter 3 times.
CAccountManager enter 3 times.
CAccountManager dtor.
CAccountManager enter 3times.
CAccountManager dtor.
可以看到,没有指定singleton属性时是分别调用了两个不同对象实例的enter方法,并且最后析构时调用了两次。这个结果是符合预期的。
同时也可看到,在具体的CAccountManager类的实现中,并没有包含任何singleton模式的管理代码,用户只需要指定CAccountManager类为singleton属性并重新编译后,相同的实现代码立即有了不同的运行结果。用户通过这种方式,可以很方便的用singleton模式去解决其他工程问题。