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.exe
和 libB.a
,以及 A
、B
和 C
的所有相关符号已定义,但 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*);
我目前正在编写一个小型测试应用程序来静态探索 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.exe
和 libB.a
,以及 A
、B
和 C
的所有相关符号已定义,但 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*);