有没有办法从传递给C API的函数指针修改类状态?

时间:2015-05-16 11:45:48

标签: c++ c class callback global-variables

我有一个有一些内部状态的课程。该类使用名为GLFW的C库来处理窗口管理和键盘输入。

该课程看起来像这样:

class Object {
public:
    Object(...);
    ...
private:
    int _member_variable; // Internal state
    GLFWwindow *_window;
    ...
}

关于GLFW的背景

GLFW是一个处理窗口处理和键盘输入的C库。当窗口聚焦时,它使用回调函数来报告用户何时按下键。

void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
    printf("User pressed key #%d\n", key);
}

要将回调函数绑定到窗口,请使用名为glfwSetKeyCallback的函数。

// This will bind the `key_callback` function to `window`.
glfwSetKeyCallback(window, key_callback);

我遇到的问题

我想从一个关键的回调函数修改我的类的内部状态,而不依赖于static存储的状态。我不能拥有全局状态,因为我希望能够创建Object的多个实例并使它们彼此独立工作。

假设在这种情况下我想在每次用户按下某个键时增加_member_variable

我想做这样的事情:

// Private method for dealing with key presses.
void Object::key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
    if (action == GLFW_PRESS)
        ++_member_variable; // Increment the internal state
}

Object::Object(GLFWwindow *window, ...)
: _window(window), ...
{
    glfwSetKeyCallback(window, key_callback);
}

但这不起作用,因为Object::key_callbackObject的成员函数,需要不可见的this参数。 (Object::*)(GLFWwindow *, int, int, int, int)无法转换为(*)(GLFWwindow *, int, int, int, int)

我尝试了什么

  • 无法传递GLFW API的私有成员函数。考虑到成员函数需要不可见的this参数,这是可以理解的。
  • 我也尝试过使用lambda捕获我想要的状态但是也没用。

有没有办法从传递给C API的函数修改类状态而不依赖于全局状态?就像在这种情况下从传递给_member_variable的函数中递增glfwSetKeyCallback

4 个答案:

答案 0 :(得分:2)

是的,有可能。该解决方案是GLFW API支持的解决方案。

典型的C库允许您在注册回调时将不透明指针传递给用户数据。 glfwSetKeyCallback没有。

相反,在GLFW中,您调用glfwSetWindowUserPointerGLFWWindow内设置用户指针字段。在this构造函数中或实际创建窗口的任何位置将其设置为Object。然后,向Object类添加静态成员函数。此功能可以是私人的。将其作为回调传递给glfwSetKeyCallback。调用此回调时,使用GLFWWindowglfwGetWindowUserPointer检索用户指针,将其转换为Object*,并使用它将调用分派给该对象的非静态成员函数或修改数据成员直接。

http://www.glfw.org/docs/latest/window.html#window_userptr

修改

为了使这个答案更加完整,让我评论你试图解决这个问题的方法。

  
      
  • 无法传递GLFW API的私有成员函数。考虑到成员函数需要不可见的这个参数,这是可以理解的。
  •   
  • 我也尝试过使用lambda捕获我想要的状态但是也没用。
  •   

确实可以传递一个私有的静态成员函数,就像我和@derhass所描述的那样(几乎同时)。不过,我确定你的意思是非静态成员函数。正如您所观察到的,您需要this指针来分派到正确的对象。您似乎可以使用this设置glfwSetWindowUserPointer指针,然后传递pointer to a private non-static member function。但是,这不起作用。指向成员函数的指针通常被实现为“胖”指针,除了地址之外还有额外的数据。例如,请参阅此discussion。如果您设法将其转换为普通指针并将其传递给C库,则会以某种方式截断。即使你可以避免强制转换的截断,我认为你也不能,但是C库没有足够的存储空间来存储胖指针,并且无法将它完整地返回给你。

同样,lambdas也是如此。

答案 1 :(得分:1)

