如何在编译时检查函数是否在全局范围内声明

5

比如,我有一个头文件 #include <GL/gl.h>,其中包含了OpenGL API函数的子集。我需要像这样的内容:

static_assert(has_glDrawArraysIndirect::value, "There is no glDrawArraysIndirect");

甚至更好的是:

PFNGLDRAWARRAYSINSTANCEDPROC ptr_glDrawArraysIndirect = ptr_to_glDrawArraysIndirect::ptr;

ptr_to_glDrawArraysIndirect::ptr展开为指向glDrawArraysIndirect的指针时,如果已定义,则指向glDrawArraysIndirect,否则指向存根函数stub_glDrawArraysIndirect

我的目标操作系统非常特殊。任何基于链接器的解决方案(如GetProcAddressdlsym)对我来说都无效,因为没有动态链接器。此外,我的驱动程序不提供glXGetProcAdrresswglGetProcAddress,基本上没有办法通过函数名称在运行时查询指针(实际上,我想实现这样的机制)。

有什么好的想法吗?


1
如果没有动态链接,那么它必须是静态链接的。因此,函数要么在编译时存在,要么不存在。由于这将由头文件的内容确定,因此您可以直接查看它。 - Nicol Bolas
@Nicol Bolas:这是我长期以来一直在做的事情。由于驱动程序正在进行大量开发,我已经厌倦了不断更新我的代码。我想一劳永逸地解决它。 - ivaigult
4个回答

7
这里有一个答案,它可以在编译时检测它并生成一个布尔值。它的工作原理是在命名空间中创建一个相同名称的模板函数,然后在is_defined()方法中使用该命名空间。如果真正的glDrawArraysIndirect()存在,则会优先于模板版本。如果您注释掉glDrawArraysIndirect()的第一个声明,则底部的静态断言将触发。 在 GodBolt 上测试
#include <type_traits>

enum GLenum {};

void glDrawArraysIndirect(GLenum, const void*);

namespace detail {
    struct dummy;

    template<typename T>
    dummy& glDrawArraysIndirect(T, const void*);
}

constexpr bool is_defined()
{
    using namespace detail;
    using ftype = decltype(glDrawArraysIndirect(GLenum(), nullptr));
    return std::is_same<ftype, void>();
}

static_assert(is_defined(), "not defined");

通过一些小的调整,你可以将你的自定义函数作为模板,并使用类似的技巧。

ideone.com

#include <type_traits>
#include <iostream>

//#define USE_REAL

enum GLenum {TEST};

typedef void (*func_type)(GLenum, const void*);

#ifdef USE_REAL
void glDrawArraysIndirect(GLenum, const void*);
#endif

namespace detail {
    struct dummy {};

    template<typename T = dummy>
    void glDrawArraysIndirect(GLenum, const void*, T = T())
    {
        std::cout << "In placeholder function" << std::endl;
    }

}

void wrapDraw(GLenum x, const void* y)
{
  using namespace detail;
  glDrawArraysIndirect(x, y);
}

#ifdef USE_REAL
void glDrawArraysIndirect(GLenum, const void*)
{
    std::cout << "In real function" << std::endl;
}
#endif

int main()
{
    wrapDraw(TEST, nullptr);
}

实际上,对于第二个示例,您根本不需要命名空间、虚拟参数或默认模板参数。只需让T泛型类型代替GLenum的位置,并且它将在重载分辨率中被选择为真正的类型。 - Siyuan Ren
1
这样做可能会降低类型安全性,并且根据您用于枚举的T推断出的类型,可能会生成多个模板实例化。这就是为什么我选择了这种方法。 - Kurt Stutsman
@KurtStutsman:嗯,你可以添加一个static_assert来强制执行TGLenum - Siyuan Ren
详细命名空间是否必要? - Siyuan Ren
不应该需要。 - Kurt Stutsman

3

在某处包含表达式sizeof(::function)。(如果该函数存在,则请求指向该函数的指针的大小是完全有效的事情)。

在运行时,它将是良性的,::强制使用在全局范围内声明的function

当然,如果function不存在于全局作用域,则编译将失败。

除了其他错误之外,如果您写出以下内容,则编译器将发出特定错误:

static_assert(sizeof(::function), "There is no global function");


static_assert(sizeof(::function), "There is no global function"); - R Sahu
1
如果您能够找出如何检测函数的存在并将值1分配给一个布尔常量(如果存在)且否则将值0分配给该常量,而不是中止编译,则可以获得额外的奖励分数。 - HolyBlackCat

1
我的目标操作系统非常特定。任何基于链接器的解决方案(例如GetProcAddress或dlsym)对我来说都不起作用,因为没有动态链接器。
这是一个嵌入式系统还是在标准PC硬件上运行的奇怪削减版操作系统?
此外,我的驱动程序不提供glXGetProcAdrress或wglGetProcAddress,基本上没有办法通过函数名在运行时查询指针。
在运行时查询函数指针的能力并不取决于动态链接器的存在。这两者完全无关,即使纯静态链接的嵌入式OpenGL实现也可以很好地提供GetProcAddress接口。我不想试图在编译或链接时解决问题,而是更愿意通过为您的OpenGL驱动程序实现GetProcAddress来解决问题。即使驱动程序仅以二进制形式的静态库可用,您也可以这样做。第一步:
为每个OpenGL函数创建函数指针存根,静态初始化为NULL,并具有弱链接属性。将其链接到静态库中,您可以称之为gl_null_stubs或类似名称。
创建一个GetProcAddress函数,使每个OpenGL函数都能返回指向该函数符号的指针,该指针位于函数编译单元的范围内。
现在将您的奇怪的OpenGL驱动程序与存根库和GetProcAddress实现链接。对于每个函数,存根的弱链接将优先使用静态库符号。对于不在您的驱动程序中的所有OpenGL符号,存根将接管。
这样,您就拥有了一个OpenGL驱动程序库,它具有已经有一个GetProcAddress实现。这并不难,是吗?

1
如何在编译时检查函数是否在全局范围内声明?我的目标操作系统非常特殊...
可能的解决方案是,如果您正在使用最近的GCC(可能作为您奇怪的目标操作系统和ABI的交叉编译器),则可以使用自己的MELT扩展来自定义gcc(或g++等)编译器。
MELT是一种特定领域的语言,实现为自由软件GCC插件(主要在Linux上),用于自定义GCC编译器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接