在开发和维护的过程中,很多时候会涉及一个问题,就是如何实现派生类的动态加载。由于C++本身不支持动态类的加载机制,需要通过配置文件描述服务信息并采用动态库来加载。因此在ACE中专门设计了service config框架来解决这样的问题。
这篇文章主要讲述了在ACE中如果通过宏定义的方式实现服务的动态加载与配置。
在开发和维护的过程中,很多时候会涉及一个问题,就是如何实现派生类的动态加载。由于C++本身不支持动态类的加载机制,需要通过其他的方式来进行支持。因此在ACE中专门设计了service config框架来解决这样的问题。
service config框架,通过修改配置文件,能够实现对派生于ACE_Service_Object的服务对象的加载,在service config中根据加载服务代码方式的不同可以分为:
静态服务
是被静态链接至应用程序的服务
动态服务
是从一个和多个共享库中链接的服务
由于本文讨论的重点时动态类的加载机制,因此主要讨论service config中有关动态服务的实现,其他的实现内容,大家可以参考 C++NPV2中的第5章。
在这里主要是从原理方面,简单分析service config框架的实现。
在分析service config框架之前,需要明确service config需要实现的功能:
1.我们写的新的服务需要在运行时(不是在编译时)能够被service config框架识别以及调用。
2.其次是服务对象的生命周期由service config框架进行管理。
3.通过配置文件,来获知服务对象加载信息。
对于前面提到的两点功能,只需要我们写的服务类继承ACE_Service_Object ,实现这个类所提供的init(),fini(),suspend(),resume(),info()对服务管理的方法。由于ACE_Service_Object继承了ACE_Event_Handler,因此可以实现相关服务处理的方法。
对与第三点来说,如果是用Java语言来实现service config框架,那也很容易,可以通过动态类型识别(RTTI),在语言运行库中,就提过了描述Class信息的机制,使用著名的Class.forName(),可以获得需要加载类的主要信息。
而对于C++来说,这就比较麻烦了,主要是需要解决有关类信息描述的问题。对于DLL来说,如果直接将对象进行输出的话,由于C++所支持的重载机制,输出的类方法名会结合参数产生变化,如果想通过service config框架,加载DLL输出的类,就必须通过静态编译的方法,获知类的定义。而我们的要求是在程序运行时来,通过配置文件中定义的dll名,以及相关的类名来加载相关的服务类。
通过分析源码,我们可以发现一些有意思的东东。
# define ACE_Local_Service_Export
# define ACE_FACTORY_DEFINE(CLS,SERVICE_CLASS)
void _gobble_##SERVICE_CLASS (void *p) {
ACE_Service_Object *_p = ACE_static_cast (ACE_Service_Object *, p);
ACE_ASSERT (_p != 0);
delete _p; }
extern "C" CLS##_Export ACE_Service_Object *
_make_##SERVICE_CLASS (ACE_Service_Object_Exterminator *gobbler)
{
ACE_TRACE (#SERVICE_CLASS);
if (gobbler != 0)
*gobbler = (ACE_Service_Object_Exterminator) _gobble_##SERVICE_CLASS;
return new SERVICE_CLASS;
}
/// The canonical name for a service factory method
#define ACE_SVC_NAME(SERVICE_CLASS) _make_##SERVICE_CLASS
/// The canonical way to invoke (i.e. construct) a service factory
/// method.
#define ACE_SVC_INVOKE(SERVICE_CLASS) _make_##SERVICE_CLASS (0)
//@}
/** @name Helper macros for services defined in the netsvcs library.
*
* The ACE services defined in netsvcs use this helper macros for
* simplicity.
*
*/
//@{
# define ACE_SVC_FACTORY_DECLARE(X) ACE_FACTORY_DECLARE (ACE_Svc, X)
# define ACE_SVC_FACTORY_DEFINE(X) ACE_FACTORY_DEFINE (ACE_Svc, X)
//@}
ACE_SVC_FACTORY_DECLARE (...)
在这里用...来代替具体的类名
_make_... (ACE_Service_Object_Exterminator *gobbler) 是dll输出的调用接口(注意是C方式进行输出的,如果是C++的话就另当别论了),我们需要在svc.conf中写出输出的调用函数名。
_make... 这个宏精妙之处就在于此,由于C++不支持reflect机制,为了能够让Service_Config类
在不做任何改变的情况下,就能创建相关的ACE_Service_Object 继承类的实例,_make提供了一个
创建类实例的方法。 这也许是Factory模式的另类应用,其实这个方法在某些环境还是很有效,
也可以帮助我们将C++的类对象输出至delphi中,供delphi进行调用。
return new SERVICE_CLASS;
这样就有人会问了,提供new,如何提供delete呢?
大家再看一下_gobble_...这个宏定义,
从 ACE_Service_Object_Exterminator *gobbler 的定义
typedef void (*ACE_Service_Object_Exterminator)(void *);
可知 ACE_Service_Object_Exterminator 是一个函数指针而gobbler定义是指向这个函数指针的指针。
通过给gobbler赋值,就可以给service config框架提供释放的ACE_Service_Object派生类的方法了。
还有为什么在_gobble_...函数中需要ACE_Service_Object *_p = ACE_static_cast (ACE_Service_Object *, p); 来一个强制转换呢?
强制转换是因为 _gobble_##SERVICE_CLASS (void *p)中的p是无类型指针,强制转换成ACE_Service_Object * 类型,delete *p 时,就可以调用p所指向的ACE_Service_Object子类的析构函数。
svc.conf 示例文件
dynamic Timer_Service_3 Service_Object *
./Timer:_make_Timer_Service_3()
"timer $INTERVAL $MAX_TIMEOUTS $TRACE"
里面没有有关_gobble_##SERVICE_CLASS 函数的描述,
这是因为_make_##SERVICE_CLASS 的宏定义中已经完成有关析构函数的注册工作了。