Orocos OperationCaller 解析

来源:互联网 发布:淘宝刷皇冠多少钱 编辑:程序博客网 时间:2024/06/18 18:38

Orocos 中 OperatoinCaller 表示一个可以调用其他模块的函数的对象, 如果一个模块添加了一个 Operation,则该 Operation 表示的函数可以被其他模块的 OperationCaller 调用。
在连接 ( connectService ) 调用端(OperationCaller)和被调用端(Operation)之后,该 OperatoinCaller 就能够执行其他模块对应的函数。这里涉及到两个对象,ServiceRequester 和 Serivce,前者 拥有一个 OperatoinCaller 的 map, 后者拥有一个 Operation 的 map。

另外,一个 OperatoinCaller 可以在本模块的 ExecutionEngine 中执行,也可以在被调端的 ExecutionEngine 中执行。

其中一个关键的类为 OperationCallerBase,因为他分别被 Operation 和 OperationCaller 继承,充当两边的接口类,并且在两边都有实现,该类可以分成两个部分 :

template<class F>  // 这个F是函数原型,例如: bool(int)struct OperationCallerBase    : public internal::InvokerBase<F>,      public OperationCallerInterface{    typedef boost::shared_ptr<OperationCallerBase<F> > shared_ptr;    virtual ~OperationCallerBase() {}    virtual OperationCallerBase<F>* cloneI(ExecutionEngine* caller) const = 0;};

第一个部分:其中子类 OperationCallerInterface 中有两个指针,分别保存着自己的和另外模块的 ExecutionEngine:

struct RTT_API OperationCallerInterface: public DisposableInterface{    ...protected:    ExecutionEngine* myengine;    ExecutionEngine* caller;}

第二个部分:子类 InvokerBase 则保存着被调用的函数原型(这样在匹配 Operation 和 OperationCaller 类的时候可以知道函数签名是否相同——dynamic_cast,并且可以定义统一的调用形式——call() 函数):

template<int, class F>struct InvokerBaseImpl;/** * This is the base class that defines the interface * of all invocable method implementations. * Any invocable method implementation must inherit * from this class such that it can be used transparantly * by the OperationCaller, Operation and SendHandle containers. */template<class F>struct InvokerBase    : public InvokerBaseImpl<boost::function_traits<F>::arity, F>{};template<class F>struct InvokerBaseImpl<0,F>{    typedef typename boost::function_traits<F>::result_type result_type;    typedef typename boost::function_traits<F>::result_type result_reference;    virtual ~InvokerBaseImpl() {}    virtual SendHandle<F> send() = 0;    virtual result_type call() = 0;};template<class F>struct InvokerBaseImpl<1,F>{    typedef typename boost::function_traits<F>::result_type result_type;    typedef typename boost::function<F>::arg1_type arg1_type;    virtual ~InvokerBaseImpl() {}    virtual result_type call(arg1_type a1) = 0;    virtual SendHandle<F> send(arg1_type a1) = 0;};...

关于如何关联 Operation 和 OperationCaller:

Operation(被调用端) 和 OperationCaller(调用端) 之间共享一个智能指针(internal::LocalOperationCaller<Signature>::shared_ptr impl):

// 在 Operation 端:impl = boost::make_shared<internal::LocalOperationCaller<Signature> >( boost::function<Signature>(), this->mowner, null_caller, ClientThread)// 在 OperationCaller 端:boost::dynamic_pointer_cast< base::OperationCallerBase<Signature> >(impl)// 上面这个地方是关键,如果Operation端和OperationCaller端定义的函数签名不同,dynamic_pointer_cast会得到一个空指针,并报告函数签名不同。

该智能指针指向一个可调用的对象 ( LocalOperationCaller,该对象中有个成员变量 boost::function ),这个 LocalOperationCaller 类才是orocos所有底层的调用的实现类。

如何被调用:

写到这里,到了选择如何调用这个 boost::function 的时候了。有两个选择,如果是在调用端(Caller)执行,则只需要直接调用即可;但是如果在被调用端(Operation)的线程中执行,则需要保存参数,保存函数指针,等到线程被执行时再进行函数的调用。简要如下:

