Orocos OperationCaller 解析

15 篇文章 1 订阅
12 篇文章 5 订阅

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值