有没有一种方法可以从传递给C API的函数指针中修改类状态
Is there a way to modify class state from a function pointer passed to a C API?
我有一个类,它有一些内部状态。该类使用一个名为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 #%dn", 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_callback
是Object
的成员函数,并且需要不可见的this
参数。(Object::*)(GLFWwindow *, int, int, int, int)
无法转换为(*)(GLFWwindow *, int, int, int, int)
。
我尝试过的
- 无法将私有成员函数传递给GLFW API。考虑到成员函数需要不可见的
this
参数,这是可以理解的 - 我还尝试过使用lambda来捕获我想要的状态,但也没有成功
有没有一种方法可以在不依赖全局状态的情况下修改传递给C API的函数的类状态?就像在这种情况下从传递给glfwSetKeyCallback
的函数中递增_member_variable
一样?
是的,这是可能的。该解决方案是GLFW的API支持的解决方案。
典型的C库允许您在注册回调时向用户数据传递不透明的指针。glfwSetKeyCallback
没有。
相反,在GLFW中,您可以调用glfwSetWindowUserPointer
来在GLFWWindow
中设置一个用户指针字段。在Object
构造函数中或在实际创建窗口的任何位置将其设置为this
。然后,在Object
类中添加一个静态成员函数。此功能可以是私有的。将其作为回调传递给glfwSetKeyCallback
。调用此回调时,使用glfwGetWindowUserPointer
从GLFWWindow
检索用户指针,将其强制转换为Object*
,并使用它将调用分派给该对象的非静态成员函数或直接修改数据成员。
http://www.glfw.org/docs/latest/window.html#window_userptr
编辑
为了使这个答案更加完整,让我评论一下你尝试的解决方法。
- 无法将私有成员函数传递给GLFW API。考虑到成员函数需要不可见的this参数,这是可以理解的
- 我还尝试过使用lambda来捕获我想要的状态,但也没有成功
确实可以像我和@derhass所描述的那样(几乎同时(传递一个私有的静态成员函数。不过,我确信你指的是非静态成员函数。正如您所观察到的,您将需要this
指针来调度到正确的对象。看起来可以使用glfwSetWindowUserPointer
设置this
指针,然后将指针传递给私有的非静态成员函数。然而,这是行不通的。指向成员函数的指针通常被实现为"胖"指针,除了地址之外还有额外的数据。例如,请参阅本讨论。如果您设法将一个强制转换为普通指针并将其传递给C库,您将以某种方式得到截断。即使可以避免强制转换截断(我认为这是不可能的(,C库也没有足够的存储空间来存储胖指针,也无法将其原封不动地返回给您。
同样,lambdas也是如此。
这个答案是特定于GLFW库的,并不是试图回答更通用的编程语言问题。然而,你可能会发现这很有用,因为它解决了你真正想要解决的问题。
GLFW确实支持一种方式,允许通过每个窗口指定的用户指针将用户特定的数据传递给回调。
某种使用方法可能是这个
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及其所有回调包装在某个"Window"类中并从中派生,覆盖各种onEvent
处理程序,而不必再关心GLFW的C-API。在GLFW回调注册之前,您只需要在对象中创建(或分配给(窗口后glfwSetWindowUserPointer(_window, this);
即可。
GLFWwindow
的指针。您可以使用它来标识您的C++窗口对象,例如通过std::unordered_map
(然后需要具有静态存储或从静态存储中引用(。然后将调用传递给一个合适的方法。
注意线程问题。
此外,提前考虑创建窗户和破坏窗户的责任也是个好主意。GUI窗口通常是自测试的,而且很可能你的C窗口API也是这样。我或多或少地已经掌握了启动API级自我构建的技术,并让它导致C++对象的破坏,而不是相反,因为它产生了最不复杂的代码,但无论你选择什么,在做出设计选择之前,最好先考虑一下这一点。
我可能错了,但我不知道如果没有一些全局状态,你如何才能完成你想要做的事情。可以创建一个全局变量,该变量存储指向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
函数将能够调用专用Object::key_callback
函数。
- 调用 Win32 API 函数时未定义的引用
- 谁拥有作为指向 Windows API 函数的指针传递的值?
- 将uint8_t*buffer和size_tbufferlen从C++传递到C中的API函数的最佳方式是什么
- 如何在应用程序正在运行的系统上查看所需的Windows API函数
- 我的 API 函数应该shared_ptr还是weak_ptr
- 将 API 函数参数从 'char *' 更改为"const char *"有哪些潜在问题?
- 当涉及数组时,我们是否可以安全地从C++调用C API函数
- 从 Windows KMD 调用用户空间 API 函数
- 写入const API函数(C )中的成员变量
- 毒害某些API函数(不是维护者)
- win32 API 函数 GetModuleFileName 或 GetModuleHandle 中的"Module"是什么?
- 将指针传递给API函数
- 如何通过重用单个API函数来调用不同的操作
- 将 ASCII 字符串转换为 UTF-16,然后再将它们传递给 Windows API 函数
- POSIX 或 Linux API 函数,用于从路径获取文件扩展名
- 如何正确调用 UrlCanonicalize API 函数
- 找不到Win32 API函数
- 将 AfxGetInstanceHandle() 的调用替换为 Windows API 函数
- 包装任何API函数
- Windows API函数不可用,但它应该可用