手动检索和存储指向 OpenGL 函数的指针时遇到问题

Posted

技术标签:

【中文标题】手动检索和存储指向 OpenGL 函数的指针时遇到问题【英文标题】:Trouble retrieving and storing pointers to OpenGL functions manually 【发布时间】:2016-06-02 16:20:52 【问题描述】:

我在手动检索和存储指向 OpenGL 函数的指针时遇到了一些麻烦,这是我的代码的“简化 sn-p”版本:

#ifdef WIN32
#include <windows.h>
#endif

#include <GL/gl.h>

class CGLManager

public:
    // Manager functions
    bool GetAnyGLFuncAddress( const char *_cName, void *_pFunc );
    bool LoadFunctions( void );

    // OpenGL functions
    void (APIENTRY *glBegin)( GLenum mode );
    void (APIENTRY *glEnd)( void );
    void (APIENTRY *glVertex3f)( GLfloat x, GLfloat y, GLfloat z );

private:
#ifdef WIN32
    HMODULE m_hLib; // opengl32.dll
#else
    void *m_hLib; // libGL.so
#endif
;

这是源代码:

extern CGLManager gGL;

// GetAnyGLFuncAddress - Attempt to retrieve the OpenGL function named "_cName" and store it in "_pFunc", returns true if success or false otherwise
bool CGLManager::GetAnyGLFuncAddress( const char *_cName, void *_pFunc )

#ifdef WIN32
    // Similar to https://www.opengl.org/wiki/Load_OpenGL_Functions#Windows
    _pFunc = (void *)wglGetProcAddress( _cName );
    if ( _pFunc == 0 || (_pFunc == (void *)0x1) || (_pFunc == (void *)0x2) || (_pFunc == (void *)0x3) || (_pFunc == (void *)-1) )
        _pFunc = (void *)GetProcAddress( m_hLib, _cName );
#else
    // TODO: Test this
    // According to some websites, NVIDIA drivers prefer the ARB implementation over the core one
    _pFunc = (void *)glXGetProcAddressARB( _cName );
    if ( _pFunc == 0 || (_pFunc == (void *)0x1) || (_pFunc == (void *)0x2) || (_pFunc == (void *)0x3) || (_pFunc == (void *)-1) )
        _pFunc = (void *)glXGetProcAddress( _cName );
#endif

    return (_pFunc != NULL);


// LoadFunctions - Attempt to retrieve all used OpenGL functions, returns true if all of them were retrieved or false if there is a single failure
bool CGLManager::LoadFunctions( void )

    if ( !(GetAnyGLFuncAddress( "glBegin", &gGL.glBegin )) )
        return false;

    if ( !(GetAnyGLFuncAddress( "glEnd", &gGL.glEnd )) )
        return false;

    if ( !(GetAnyGLFuncAddress( "glVertex3f", &gGL.glVertex3f )) )
        return false;

    return true;

这是我的经理的一般工作方式:它首先检查游戏引擎使用的渲染器(软件、OpenGL 或 Direct3D),如果不是 OpenGL,我们将停止进一步研究。否则,我们加载库(opengl32.dlllibGL.so)并检查它是否良好(同样,如果失败,我们停止),我们检索并存储指向 OpenGL 函数的指针(glBeginglEnd , glVertex3f) 使用 LoadFunctions 方法,如果一切正常或发生错误,我们将返回。

现在的问题: GetAnyGLFuncAddress 方法成功检索 OpenGL 函数(换句话说,glBegin 将返回 true,glARandomMethodThatDontExist 将返回 false)但出于某种原因,gGL.glBegin LoadFunctions 中的(它是“朋友”)没有更新,它总是 NULL 导致崩溃。

我已经尝试了几个小时通过在 Internet 和 *** 上搜索来找到解决方案,但我没有找到任何可以解决问题的答案。

在我在 *** 上找到的许多网站和答案中,很多人建议使用像 GLEW 这样的 OpenGL 加载库,甚至 OpenGL wiki 也推荐它。 但是,由于我正在处理的环境的性质,我不能使用这些库,也不能直接使用 OpenGL 函数,我知道我正在经历手动做所有事情的痛苦方式,但我别无选择。

