在 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()
调用。