为C++实现一个IDL (零)

阅读更多

一、问题。

这段时间考虑实现一个纯C++的分布式服务包装库,简要描述如下:

有如下类和函数:

struct  Test
{
    
void  test1 ( /* in */   int  v1,  /* in */   int *  v2);
    
int  test2 ( /* in */   int &  v1,  /* out */   int *  v2);
};

int  test_func ( /* in */   int *  v1,  /* inout */   string *  v2);


想把它们作为服务发布出去,以SOAP或其它方式。发布为一个TestService,并让它携带多一些信息:

struct  TestService
{
    
void  test1 ( in < int >  v1,  in < int >  v2);
    
int  test2 ( in < int >  v1,  out < int >  v2);
    
int  test_func ( in < int >  v1, inout < string >  v2);
};


C++有许多工具、库来做到这点,但是,都需要生成一堆代码,很是不爽。

其它语言,比如python, java, c#等,都可以通过自省机制,抛开IDL在语言内实现。

C++并非不能做这个,它只是缺少足够的类型信息。比如上面的例子,如果要发布为服务,那么至少应该把它的参数、返回值搞得明确些,否则要么会造成不必要的参数传递,要么会产生错误(把OUT参数取值可不是安全的)。

比如上面出现的int, int&, int*,在作为in参数时,我们是想传递它的值,类型为int。而int*和string*作为out参数时,我们想让它传递指针或引用,当调用返回时,我们给它赋值。

C++语言的类型极为丰富,却没有描述一个参数到底是in还是out。java也没有,但它可以正常序列化一个null值,在C++中,这可能存在一些麻烦。

再考虑一下char*类型,假如它是in参数,那么它是要传递一个字符还是一个字符串?C++语言没有对它进行描述。

所以要实现一个分布式服务包装(或代理)库,必须让发布者提供这些信息。

我们知道,要查询一个远程服务,必须查询相应主机端口,获取服务信息。最简单的服务信息包括:服务列表,每个服务中的方法列表,方法的类型(包括参数和返回值类型,in/out信息等)。

实际上,我们是要为C++增加一些简单的自省能力。上面那个服务发布接口,实际上离这个要求还有很远,再来看一下:

struct  TestService
{
    
void  test1 ( in < int >  v1,  in < int >  v2);
    
int  test2 ( in < int >  v1,  out < int >  v2);
    
int  test_func ( in < int >  v1, inout < string >  v2);
};


可以想见,它是没有一点自省能力的,我们如何向它查询,它的名字?它的方法列表?方法的类型?它如何与Test类的成员函数以及test_func函数关联?

二、方向。

要让上面那个服务具有自省能力,要做的扩充其实并不多。考虑下面的代码:

struct  TestService :  public  Service
{
    TestService ();
    Method 
< void ( in < int > in < int > ) >  test1;
    Method 
< int ( in < int > out < int > ) >  test2;
    Method 
< int ( in < int > , inout < string > ) test_func;
};


这几个Method可以用自己写的委托类来做。

1、假如我们在TestService的构造函数里给它分配一个“TestService”名字,并且Service类实现了查询名字的接口,那么它就知道它自己的名字了。

2、假如在TestService的构造函数里为各个Method分配名字,并且注册到TestService,那么它就能够查询方法列表。

3、方法的类型?通过模板方式,把各个参数类型收集起来,给个字符串名称就可以了。

使用宏来实现,大概可以写成这样:

BEGIN_SERVICE (TestService)
    METHOD (
void ( in < int > in < int > ), test1,  & Test::test1)
    METHOD (
int ( in < int > out < int > ), test2,  & Test::test2)
    METHOD (
int < in < int > , inout < string > ), test_func, test_func)
END_SERVICE ()


通过上面这几个宏,我们能够生成TestService声明。

不过,有几个问题,罗列如下,并一一解决它:

1、如何把函数指针传给它?如何把方法名称传给它?
这个只是C++语言为我们增加了一些麻烦,我们无法在定义成员的地方调用它的构造函数,不过这并不会造成多大障碍。
上面的METHOD宏如果只是生成类的声明,那么函数指针可以省略。我把它加上的原因是,它可以被我用Ctrl+C, Ctrl+V这种世界上最先进的技术原样拷贝下来,并且通过简单修改的方法实现这种世界上最先进的重用。

上面的代码经过修改,结果就成这样:

BEGIN_SERVICE (TestService)
    METHOD (
void ( in < int > in < int > ), test1,  & Test::test1)
    METHOD (
int ( in < int > out < int > ), test2,  & Test::test2)
    METHOD (
int < in < int > , inout < string > ), test_func, test_func)

    BEGIN_DEFINE (TestService)
        METHOD_DEFINE (
void ( in < int > in < int > ), test1,  & Test::test1)
        METHOD_DEFINE(
int ( in < int > out < int > ), test2,  & Test::test2)
        METHOD_DEFINE(
int( in < int > , inout < string > ), test_func, test_func)
    END_DEFINE ()

END_SERVICE ()


看上去对应得非常整齐,修改起来也比较简单。上面那部分被扩充为如下代码:

struct  TestService :  public  Service
{
    Method 
< void ( in < int > in < int > ) >  test1;
    Method 
< int ( in < int > out < int > ) >  test2;
    Method 
< int ( in < int > , inout < string > ) test_func;
    TestService ()
    : Service (
" TestService " )
    {
        test1.setName (
" test1 " );
        test1.setMethod (
& Test::test1);
        
this -> registerMethod ( & test1);
        test2.setName (
" test2 " );
        test2.setMethod (
& Test::test2);
        
this -> registerMethod ( & test2);
        test_func.setName (
" test_func " );
        test_func.setMethod (test_func);
        
this -> registerMethod ( & test3);
    }
};