感谢您的回答。

【问题讨论】:

暂时忘记openGL等,如果你在GetAnyGLFuncAddress中更新pFunc(实际上是一个本地临时的),该功能将如何工作?您设置 pFunc 的值不会反映给调用者。我希望*pFunc = whatever;,而不是pFunc = whatever,即您应该取消引用该指针值。 "但是,由于我工作环境的性质,我不能使用那种库" ...这确实是一个非常奇怪的环境不能包含 .h 文件和 .cpp 文件的地方。有些加载器比其他加载器简单。 @PaulMcKenzie 在_pFunc 之前放置星号会导致 C2100 错误(非法间接) @Shepard62700FR:这不是论坛。答案在答案部分。你不会编辑你的问题说它已经解决了。 @NicolBolas 抱歉,不会再发生了。 【参考方案1】:
bool CGLManager::GetAnyGLFuncAddress( const char *_cName, void *_pFunc )

这只是常见的、损坏的 C++。 _pFuncvoid*。指针就是值。改变_pFunc的值不会改变传入的变量的值。

您应该只返回指针(NULL 表示失败条件),或者_pFunc 应该是void**。不过,这可能需要调用者进行强制转换,GetAnyGLFuncAddress 需要执行 *_pFunc = 来设置值。

如果要返回函数指针,则需要在存储之前将返回的void* 转换为适当的函数指针类型。这就是为什么你经常看到 OpenGL 加载器使用 typedef 来表示函数指针类型。

typedef void (APIENTRY *(PFN_GLBEGIN))( GLenum );

...

PFN_GLBEGIN glBegin;

...

glBegin = static_cast<PFN_GLBEGIN>(GetAnyGLFuncAddress("glBegin"));

类似的东西。

【讨论】:

请注意,您正在返回指向函数的指针。 OP 似乎是通过一个参数来设置指针,并且从它的外观来看,这样做是不正确的。 @PaulMcKenzie:我在发布原始版本后意识到这一点。 @NicolBolas:我的第一次尝试就像你说的那样:返回指针而不是在方法中分配它,并根据成功/失败返回真或假,但由于某种原因,我永远无法“将结果分配给gGL.glBegin @Shepard62700FR:当然不是。这是 C++,而不是 C。在 C++ 中,void* 指针不会神奇地将自己转换为其他类型。如果你想玩 C++ 中的类型游戏,你必须明确并进行适当的转换。 @Shepard62700FR:此外,“出于某种原因”在 C++ 中什么也没有发生。如果编译失败,它会告诉你原因是什么。【参考方案2】:

忘记你的最终目标是什么,我看到了基本的 C++ 错误。

让我们删掉所有不相关的代码并专注于此:

bool CGLManager::GetAnyGLFuncAddress( const char *_cName, void *_pFunc )

    _pFunc = (void *)wglGetProcAddress( _cName ); // <-- This sets the local pointer
    //...

然后你像这样调用上面的代码:

bool CGLManager::LoadFunctions( void )

    if ( !(GetAnyGLFuncAddress( "glBegin", &gGL.glBegin )) )
        return false;

您在第二个参数中传递了地址,但在函数GetAnyGLFuncAddress 中,您并没有更新值以便将新值反映回调用者。您正在设置本地值,因此不会设置 gGL.glBegin(以及来自其他两个调用的所有其他地址)。

GetAnyGLFuncAddress 内部,我原以为这会起作用:

bool CGLManager::GetAnyGLFuncAddress( const char *_cName, void *_pFunc )

    *_pFunc = (void *)wglGetProcAddress( _cName ); // <-- Note the *
    //...

GetAnyGLFuncAddress 内部对pFunc 的任何后续使用也应该反映取消引用的值。


另一种解决方案(因为这个 C++),您可以放弃void * 类C 编码,并将函数作为模板,将函数指针类型作为模板参数:

template <typename FnPtr>
bool CGLManager::GetAnyGLFuncAddress( const char *_cName, FnPtr* _pFunc )

    *_pFunc = reinterpret_cast<FnPtr>(wglGetProcAddress( _cName )); 
    //...

