我如何让 GNU __attribute__((constructor)) 在图书馆工作?
How do I get GNU __attribute__((constructor)) to work in a library?
如果我 link 将所有目标文件放在一个 link 中,我可以让 GNU __attribute__((constructor))
工作(对于 C++ 程序),但它不再工作,如果我将包含构造函数的目标文件存储在库中,然后 link 库而不是目标文件。我做错了什么?
Makefile.am:
SUBDIRS = src
src/Makefile.am:
bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh myfunc.cc
src/hello.cc:
#include <iostream> // for cout
#include <map>
#include "register.hh"
int main(int argc, char* argv[])
{
std::cout << "Hello, World!" << std::endl;
std::cout << "Have " << functions.size() << " functions registered."
<< std::endl;
for (Function_map::iterator it = functions.begin(); it != functions.end(); ++it) {
std::cout << "Registered " << (*it).first << std::endl;
(*it).second();
}
return 0;
}
src/register.cc:
#include <map>
#include <string>
#include "register.hh"
Function_map functions;
void register_function(const std::string& name, Function f)
{
functions[name] = f;
}
src/register.hh:
#ifndef REGISTER_H_
#define REGISTER_H_
#include <map>
#include <string>
typedef void (*Function)();
typedef std::map<const std::string, Function> Function_map;
extern Function_map functions;
void register_function(const std::string& name, Function f);
#endif
src/myfunc.cc:
#include "register.hh"
#include <iostream>
void myfunc()
{
std::cout << "This is myfunc!" << std::endl;
}
__attribute__((constructor))
void register_myfunc()
{
register_function("MYFUNC", myfunc);
}
configure.ac:
AC_PREREQ([2.69])
AC_INIT([hello], [1.4], [bugs@my.domain])
AC_CONFIG_SRCDIR([src/hello.cc])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_AUX_DIR([auxiliary])
AM_INIT_AUTOMAKE([-Wall -Werror])
AC_PROG_CXX
AM_PROG_AR
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
所以所有 C++ 文件都被编译成目标文件,这些文件 link 一起进入 'hello' 可执行文件。
生成的 'hello' 程序的输出是:
Hello, World!
Have 1 functions registered.
Registered MYFUNC
This is myfunc!
如果我将 src/Makefile.am 更改为
bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh
hello_LDADD = liblibrary.a
noinst_LIBRARIES = liblibrary.a
liblibrary_a_SOURCES = myfunc.cc
(即,myfunc.cc 编译成 myfunc.o,存储在 liblibrary.a 中,后者与其他目标文件一起 link 编辑成 'hello'),那么 'hello' 的输出是
Hello, World!
Have 0 functions registered.
所以现在 'register_myfunc' 函数没有被执行。为什么不呢?
已编辑 2015-02-22(回应 Basile Starynkevitch 的回答):我使用的是 GNU/Linux (Fedora 20) 系统。我尝试使用 libtools 构建共享库,但没有成功。我调整了 src/Makefile.am 如下:
bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh
hello_LDADD = liblibrary.la
noinst_LTLIBRARIES = liblibrary.la
liblibrary_la_SOURCES = myfunc.cc
liblibrary_la_LDFLAGS = -shared -fPIC
(首先只有 -shared
,后来也有 -fPIC
)并将 LT_INIT
添加到 configure.ac,但这并没有改变结果。我将尝试你提到的用于 C++ 的 "static data with explicit constructor" 技巧,但我仍然想知道如何让我的示例与 __attribute__((constructor))
.
一起工作
已编辑 2015-02-23 我尝试了 "static data with explicit constructor" 技巧但得到了与以前相同的结果:如果所有对象文件都是 link明确地一起编辑到一个可执行文件中,但如果我想要自动构建的东西是通过库link编辑到可执行文件中的。
添加 hello_LDFLAGS = -Wl,--whole-archive
(David Grayson 建议)会导致许多 "multiple definition" 错误。 Automake 将这些标志放在 link 命令的开头附近,因此它不仅适用于库。 Automake 建议不要直接在 hello_LDADD
中包含 linker 标志,其中指定了 link 的库。可以使用显式 Make 规则覆盖 Automake 规则(我可以将 linker 标志准确地放在我想要的位置),但是我可能 运行 其他标准 Make 规则(由Automake) 行为不端。
我会看看是否可以使用 dlopen
。
我猜你有一个 Linux 系统。然后确保该库构建为共享库(请参阅 here),而不是静态库。
具有 __attribute__(constructor)
的函数将在加载该共享库时调用,例如在 ld.so
时间,或者在 dlopen
时间,如果库是加载的插件。
顺便说一句,__attribute__(constructor)
在 C 中比在 C++ 中更有用。在 C++ 中,您并不真正需要它,因为您可以在其 class
中使用一些 明确定义的 构造函数来获得 static
数据,以实现相同的结果。
默认情况下,GCC 的 linker 只会在您的静态库 (link) 中 link 如果您的程序实际从中引用了某些符号。
只是使用图书馆
因此,让您的图书馆获得 linked 的一种方法是使用其中的一个符号。例如,您可以将其添加到 main.cc
:
void myfunc();
...
std::cout << (void *)&myfunc << std::endl;
或者您可以手动调用库中的一些初始化函数。在您这样做的时候,可能没有理由再使用 __attr__((constructor))
。
添加 linker 选项
或者,您可以按照 here 所述,尝试对 link 用户使用 -Wl,--whole-archive
选项。为此,您可以将此行添加到 src/Makefile.am:
hello_LDFLAGS = -Wl,--whole-archive
然而,这导致我的 GCC 版本为 libgcc.a
中的各种符号输出大量多重定义错误,所以我不知道这是否是一个真正的解决方案。
我最终使用了 -u link 选项,并包含了我实际需要的驱动程序档案中的驱动程序初始化代码。这似乎是合理的,因为它也是构建所有内容然后精确控制最终程序中的内容的好方法。我非常喜欢这种方式,因为我不再需要用包含的内容来监督编译步骤。我可以编译和存档所有内容。
所以当你 link:
gcc -Wl,-u,myconstructor1,-u,myconstructor2 -o prog ... -llib1 -llib2
可以根据应用程序的选定功能自动生成您需要的构造函数列表。尽管我还没有弄清楚如何使用 autotools 自动执行此操作。
如果我 link 将所有目标文件放在一个 link 中,我可以让 GNU __attribute__((constructor))
工作(对于 C++ 程序),但它不再工作,如果我将包含构造函数的目标文件存储在库中,然后 link 库而不是目标文件。我做错了什么?
Makefile.am:
SUBDIRS = src
src/Makefile.am:
bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh myfunc.cc
src/hello.cc:
#include <iostream> // for cout
#include <map>
#include "register.hh"
int main(int argc, char* argv[])
{
std::cout << "Hello, World!" << std::endl;
std::cout << "Have " << functions.size() << " functions registered."
<< std::endl;
for (Function_map::iterator it = functions.begin(); it != functions.end(); ++it) {
std::cout << "Registered " << (*it).first << std::endl;
(*it).second();
}
return 0;
}
src/register.cc:
#include <map>
#include <string>
#include "register.hh"
Function_map functions;
void register_function(const std::string& name, Function f)
{
functions[name] = f;
}
src/register.hh:
#ifndef REGISTER_H_
#define REGISTER_H_
#include <map>
#include <string>
typedef void (*Function)();
typedef std::map<const std::string, Function> Function_map;
extern Function_map functions;
void register_function(const std::string& name, Function f);
#endif
src/myfunc.cc:
#include "register.hh"
#include <iostream>
void myfunc()
{
std::cout << "This is myfunc!" << std::endl;
}
__attribute__((constructor))
void register_myfunc()
{
register_function("MYFUNC", myfunc);
}
configure.ac:
AC_PREREQ([2.69])
AC_INIT([hello], [1.4], [bugs@my.domain])
AC_CONFIG_SRCDIR([src/hello.cc])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_AUX_DIR([auxiliary])
AM_INIT_AUTOMAKE([-Wall -Werror])
AC_PROG_CXX
AM_PROG_AR
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
所以所有 C++ 文件都被编译成目标文件,这些文件 link 一起进入 'hello' 可执行文件。
生成的 'hello' 程序的输出是:
Hello, World!
Have 1 functions registered.
Registered MYFUNC
This is myfunc!
如果我将 src/Makefile.am 更改为
bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh
hello_LDADD = liblibrary.a
noinst_LIBRARIES = liblibrary.a
liblibrary_a_SOURCES = myfunc.cc
(即,myfunc.cc 编译成 myfunc.o,存储在 liblibrary.a 中,后者与其他目标文件一起 link 编辑成 'hello'),那么 'hello' 的输出是
Hello, World!
Have 0 functions registered.
所以现在 'register_myfunc' 函数没有被执行。为什么不呢?
已编辑 2015-02-22(回应 Basile Starynkevitch 的回答):我使用的是 GNU/Linux (Fedora 20) 系统。我尝试使用 libtools 构建共享库,但没有成功。我调整了 src/Makefile.am 如下:
bin_PROGRAMS = hello
hello_SOURCES = hello.cc register.cc register.hh
hello_LDADD = liblibrary.la
noinst_LTLIBRARIES = liblibrary.la
liblibrary_la_SOURCES = myfunc.cc
liblibrary_la_LDFLAGS = -shared -fPIC
(首先只有 -shared
,后来也有 -fPIC
)并将 LT_INIT
添加到 configure.ac,但这并没有改变结果。我将尝试你提到的用于 C++ 的 "static data with explicit constructor" 技巧,但我仍然想知道如何让我的示例与 __attribute__((constructor))
.
已编辑 2015-02-23 我尝试了 "static data with explicit constructor" 技巧但得到了与以前相同的结果:如果所有对象文件都是 link明确地一起编辑到一个可执行文件中,但如果我想要自动构建的东西是通过库link编辑到可执行文件中的。
添加 hello_LDFLAGS = -Wl,--whole-archive
(David Grayson 建议)会导致许多 "multiple definition" 错误。 Automake 将这些标志放在 link 命令的开头附近,因此它不仅适用于库。 Automake 建议不要直接在 hello_LDADD
中包含 linker 标志,其中指定了 link 的库。可以使用显式 Make 规则覆盖 Automake 规则(我可以将 linker 标志准确地放在我想要的位置),但是我可能 运行 其他标准 Make 规则(由Automake) 行为不端。
我会看看是否可以使用 dlopen
。
我猜你有一个 Linux 系统。然后确保该库构建为共享库(请参阅 here),而不是静态库。
具有 __attribute__(constructor)
的函数将在加载该共享库时调用,例如在 ld.so
时间,或者在 dlopen
时间,如果库是加载的插件。
顺便说一句,__attribute__(constructor)
在 C 中比在 C++ 中更有用。在 C++ 中,您并不真正需要它,因为您可以在其 class
中使用一些 明确定义的 构造函数来获得 static
数据,以实现相同的结果。
默认情况下,GCC 的 linker 只会在您的静态库 (link) 中 link 如果您的程序实际从中引用了某些符号。
只是使用图书馆
因此,让您的图书馆获得 linked 的一种方法是使用其中的一个符号。例如,您可以将其添加到 main.cc
:
void myfunc();
...
std::cout << (void *)&myfunc << std::endl;
或者您可以手动调用库中的一些初始化函数。在您这样做的时候,可能没有理由再使用 __attr__((constructor))
。
添加 linker 选项
或者,您可以按照 here 所述,尝试对 link 用户使用 -Wl,--whole-archive
选项。为此,您可以将此行添加到 src/Makefile.am:
hello_LDFLAGS = -Wl,--whole-archive
然而,这导致我的 GCC 版本为 libgcc.a
中的各种符号输出大量多重定义错误,所以我不知道这是否是一个真正的解决方案。
我最终使用了 -u link 选项,并包含了我实际需要的驱动程序档案中的驱动程序初始化代码。这似乎是合理的,因为它也是构建所有内容然后精确控制最终程序中的内容的好方法。我非常喜欢这种方式,因为我不再需要用包含的内容来监督编译步骤。我可以编译和存档所有内容。
所以当你 link:
gcc -Wl,-u,myconstructor1,-u,myconstructor2 -o prog ... -llib1 -llib2
可以根据应用程序的选定功能自动生成您需要的构造函数列表。尽管我还没有弄清楚如何使用 autotools 自动执行此操作。