基本上需要的东西都在这里了。

2、客户端的问题。

上面这种映射,直接拿到客户端会有问题,Test类和test_func函数我们并不打算交给客户端,所以使用函数指针会出现链接错误。

实际上客户端不需要这个,我们想办法把它拿掉就行了。客户端实际需要生成的代码如下:

struct  TestService :  public  Service
{
    Method 
< void ( in < int > in < int > ) >  test1;
    Method 
< int ( in < int > out < int > ) >  test2;
    Method 
< int ( in < int > , inout < string > ) test_func;
    TestService ()
    : Service (
" TestService " )
    {
        test1.setName (
" test1 " );
        
this -> registerMethod ( & test1);
        test2.setName (
" test2 " );
        
this -> registerMethod ( & test2);
        test_func.setName (
" test_func " );
        
this -> registerMethod ( & test3);
    }
};


还是上面提到的,C++给我们带来的麻烦。这次需要另一组宏来完成它:

BEGIN_SERVICE_D (TestService)
    METHOD_D (
void ( in < int > in < int > ), test1)
    METHOD_D (
int ( in < int > out < int > ), test2)
    METHOD_D (
int ( in < int > , inout < string > ), test_func)

    BEGIN_DEFINE_D (TestService)
        METHOD_DEFINE_D (
void ( in < int > in < int > ), test1)
        METHOD_DEFINE_D(
int ( in < int > out < int > ), test2)
        METHOD_DEFINE_D(
int ( in < int > , inout < string > ), test_func)
    END_DEFINE_D ()

END_SERVICE_D ()


METHOD*和METHOD_DEFINE*宏的参数都有一些多余的信息,没有去掉是因为放在一起容易看到写错的地方。(这个技巧来源于前几天看的一篇BLOG,很报歉没有记下地址)

3、使用的问题。

如何才能比较方便地使用?我考虑了下面这种方式:

template  < class  T >
struct  IProxy;

template 
< class  T >
struct  SOAPProxy;

SOAPProxy 
< TestService >  service;
service.connect (
5000 " localhost " );
int  a = 0 ;
int   * =   & a;
service.test1 (
3 , n);
service.test1 (
3 * n);
service.test2 (
3 , n);
service.test2 (
3 * n);
service.test2 (
3 , NONE);
//


Method::operator ()的各个参数都将可以接受相容的类型,像上面一样,因为在TestService中我们已经定义了它要传输的值的类型。

a.NONE是什么?其实是为异步调用考虑的。假如指定某个OUT参数为NONE,则这个参数的值并不真正的OUT,而是保存在Method中。实际上Method中保存每个参数的值。

b.Method与Service如何发生关系?
从TestService的定义中我们知道,Method向Service注册自己以实现自省,但它同时也会保存Service的指向。
我们的Proxy实际上是一个继承模板,上面并没有把它指出来。它的定义是:

template  < class  T >
class  XProxy :  public  T
{
    
//
};


所以我们的TestService其实也是模板类,它将使用XProxy中定义的序列化类。XProxy将实现Service基类中序列化虚函数以及调用虚函数。

当一个Method调用时,它会调用Service的序列化,由于被重写了,所以调用的是XProxy中的序列化方法。这个方法会把这个Method的各in/inout参数序列化,然后执行远程调用,再把调用结果反序列化给inout/out参数。

4、其它想法。

在考虑上面的定义方式时,我也考虑了其它方式,主要是返回值处理的方法,简述如下。

前面我们假设了一段将被开放为远程服务的代码:

struct  Test
{
    
void  test1 ( /* in */   int  v1,  /* in */   int *  v2);
    
int  test2 ( /* in */   int &  v1,  /* out */   int *  v2);
};

int  test_func ( /* in */   int *  v1,  /* inout */   string *  v2);


在前面的做法中,我们的服务描述是放在那一组宏里面,好处是不用改这段代码,坏处就是代码定义的地方和描述不在一起,协调可能会有一些不便。

我也考虑了另一种做法:

struct  Test
{
    idl 
< void ( in < int > in < int > ) >  test1 ( int  v1,  int *  v2);
    idl 
< int ( in < int > out < int > ) >  test2 ( int &  v1,  int *  v2);
};

idl 
< int ( in < int > , inout < string > ) test_func  int *  v1,  string *  v2);


对于实现代码,只需要修改返回值为void的函数,把return;修改为return VOID;,并且为没有写此语句的分支加上此句。

VOID是一个特殊类型的静态变量,专为void返回值的函数设定。

这种做法修改了原有的代码,不过在定义服务时可以节省一些工作:

BEGIN_SERVICE (TestService)
    METHOD (test1, 
& Test::test1)
    METHOD (test2, 
& Test::test2)
    METHOD (test_func, test_func)

    BEGIN_DEFINE (TestService)
        METHOD_DEFINE (test1, 
& Test::test1)
        METHOD_DEFINE (test2, 
& Test::test2)
        METHOD_DEFINE (test_func, test_func)
    END_DEFINE ()

END_SERVICE ()


它所需要的函数类型,将由函数指针推导。

在G++编译器下,可以使用typeof来获得函数指针的类型而不需要真得获得函数指针值,不过目前仅仅在G++下可用。(顺便说一下,typeof已经列入c++0x)

最终我放弃了这个想法,毕竟它要修改现有的代码,某些情况下这是不可能的,而且typeof目前也不能跨编译器。

三、实现。

老实说我现在还没有一份完整的或半完整的实现,大部分想法还在头脑中,测试代码倒是写了不少,主要是用来测试上述想法能否实现,我想大部分情况都已经测试了,只需要有时间来把它实现出来。

这是我近期要做的事之一,争取月内把它做完罢。

你可能感兴趣的:(C,C++,C#,D语言,Python)