由于类型现在是FnPtr*,所以无论您传入什么,FnPtr 都会自动成为该类型。没有void *(除了wglGetProcAddress的返回值,它是强制转换的)。

【讨论】:

如果我这样做并将原型更改为const char *_cName, void **_pFunc,我会遇到与强制转换相关的错误(请参阅@Nicol Bolas 的回答,他建议我应该返回指针而不是分配和返回成功/失败) @Shepard62700FR 如果您使用模板函数而不是void *,则可能会缓解此错误。查看我的编辑。 这解决了我的问题,问题似乎解决了,不再崩溃,gGL.glBegin 和其余的不再为 NULL,谢谢^^【参考方案3】:

*gl*GetProcAddres 仅用于返回扩展 功能的入口点地址。它不需要返回在操作系统 OpenGL ABI 要求中找到的函数的地址(用于 Windows 的 OpenGL-1.1、用于 Linux Softare Base (LSB) 4 的 OpenGL-1.2、用于 LSB-5 的 OpenGL-2.1)。

一般来说,如果您的程序二进制文件直接链接到 API 库(存根)(与使用在运行时动态加载 API 库(存根)的加载器相比),这非常愚蠢使用*gl*GetProcAddress 检索作为操作系统的 OpenGL ABI 合同一部分的函数,因为这些函数无论如何都可以通过常规 API 库获得。 wglGetProcAddress 与 OpenGL-1.1 函数一起导出,glXGetProcAddress 与所有 OpenGL-1.2 函数一起导出(至少),所以如果您的程序看到 *GetProcAddress 函数,它也会看到其他 OpenGL 函数。

通过 GetProcAddress 加载 所有 OpenGL 符号的唯一原因是,如果您使用 LoadLibrary 加载 opengl32.dll 或使用 dlopen 加载 libGL.so

【讨论】:

已经编写了几个 OpenGL 加载器,如果它在每个平台上的工作方式都相同,它会使加载代码变得简单得多。您不必根据所使用的平台在函数声明和函数指针声明之间进行选择;一切都是总是一个函数指针。然后,您的加载器可以循环所有函数并加载它们;它不必有#ifdef WINDOWS 来检查它是否应该加载GL 1.1 指针。 @NicolBolas:我的回答中没有任何内容否认您的说法。事实上,最后一段是说明 gl_LoadGen 中的方法 "一般来说,检索作为操作系统的 OpenGL ABI 合同一部分的函数是非常愚蠢的" 这就是我对你的回答的问题。这根本不是“很傻”。这是完全合理的。事实上,如果你正在编写一个加载器,我会认为加载所有函数指针是非常愚蠢的not。此外,OP 确实加载了opengl32.dll,这是m_lib 在他对GetProcAddress 的调用中的来源。诚然,他没有显示模块的加载,但它一定是发生了调用才能工作。 @NicolBolas:是的,它的措辞有点偏颇。我在这方面编辑了答案。当谈到 OpenGL 加载器时,恕我直言,这是一把双刃剑:如果系统配置中的某些内容损坏或缺少功能,则不使用加载器会在程序启动时提供直接反馈。拥有一个加载器可以提供用户友好的诊断。此外,加载器是实现更高功能库的唯一方法,这些库只能可选地依赖于 OpenGL 或其他 API(例如我的体积光栅化器库,它可能使用 3D API,但如果没有可用的 API 不会破坏程序) @NicolBolas 和 @datenwolf :感谢您的解释,为了清楚起见,我没有发布初始化/关闭方法,因为问题不存在,但确实有一个 @987654332 @/m_hLib = dlopen( "libGL.so", RTLD_LAZY );FreeLibrary( m_hLib );/dlclose( m_hLib ); 参与

以上是关于手动检索和存储指向 OpenGL 函数的指针时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

为啥在使用 OpenGL 核心配置文件时会崩溃?

指针和引用函数指针

渲染openGL纹理:类函数导致渲染失败

存储指向成员函数的指针不适用于 clang++

返回一个指针数组?

删除指针向量时遇到问题