Chromium 回调函数一

Chromium 基础库提供了 base::Bind 机制,可以将全局函数和成员函数,跟它的调用上下文绑定在一起,构成一个回调函数对象。这个回调函数对象可以被传递,被保存,被当做消息发送到线程的消息循环里面,最后我们可以通过这个回调函数对象的 Run 方法调用跟它关联的函数。

一、绑定全局函数

Chromium 提供了 base::Bind 和模版类型 base::Callback 对函数回调提供了支持,下面是一个简单的使用例程,将一个全局函数绑定到一个 Callback 对象,并通过 Callback.Run 调用这个函数:

int Return5() { return 5; }
base::Callback<int(void)> func_cb = base::Bind(&Return5);
LOG(INFO) << func_cb.Run(); // Prints 5.

二、绑定成员函数

如果要绑定一个类的成员函数,我们需要为 Bind 方法提供这个类的一个实例对象,把它跟 Callback 对象绑定,为了保证这个对象在 Callback 对象被执行时仍然存活,或者 Callback 对象能够知道这个对象已经被销毁,我们需要提供一个 scoped_refptr 或者 WeakPtr,通过 base::Unretained(ptr) 用 raw pointer 也可以,不过后果自负... 早期 Chromium 的代码使用 scoped_refptr 比较多,现在 Chromium 更倾向于使用 WeakPtr,当然使用 WeakPtr 时我们要注意这个 Callback 只能在 WeakPtr 所属的线程中被调用,因为它是非线程安全的,下面是一个使用 scoped_refptr 的例子:

class Ref: public base::RefCountedThreadSafe < Ref > {
public: 
    int Foo() {
        return 3;
    }
    void PrintBye() {
        LOG(INFO) << "bye.";
    }
};
scoped_refptr < Ref > ref = new Ref();
base::Callback < void(void) > ref_cb = base::Bind(&Ref::Foo, ref);
LOG(INFO) << ref_cb.Run(); // Prints out 3.

三、函数实参的位置

如果绑定的函数需要参数,我们可以事先绑定所有参数对象到 Callback 里面,也可以事先不绑定参数,甚至可以事先只绑定一部分参数,事先绑定所有参数的 Callback 在 Chromium 里面称为闭包 Closure:

void MyFunc(int i, const std::string& str) {}
base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc); 
cb.Run(123, "hello, world"); 

base::Callback<void(void)> cb = base::Bind(&MyFunc, 123, "hello world");
cb.Run();

base::Closure cb = base::Bind(&MyClass::MyFunc, this, 123, "hello world");
cb.Run(123, "hello, world"); 

四、函数参数对象的所有权

如果想让 Callback 对象拥有跟它绑定的类对象或者参数对象,也可以使用 base::Owned 或者 base::Passed 方法,分别针对 raw pointer 和 scoped_ptr,如果是 scoped_ptr 类型参数的话,在调用时 Callback 就会将这个参数对象的所有权转移给被回调的函数,最后 Callback 对象被销毁时会自动销毁绑定的类对象和参数对象(如果还拥有这个参数对象的话):

MyClass* myclass = new MyClass;
base::Bind(&MyClass::Foo, base::Owned(myclass));

void TakesOwnership(scoped_ptr<Foo> arg) {}
scoped_ptr<Foo> f(new Foo);
// f becomes null during the following call.
base::Closure cb = base::Bind(&TakesOwnership, base::Passed(&f));

总而言之,在使用 Chromium 的回调函数机制时,一定要非常清楚跟 Callback 对象绑定的类对象和参数对象的所有权和生命周期,避免在 Callback 被调用时,访问到已经被销毁的对象。