您的位置:首页 > 运维架构

Orocos OperationCaller 解析

2016-06-02 11:10 295 查看
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();
}


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

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

以自己为模板,新建(
new
)一个
LocalOperationCaller
对象,并赋值给 shared_ptr:
shared_ptr cl = this->cloneRT();


保存调用函数的参数 :
cl->store( a1, a2, ... );


将该 shared_ptr 的指针添加到执行该函数的线程类的 Queue 中(该线程会在执行时从Queue 中 pop 函数指针并执行! see link ExecutionEngine 实现

等待该函数被线程调用(时间不确定,因此需要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才能被唤醒。

}


返回结果,并销毁在第一部
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.hpp
evaluate()
调用。

see link: http://www.boost.org/doc/libs/1_63_0/libs/fusion/doc/html/fusion/functional/invocation/functions/invoke.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: