`
qiezi
  • 浏览: 497722 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

    博客分类:
  • c++
阅读更多

一、问题。

这段时间考虑实现一个纯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<xproxy>
{
    
//
};


所以我们的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目前也不能跨编译器。

三、实现。

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

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

分享到:
评论

相关推荐

    ENVI的IDL二次开发教程

    1. **iTools界面:** iTools是IDL提供的一个界面设计工具,用于创建交互式的用户界面。 2. **界面创建:** - **单元组件界面:** 由多个单元组件组成的界面,如按钮、文本框等。 - **复合界面:** 由多个子界面...

    windows下vs2010编译的thrift,包含lib和cpp源码

    在C++中,服务端通常会包含一个处理循环,监听特定端口,接收并处理来自客户端的消息。 4. **客户端**:客户端代码负责与服务端通信,创建服务代理对象,调用远程方法。Thrift提供了高效的二进制协议和传输机制,...

    cpp-CapnProto序列化RPC系统

    Cap'n Proto是一个高效、快速的序列化和远程过程调用(RPC)框架,主要由C++编写,但也支持其他语言。这个框架的设计目标是提供比现有的序列化库(如Protocol Buffers、Thrift等)更快的速度和更小的内存占用。在...

    vc 使用corba 开发学习手册

    1. 编写IDL文件,例如创建一个名为simple.idl的文件,定义接口Simple和SimpleFactory,接口中包含了具体的业务方法。 2. 使用idl命令编译IDL文件,这会生成一系列的C++源文件,包括服务器端和服务代理的实现。 3. ...

    从头写一个OPC客户端

    在Visual C++ 6.0中,使用"File" -&gt; "New..."命令创建一个"Win32 Console"类型的项目,并将其命名为"SimpleOPCClient"。接着,我们需在项目中添加三个文件夹: - Source files:存储.cpp源文件。 - Header files:...

    thrift-0.9.0-dev.tar.gz

    Thrift 是一个开源的软件框架,它用于构建跨语言的服务。这个"thrift-0.9.0-dev.tar.gz"文件是一个源代码压缩包,包含了Thrift 0.9.0开发版本的所有组件。Thrift最初由Facebook开发,后来成为Apache软件基金会的一个...

    CORBA 程序设计指南(入门)

    1. 接口定义语言(IDL):这是CORBA的基础,用于定义服务对象的接口,它可以被编译成各种目标语言,如Java或C++,使得不同的实现语言可以共享相同的接口。 2. 对象请求代理(ORB):ORB是CORBA的核心,它负责管理和...

    protobuf 的源码 版本2.5.0

    protobuf-2.5.0是protobuf的一个稳定版本,对于理解和使用protobuf有很重要的参考价值。 1. **protobuf基本概念** - **.proto文件**:protobuf的接口定义语言(IDL),用于描述数据结构,如消息类型、字段、枚举等...

    svd.zip_SVD_zip

    在给定的压缩包文件中,有 `cuda_svd.c`、`cuda_svd.def`、`cuda_svd.dll` 和 `cuda_svd.dlm` 这些文件,它们很可能包含了一个使用 NVIDIA CUDA 技术实现的奇异值分解的 GPU 加速版本。CUDA 是一种编程模型,允许...

    Python库 | pycapnp-0.5.0.tar.gz

    其API设计简洁,易于理解,同时也保持了与Cap'n Proto的C++实现的兼容性。 在Python中使用pycapnp,首先需要安装这个库。通常,这可以通过Python的包管理器pip完成,命令可能是`pip install pycapnp-0.5.0`。一旦...

    用VC进行COM编程所必须掌握的理论知识.doc

    每个对象都必须实现至少一个接口,通常是`IUnknown`接口。 - **GUID**:全局唯一标识符用于唯一标识COM组件、接口等。 - **类型库**:类型库是一个包含了所有接口定义、常量和其他类型信息的文件,它有助于客户端...

    编程人员必备【英语单词】

    抽象基类是指包含一个或多个纯虚函数的类,不能被实例化,主要用于定义子类必须实现的接口。抽象基类是实现多态性的基础,使得子类可以根据具体需求重写父类的方法。 #### identifier 标识符 在编程中,标识符是...

    hostrpc:共享地址空间中的RPC

    标题 "hostrpc:共享地址空间中的RPC" 指涉的是一个利用C++实现的远程过程调用(Remote Procedure Call, RPC)框架,它在同一个进程或共享内存区域内的不同线程间进行通信,而不是跨越网络。这种设计提高了效率,减少...

    ArcGIS开发平台框架

    - ArcGIS Engine是一个软件开发包(SDK),它允许开发者将GIS功能嵌入到自定义的应用程序中,支持.NET、Java、C++等编程语言,并提供了成千上万的类、接口和方法。 - ArcGIS Server为开发者提供了搭建Web GIS服务的...

    一个简单但可调试的COM骨架代码

    例如,你可能会在接口中声明一个方法`DoSomething()`,然后在实现中提供具体的功能。在IDL文件中,我们还会定义组件的类ID(CLSID)和接口ID(IID),这些都是COM组件的重要标识符。 接下来,我们需要实现COM服务器...

    调试ds所需文件,两个头文件和两个库文件

    `wmstub.lib`可能是Windows Media Services的一个静态库,其中包含了某些功能的预编译代码,它在链接时会被整合到最终的可执行文件中。而`WMVCORE.lib`很可能是核心组件的库,包含了处理媒体编码、解码、流处理等...

    opendds pdf

    综上所述,OpenDDS作为一个强大的实时数据分发平台,不仅提供了丰富的功能特性,还支持高度的可配置性和灵活性,能够满足各种复杂应用场景的需求。无论是对于实时系统的开发还是研究,OpenDDS都是一个值得深入探索的...

    CORBAR-COM-EJB三种组件模型的分析与比较.pdf

    由于提供的【部分内容】信息量为零,没有具体的文本内容可以分析,以下是基于标题、描述和标签提供的知识点。 CORBA(Common Object Request Broker Architecture),即通用对象请求代理体系结构,是一种由OMG...

    THRIFT 学习资料

    THRIFT 提供了一个编译器,它可以将 `.thrift` 文件转换为不同目标语言的代码,如 C++, Java, Python, Go, PHP 等。编译后的代码包含了服务客户端和服务器端的实现,以及序列化和反序列化的逻辑。 3. **传输协议**...

Global site tag (gtag.js) - Google Analytics