class LocalOperationCaller    ...LocalOperationCallerImpl        ...template<class T1>result_type call_impl(T1 a1){    SendHandle<Signature> h;    if ( this->isSend() ) {  // true 表示在Operation的线程中执行, false表示在 Caller 的线程中执行    // 如果在 Operation 的线程中执行,则需要保存参数,将该函数添加到 ExecutionEngine 的函数指针 Queue 中, 然后到了 ExecutionEngine 的执行点再执行    // 见下述 RStore 和 BindStorage 类          h = send_impl<T1>(a1);         if ( h.collect() == SendSuccess )            return h.ret(a1);        else            throw SendFailure;    } else{    // 如果在 Caller 的线程中执行就简单了,直接调用即可.        if ( this->mmeth )            return this->mmeth(a1);        else            return NA<result_type>::na();    }    return NA<result_type>::na();}

如果是在被调用端的线程中执行,则需要处理参数,返回值,添加额外信息以查询是否被执行过,是否有错误发生等等。

调用的过程大概是这样子的:

  1. 以自己为模板,新建(new)一个 LocalOperationCaller 对象,并赋值给 shared_ptr: shared_ptr cl = this->cloneRT();
  2. 保存调用函数的参数 : cl->store( a1, a2, ... );
  3. 将该 shared_ptr 的指针添加到执行该函数的线程类的 Queue 中(该线程会在执行时从Queue 中 pop 函数指针并执行! see link ExecutionEngine 实现)
  4. 等待该函数被线程调用(时间不确定,因此需要wait,通过信号量和 Mutex 来实现互锁等待 (see link) 其效果就是调用端的线程被挂起,等待): this->caller->waitForMessages(boost::bind(&RStoreType::isExecuted,boost::ref(this->retv)) );
    其中 waitForMessages 的函数可简要表示如下:

