我如何让 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 数据,以实现相同的结果。

详情请阅读Drepper's paper: How to Write a Shared Library

默认情况下,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 自动执行此操作。