回调的第一个参数是指向CREATE TABLE company ( id SERIAL PRIMARY KEY NOT NULL, name VARCHAR(150) NOT NULL, city_name VARCHAR(50), ); CREATE TABLE company_industry ( company_id INT NOT NULL REFERENCES company (id) ON UPDATE CASCADE, industry_id INT NOT NULL REFERENCES industry (id) ON UPDATE CASCADE, PRIMARY KEY (company_id, industry_id) ); CREATE TABLE industry ( id SERIAL PRIMARY KEY NOT NULL, name VARCHAR(100) NOT NULL ); CREATE INDEX company_city_name_index ON company (city_name); 的指针。您可以使用它来标识您的C ++窗口对象,例如通过GLFWwindow(然后需要从静态存储中获取或引用)。然后只需将调用传递给合适的方法。

了解线程问题。

此外,最好先考虑窗口创建和窗口销毁的责任。 GUI窗口通常是自毁的,很可能你的C窗口API也是这样。我或多或少地接受了启动API级自毁的技术,并且让它导致C ++对象的破坏,而不是相反,因为产生最复杂的代码,但无论你选择什么,真正的想法是个好主意在承诺设计选择之前,先做好准备。

答案 2 :(得分:1)

我可能错了,但我不知道如果没有某些全局状态,你将如何完成你想要做的事情。你可以做的是创建一个全局变量来存储指向jvalue2monadic实例的指针,然后在Object运行时调用它们的成员函数。这样,你至少可以包含损坏因为你只有一个全局变量,那么你就不必将key_callback的每个实例都变成一个全局变量。

以下是一些代码,演示了我想说的内容:

Object

使用此代码,您可以调用传递// the only global variable in the code: std::list<Object *> registeredObjects; // register your callback to a free function instead of a member function void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) { // however, make this function call the member function of your Objects for (Object *registeredObject : registeredObjects) registeredObject->key_callback(window, key, scancode, action, mods); } // call this function to make your Objects able to react to key presses void registerKeyCallback(Object *objectToRegister) { for (Object *obj : registeredObjects) if (obj == objectToRegister) // is the given object already registered? return; // yes, so don't register again registeredObjects.push_back(objectToRegister); } 地址的registerKeyCallback函数,以便能够对按键做出反应,然后Object将每当它对按键做出反应时,都能够改变自己的内部状态。如果Object函数是私有的,那么您可以将Object::key_callback函数设为您班级的朋友,如下所示:

key_callback

然后,class Object { ... friend void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods); }; 函数将能够调用私有key_callback函数。

答案 3 :(得分:1)

这个答案特定于GLFW库,而不是试图回答更一般的编程语言问题。但是,你会发现这很有用,因为它解决了你真正想要解决的问题。

GLFW支持一种允许通过user pointer which can be specified per window将用户特定数据传递给回调的方式。

使用它的一些方法可能是这个

class Object {
public:
    Object(...);
    ...
private:
    static void keyboard_callback_glfw(GLFWwindow *win_glfw, int key, int scancode, int action, int mods); // raw GLFW callback
    int _member_variable; // Internal state
    GLFWwindow *_window;
    ...
protected:
    void onKeyPress(int key, int scancode, int action, int mode); // object-oriented callback that does the work
}

void Object::keyboard_callback_glfw(GLFWwindow *win, int key, int scancode, int action, int mode)
{
     Object *obj=static_cast<Object*>(glfwGetWindowUserPointer(win));
     // call the Objects onKey() handler
     obj->onKeyPress(key, scancode, action, mode);
}

这个方案也适用于继承,所以你基本上可以将GLFW及其所有回调包装在某些&#34; Window&#34;类,并从中派生出来,覆盖各种onEvent处理程序,而不必再关心GLFW的C-API。在注册GLFW回调之前,在对象中创建(或分配)窗口后,您必须glfwSetWindowUserPointer(_window, this);