    {
    if ( pred() ) // pred 即第四步中 boost::bind 返回的可调用对象
    return;
    // only to be called from the thread not executing step().
    os::MutexLock lock(msg_lock);
    while (!pred()) { // the mutex guards that processMessages can not run between !pred and the wait().
    msg_cond.wait(msg_lock); // unlock & 阻塞,并且只有 pred() 返回true才能被唤醒。
    }
  5. 返回结果,并销毁在第一部new出来的对象。

调用的返回值被封装成 RStore 对象(这样就可以判断该函数是否已被执行过,是否在调用的时候出错——try catch…),见下述 RStore 类和 BindStorage 类(其中 BindStorage 类拥有一个 RStore 类的成员变量 retv):

// 返回值对象template<>struct RStore<void> {    bool executed;  // 判断对应的函数是否已被调用过    bool error;     //判断在调用过程中是否出错    RStore() : executed(false), error(false) {}    void checkError() const {      if(error) throw std::runtime_error("Unable to complete the operation call. The called operation has thrown an exception");    }    bool isError() const {      return error;    }    bool isExecuted() const {  // 就是第四部中 waitForMessages 中调用的函数,判断是否已经被执行过了        return executed;    }    template<class F>    void exec(F f) {        error = false;        try{            f();  // 最终调用函数的地方        } catch (std::exception& e) {            log(Error) << "Exception raised while executing an operation : "  << e.what() << endlog();            error = true;        } catch (...) {            log(Error) << "Unknown exception raised while executing an operation." << endlog();            error = true;        }        executed = true;    }    void result() { checkError(); return; }};// ...其他类型的返回值对象的定义 继承自 RStore<void> ... 如://template<class T>//    struct RStore : public RStore<void> {//      T arg;  // 实际返回值, 调用过程为 arg = f();//      ...  //}template<class ToBind>struct BindStorage  // 就是这个类,保存着调用的实体    : public BindStorageImpl<boost::function_traits<ToBind>::arity, ToBind>{};template<class ToBind>struct BindStorageImpl<1, ToBind>{    typedef typename boost::function_traits<ToBind>::result_type result_type;    typedef typename boost::function_traits<ToBind>::arg1_type   arg1_type;    typedef RStore<result_type> RStoreType;  // RStore 类    // stores the original function pointer, supplied by the user.    boost::function<ToBind>  mmeth;  // 被调用的函数对象    // Store the argument.    mutable AStore<arg1_type> a1;  // 参数对象 1,参数也需要保存,因为有些函数是传递引用的,参数也可能会被修改。    mutable RStore<result_type> retv;  // 返回值对象    // the list of all our storage.    bf::vector< RStore<result_type>&, AStore<arg1_type>& > vStore;#ifdef ORO_SIGNALLING_OPERATIONS    typename Signal<ToBind>::shared_ptr msig;#endif    BindStorageImpl() : vStore(retv,a1) {}    BindStorageImpl(const BindStorageImpl& orig) : mmeth(orig.mmeth), vStore(retv,a1)#ifdef ORO_SIGNALLING_OPERATIONS                                                 , msig(orig.msig)#endif    {}    void store(arg1_type t1) { a1(t1); }  // 保存函数的各个参数    void exec() {#ifdef ORO_SIGNALLING_OPERATIONS        if (msig) (*msig)(a1.get());#endif        if (mmeth)            retv.exec( boost::bind(mmeth, boost::ref(a1.get()) ) );  // 调用函数        else            retv.executed = true;    }// ...234567个参数的定义形式...

总结:

总结下来就是将所有的东西都对象化;参数,返回值、函数等都是一个个对象

在没有 connectService 之前,OperationCaller 为空,在 connectService 之后会调用setImplementation(获得一个智能指针),并创建一个新的 OperationCaller, 然后让自身等于这个新的 OperationCaller (*this=tmp), 现在这个新的 OperationCaller 代表着一个可以调用其他模块函数的对象:

OperationCaller& operator=(boost::shared_ptr<base::DisposableInterface> implementation)  // 关键!该智能指针指向 Operation 的成员: LocalOperationCaller, 此处关联 Operation 和 OperationCalle 两个对象{    if (this->impl && this->impl == implementation)        return *this;    OperationCaller<Signature> tmp(implementation, mcaller);    *this = tmp;  // 更新自己为带 impl     return *this;}

而调用这个 OperationCaller 会执行如下过程:

result_type OperationCaller::operator()(){    // impl为一个智能指针:boost::shared_ptr< base::OperationCallerBase<SignatureT>    // 该智能指针在 setImplementation 之后非空,否则为空    if (impl)        return impl->call();  // 所以最后又回到了调用 LocalOperationCaller 的 call 函数,然后判断是否在被调用端的线程中执行,详见 “如何被调用” 段落。 if ( this->mmeth ) return this->mmeth(arg1, arg2 ...); else return NA<result_type>::na();    return NA<result_type>::na();  // 否则返回一个默认的返回值}

Orocos Operation & OperationCaller的继承关系图:

这里写图片描述

综上所述,关键变量为一个 shared_ptr — impl,其指向的是一个 boost::function 的对象的wrapper(LocalOperationCaller), 传递这个 shared_ptr 即传递了这个函数的调用实体,因此 OperationCaller 端的 operator() 调用的是 imp->call() 这个函数,最终完成对具体对象的成员函数的调用。


除了用 OperationCaller 调用 另一个模块的 Operation 之外,也可以直接获取 Service 的 OperationInterfacePart 指针。
rtt/Service.hpp 文件中,addOperation 的时候同时创建 OperationInterfacePartFused 类,该类继承自 OperationInterfacePart 。直接调用 Service::getPart 函数即可获取对应的 OperationInterfacePart——即对应的 Operation。

使用的关键技术: boost::fusion::invoke, 见文件 rtt/rtt/internal/FusedFunctorDataSource.hppevaluate() 调用。

see link: http://www.boost.org/doc/libs/1_63_0/libs/fusion/doc/html/fusion/functional/invocation/functions/invoke.html

1 0
原创粉丝点击