17

注意:这个问题与 OpenCL 本身无关......检查最后一段以获得我的问题的简洁陈述。但要提供一些背景:

我正在编写一些使用 OpenCL 的 C++ 代码。我喜欢将我的 OpenCL 内核的源代码保存在它们自己的文件中,以简化编码和维护(而不是将源代码直接作为字符串常量嵌入到相关的 C++ 代码中)。这不可避免地会导致在分发二进制文件时如何将它们加载到 OpenCL 运行时的问题——理想情况下,OpenCL 源代码包含在二进制文件中,因此二进制文件不需要位于特定位置在某个目录结构中知道 OpenCL 源代码在哪里。

我想将 OpenCL 文件作为字符串常量包含在某处,并且最好不使用额外的构建步骤或外部工具(为了交叉编译器/跨平台的易用性......即,禁止xxd等)。我以为我偶然发现了一种基于线程中第二个答案的技术,如下所示:

#define STRINGIFY(src) #src

inline const char* Kernels() {
  static const char* kernels = STRINGIFY(
    #include "kernels/util.cl"
    #include "kernels/basic.cl"
  );
  return kernels;
}

请注意,如果可能的话,我宁愿不将STRINGIFY宏嵌入到我的 OpenCL 代码中(正如上面引用的 SO 问题中所做的那样)。现在,这在 Clang/LLVM 编译器上运行得非常好,但是 GCC 死得很惨(“未终止的参数列表调用宏 STRINGIFY”和与 .cl 文件内容相关的各种语法“错误”出现)。所以,很明显,这种精确的技术不能在编译器中使用(没有尝试过 MSVC,但我也希望它在那里工作)......我怎样才能最小化地按摩它以便它在编译器中工作?

总之,我想要一种符合标准的技术,用于将文件的内容作为 C/C++ 字符串常量包含在内,而无需调用外部工具或使用无关代码污染文件。想法?

编辑:正如 Potatoswatter 指出的那样,上述行为是未定义的,因此不涉及接触要字符串化的文件的真正交叉编译器预处理器技术可能是不可能的(第一个找出令人发指的人对大多数/所有编译器都有效的 hack 得到了答案。出于好奇,我最终执行了此处第二个响应中的建议……也就是说,我将STRINGIFY宏直接添加到了我包含的 OpenCL 文件中:

somefile.cl

STRINGIFY(
  ... // Lots of OpenCL code
)

somefile.cpp

#define STRINGIFY(src) #src

inline const char* Kernels() {
  static const char* kernels =
    #include "somefile.cl"
    ;
  return kernels;
}

这适用于我尝试过的编译器(Clang 和 GCC,因为它在宏中没有预处理器指令),并且至少在我的上下文中不是太大的负担(即,它没有t 干扰语法高亮/编辑 OpenCL 文件)。像这样的预处理器方法的一个特点是,由于相邻的字符串被连接起来,你可以写

inline const char* Kernels() {
  static const char* kernels =
    #include "utility_functions.cl"
    #include "somefile.cl"
    ;
  return kernels;
}

并且只要 STRINGIFY 宏在两个.cl文件中,字符串就会连接起来,允许您模块化 OpenCL 代码。

4

4 回答 4

4

该标准最相关的部分是§16.3/10:

由最外面的匹配括号界定的预处理标记序列形成了类函数宏的参数列表。列表中的各个参数由逗号预处理标记分​​隔,但匹配内括号之间的逗号预处理标记不分隔参数。如果(在参数替换之前)任何参数不包含预处理标记,则行为未定义。如果在参数列表中存在预处理标记序列,否则它们将充当预处理指令,则行为未定义。

提取关键点:

  • 您需要将头文件括在一对括号内,这样宏就不会认为文件中的每个逗号字符都会引入另一个参数。这些括号也将被字符串化,但应该不难解决。
  • 放入#include参数列表完全是官方未定义的行为,因此这将是不可移植的。编译器正式不知道您是否希望生成的字符串为"#include \"kernels/util.cl\"".
于 2011-06-28T06:35:14.057 回答
1

常规技术是使用像 bin2c 这样的程序,通常是匆忙编写的。另一种方法是使用 GNU binutils 的 objcopy:

$ objcopy -I binary extensions.cfg -O elf32-little -B i386 --rename-section .data=.rodata extensions.o
$ objdump -x extensions.o

extensions.o:     file format elf32-i386
extensions.o
architecture: i386, flags 0x00000010:
HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .rodata       00000447  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
00000000 l    d  .rodata        00000000 .rodata
00000000 g       .rodata        00000000 _binary_extensions_cfg_start
00000447 g       .rodata        00000000 _binary_extensions_cfg_end
00000447 g       *ABS*  00000000 _binary_extensions_cfg_size

-O 和 -B 标志必须与您编译的目标文件之一的 objdump 输出相匹配,以满足链接器的要求,而节重命名只是为了通知运行时链接器此数据是只读的。注意符号,映射到起始地址、结束地址和数据大小。它们每个都算作地址,因此在 C 中您可以将它们与以下内容一起使用:

extern const char _binary_extensions_cfg_start, _binary_extensions_cfg_end;
extern const char _binary_extensions_cfg_size;
for (const char *p=&_binary_extensions_cfg_start; p<&_binary_extensions_cfg_end; p++)
    do_something(p);
memcpy(somewhere, &_binary_extensions_cfg_start, (intptr_t)&_binary_extensions_cfg_size);

我意识到这些都不是你要求的预处理器,但它根本不是为了做到这一点而设计的。不过,我很想知道它是否可以。

于 2011-06-28T06:23:11.150 回答
0

你不能那样做;我很惊讶它在铿锵声中工作。如果你想在二进制文件中直接包含文本文件,你有一些选择:

1:在编译之前运行的预处理程序,它将您的 .cl 文件转换为定义字符串的 .cpp 文件。

2:通过编译器特定的存储将字符串数据存储在已编译的可执行文件中。这就是 Visual Studio 等工具包含 .rc、.ico 和其他用于构建 Windows 应用程序的文件的方式。当然,如前所述,这些是特定于编译器的。

最安全的方法是选项 1。

于 2011-06-28T06:03:18.983 回答
-2

这是我在 xcode C 中所做的:

const char oursource[60000];
const char * oursourceptr = oursource;
const char * * oursourceptrptr = & oursourceptr;

// in function "readfileintostring":

char *fylnm = "/Developer/projs/myproj/mykernel.cl";

long enby; short pathref;

FSRef dink; FSPathMakeRef( (const UInt8 *) &fylnm, &dink, NULL );
SInt16 forkRefNum;  HFSUniStr255 dataForkName;  FSGetDataForkName(&dataForkName);
FSOpenFork( &dink, dataForkName.length, dataForkName.unicode, fsRdPerm, (FSIORefNum *) &pathref );

enby = 100000;  FSRead( pathref, &enby, (void *) oursourceptr );

// .. then later ..

program = clCreateProgramWithSource(context, 1, (const char **) oursourceptrptr, NULL, &err);

...它不是预处理器巫毒,但它适用于我,我可以在我的 .cl 文件中看到突出显示的语法,我什至可以将我的 .cl 复制到 .c 中,更改一个#define,它作为 xcode C 运行……

于 2011-07-22T16:37:24.477 回答