【问题标题】:How to wrap system call with callback parameter into a C++ class?如何将带有回调参数的系统调用包装到 C++ 类中?
【发布时间】:2016-08-13 17:10:23
【问题描述】:

我想将 Linux 系统调用 API(克隆)包装到 C++ 类中。

但是,这个API需要一个函数指针,而且它的参数列表是固定的,例如:

typedef int (*callback)(void *);
void system_call(callback f) {
    void *t = nullptr;
    f(t);
}

现在,我的班级如下:

class Foo {
public:
    void start() {
        // WRONG: cannot pass non-static member function due to pointer `this`
        system_call(this->foo);
    }
private:
    int foo(void *args) {
        f->necessary();
        return 0;
    }
    void necessary() {
        std::cout << "call success!!!" << std::endl;
    }
};

int main() {
    Foo obj;
    obj.start();
}

所以,重要的问题是:

  1. system_call 的参数是固定不变的。
  2. 方法start() 必须是非静态的。

我正在考虑这个,通过使用静态成员:

class Foo {
public:
    void start() {
        auto func = std::bind(foo, std::placeholders::_1, this);
        system_call(func);  // WRONG: cannot convert std::_Bind to a function pointer
    }
private:
    static int foo(void *args, Foo *f) {
        f->necessary();
        return 0;
    }
    void necessary() {
        std::cout << "call success!!!" << std::endl;
    }
};

或者这个,通过使用带有捕获的 lambda:

class Foo {
public:
    void start() {
        auto func = [this](void *args) -> int {
            this->necessary();
        };
        system_call(func); // WRONG: cannot convert a lambda with captures to a function pointer
    }
private:
    void necessary() {
        std::cout << "call success!!!" << std::endl;
    }
};

他们都错了。

有解决这个问题的方法吗?

附:我认为这是对封装的巨大要求,但是在这里我发现一些答案并不优雅(他们修改了参数列表,无法用于系统调用):

how to pass a non static-member function as a callback?

【问题讨论】:

  • 什么是“Linux 系统调用 API”?这具有 XY 问题的所有潜力。虽然有几个经典的 hack 来实现类方法回调,但我强烈怀疑是 XY 问题,并且对于这个特定的“Linux 系统调用 API”可能有更合适的答案。
  • 哇,高信噪比:1 行系统调用需要 11 行。我建议您查看IOCCC 规则。
  • 调用静态函数时不需要this,所以不需要std::bind
  • @ThomasMatthews 好吧,你说得对,但是我们正在尝试使用 C++,所以我们必须将它变形为一个对象。如果我们直接使用系统调用,那为什么我们只是在项目中改成C代码呢?
  • 你想wrap系统调用还是warp它?或者你想对call(me)back函数进行系统调用?

标签: c++


【解决方案1】:

我知道这是一个 XY 问题。我专门问了哪个系统调用有问题,你写的是clone()

如果你review the documentation of clone()你会发现它需要一个arg参数,还有:

当fn(arg)函数应用返回时,子进程 终止。 fn 返回的整数是退出代码 子进程。子进程也可能终止 通过调用 exit(2) 或在收到致命信号后显式调用。

因此,只需将静态 callback() 函数传递给 clone()

然后将this 指针传递给arg

然后让你的callback() 调用真正的类方法。

void callback(void *arg)
{
    reinterpret_cast<Foo *>(arg)->foo();
}

不仅如此,使用单指针传递任意参数也很简单:

  1. 定义一个struct,除了指向要调用其方法的Foo 类的指针之外,它还包含任何需要的参数。

  2. new类的实例,将this放入指针中,初始化要传递的参数。

  3. 将指向此类的指针作为arg 参数传递给clone()

  4. 1234563 .

这是一种非常常见的设计模式:一个接受回调函数指针的函数还将接受一个额外的第二个不透明指针,该指针被转发到回调函数,它可以使用它来存储回调所需的任何辅助数据.例如指向应调用其方法的类的实例的指针。不以这种方式构建回调机制实际上是一种糟糕的编程习惯。

【讨论】:

  • 非常感谢山姆!我通过你的回答学习了很多。我在想 clone() 中的 args 是一个输出参数,没用,我将进一步了解 C。
【解决方案2】:

解决方案 1

最简单的解决方法是使用使用全局变量的非成员函数。

// The global variable
Foo* currentFooPtr = NULL;

// The callback function. Define it after the definition of Foo
int foo_callback(void *);

然后在设置currentFooPtr的值后使用foo_callback作为system_call的参数。

class Foo {
public:
    void start() {
        currentFooPtr = this;
        system_call(foo_callback);
    }
};

int foo_callback(void *)
{
   // Use currentFooPtr any way you wish to
}

多线程程序的重要说明(感谢@SamVarshavchik):

这只有在应用程序不是多线程的情况下才有效;或者,具体来说,不能同时执行多次系统调用。否则,您可能希望调用受全局互斥体保护。

解决方案 2

使用static 成员变量和static 成员函数代替全局变量和非成员函数。我认为它比解决方案 1 更好,因为它不会将变量或函数添加到仅供 Foo 使用的全局范围内。

class Foo {
public:
    void start() {
        currentFooPtr = this;
        system_call(foo_callback);
    }

    static Foo* currentFooPtr;
    static int foo_callback(void*);      
};

Foo* Foo::currentFooPtr = NULL;

int Foo::foo_callback(void *)
{
   // Use currentFooPtr any way you wish to
}

【讨论】:

  • 这只有在应用程序不是多线程的情况下才有效;或者,具体来说,不能同时执行多次系统调用。否则,您可能希望调用受全局互斥体保护。
  • 感谢您的解决方案!它确实解决了这个问题,但是,它不是很优雅。全局变量,属于C,你懂我的意思...
  • @OuChangkun 请注意,从技术上讲,在最后一个示例中,它们仍然是全局函数和全局变量,但带有“优雅”Foo:: 限定符。不优雅的是,现在您需要在头文件(Foo 声明)中公开所有这些优雅。如果你使用全局变量,你可以将它们隐藏在 .cpp 文件中,这样客户就不会知道里面的黑魔法。
  • @RSahu 感谢您的更新!解决方案 2 确实让我感觉更好。 :) 这是避免函数指针转换的绝佳替代方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-30
  • 2017-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多