Boost.DLL 使用静态链接库无法在可执行文件中找到符号

Boost.DLL cannot find symbols in executable using statically linked library

我目前正在编写一个小型测试应用程序来静态探索 linking 库并使用 Boost.DLL 在 运行 时间访问它们的符号。我正在编译一个非常简单的静态库和 link 它与一个非常简单的应用程序,使用 MinGW-w64,并使用 Makefile 和 mingw32-make.

A.cpp 定义一个 class A 来存储和修改对 int 的引用。库的唯一模块 B.cpp 定义了 A 的子 class 称为 B,以及一个工厂方法。主模块定义了一个 class C 来存储对 A 实例的引用,并尝试访问 B.cpp 中定义的符号来存储 [=22] 的实例=] 在 C 中。当我尝试 运行 应用程序时,输出文件已成功加载,但程序无法找到静态 linked 符号。

A.hpp:

#pragma once

#ifndef A_HPP_
#define A_HPP_

#include <memory>
#include <boost/dll/config.hpp>
#include <boost/dll.hpp>
#include <boost/function.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>

class A {
protected:
    int *_p;
public:
    A(int *p = NULL);
    virtual ~A() = default;
    
    void setp(int *p);
    int deref();

    virtual void add();
};

#endif

B.cpp:

#include "A.hpp"

class B : public A {
public:
    B();
    ~B();

    void add();
};

const size_t B_SIZE = sizeof(B);
void B_get(void *addr) {
    B *tmp = new B();
    memcpy(addr, tmp, B_SIZE);
    delete tmp;
}

B::B() {}
B::~B() {}

void B::add() {
    if (_p != NULL)
        *_p = *_p + 100;
    std::cout << "B: _p is now " << *_p << std::endl;
}

main.cpp:

#include "A.hpp"

class C {
public:
    std::shared_ptr<A> a;

    C(std::shared_ptr<A> aparam, int *x) : a(aparam) { a->setp(x); }
    int deref() { return a->deref(); }
};

int main() {
    std::string loc = boost::dll::program_location().string();

    std::cout << "loading file '" << loc << "'" << std::endl;
    boost::dll::shared_library lib(loc);
    
    std::cout << "getting symbol 'B_SIZE'" << std::endl;
    size_t B_SIZE = lib.get<size_t>("B_SIZE");

    std::cout << "getting symbol 'B_get(void*)'" << std::endl;
    boost::function<void(void*)> B_get = lib.get<void(void*)>("B_get");
    
    std::cout << "getting instance of B" << std::endl;
    void *b = new char[B_SIZE];
    B_get(b);
    
    //assign x and C
    int x = 5;
    C c(std::shared_ptr<A>((A*)b), &x);

    std::cout << "x: " << x << std::endl;
    std::cout << "c.a->deref(): " << c.a->deref() << std::endl;

    std::cout << "executing c.a->add()" << std::endl;
    c.a->add();

    std::cout << "x: " << x << std::endl;
    std::cout << "c.a->deref(): " << c.a->deref() << std::endl;
    
    return 0;
}

生成文件:

#build executable
out:
    g++ -Wall -c -IC:/Unix/boost_1_78_0/ B.cpp -o B.o
    ar -rcs -o libB.a B.o
    g++ -Wall -IC:/Unix/boost_1_78_0/ -LC:/Unix/boost_1_78_0/stage/lib -L. main.cpp A.cpp -llibboost_filesystem-mgw8-mt-x64-1_78 -llibboost_system-mgw8-mt-x64-1_78 -Wl,-Bstatic -lB -o out

#build and run executable
run: out
    ./out

输出:

C:\Unix\VSC\Static_Test>mingw32-make run
g++ -Wall -c -IC:/Unix/boost_1_78_0/ B.cpp -o B.o
ar -rcs -o libB.a B.o
g++ -Wall -IC:/Unix/boost_1_78_0/ -LC:/Unix/boost_1_78_0/stage/lib -L. main.cpp A.cpp -llibboost_filesystem-mgw8-mt-x64-1_78 -llibboost_system-mgw8-mt-x64-1_78 -Wl,-Bstatic -lB -o out
./out
loading file 'C:\Unix\VSC\Static_Test\out.exe'
getting symbol 'B_SIZE'
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  boost::dll::shared_library::get() failed: The specified procedure could not be found [system:127]
mingw32-make: *** [Makefile:9: run] Error 3

我已使用 nm 检查 out.exelibB.a,以及 ABC 的所有相关符号已定义,但 B 的符号在 out.exe.

中缺失

我也曾尝试使用标志 -g-O0 的组合来编译可执行文件,但没有成功。

据我了解,main.cpp 中包含的 B.cpp 的 header 不应该 来定位符号,也不是是否有任何符号导出限定符,如 extern "C"__declspec(dllexport)(尽管我也尝试了所有这些东西)。

boost::DLL 仅适用于 exported 符号。您的符号未导出。 “导出”符号超出了 C++ 语言的范围。需要使用 __declspec(dllexport) 或类似的方法来完成。

静态 linked 符号在运行时不可访问。 linker 丢弃它们。


错误提示“B_SIZE”未找到。 B_SIZE 在 B.cpp 中定义为

const size_t B_SIZE = sizeof(B);

此符号永远不会导出。它被静态地 link 编辑到您的 .exe 中,然后它的定义将被丢弃。

您或许可以将其更改为

const size_t __declspec(dllexport) B_SIZE = sizeof(B);

强制导出它(尽管导出变量而不是函数会带来它自己的问题)。然后,理论上,编译器应该将其标记为“export this”,并且 lib 命令 ar 应该在构建静态库“B.o”

时尊重此信息

最后,当执行 g++ -Wall ... exe -lB 命令中的隐式 linker 调用时,linker 应该将静态库“B.o”绑定到您的 exe ,找到“请导出我”标志并将 B_SIZE 的符号信息复制到 .exe.

仅当您 导出 符号时,信息才会写入 .dll。然后其他程序就可以查找了。


还有一点,隐藏在boost DLL教程中。在 Windows、__declspec(dllexport) 下,即使是 .EXE 文件也会导出符号。因此,如果您将 link 您的 B.o 静态地 link 到 .EXE,windows 工具 遵守 dllexport 标签并导出符号.微软和类似的 link 用户也是如此。

教程中关于 linux 的小注释说,您必须将 -rdynamic 传递给 linux linker。我在这里猜测,但由于从 .EXE 导出符号是不寻常的(标准是 .DLL),linux 工具可能会忽略所有 dllexport 标记并从运行时显式隐藏导出的符号 - 除非你明确告诉他们包括他们,这就是 -rdynamic 可能的目的。


顺便说一句,通常的做法是这样的:

#if defined(MYLIB_EXPORTS)
#define MYLIB_API __declspec(dllexport)
#else if defined(MYLIB_IMPORTS)
#define MYLIB_API __declspec(dllimport)
#else
#define MYLIB_API /* */
#endif

void MYLIB_API B_get(void*);