上篇文章《C++自动注册的工厂与--whole-archive》提到了--whole-archive
选项在自动工厂示例的必要,“貌似也没其他方法了”。
这篇文章介绍另一种可以替代的方式,并分析其优缺点,采用的代码示例同上篇文章。文章最后附代码。
ld
链接器提供了另外一组选项解决符号依赖的问题。
- -u symbol
- --undefined=symbol
上面的选项用来告诉链接器symbol
符号在最终的输出文件里是未定义的,需要从其他模块拉进去。
这样我们就可以指定依赖的符号,让链接器强制将pay_handler.cpp
生成的目标文件链接到可执行文件。
由于C++允许函数重载,函数名不具有唯一性,编译器需要对名字进行改编,也就是name mangling。所以不能简单的将函数名写在链接选项后面,需要指定实际改编后的符号。
pay_handler.cpp文件如下:
- #include "msg_handler.h"
- #include
-
-
- class PayHandler {
- public:
- PayHandler() {
- register_msg_handler("pay", PayHandler::handle);
- }
-
-
- static bool handle(std::string_view msg_data) {
- printf("pay handle\n");
- return true;
- }
- };
-
-
- static PayHandler pay_handler;
假设我们要找handle
函数的符号,整个流程如下:
(1)编译pay_handler.cpp
为目标文件,符号信息都存储在目标文件里
$ g++ -c pay_handler.cpp
(2)使用nm获取所有符号,结合grep命令搜索疑似符号
- $ nm -g pay_handler.o | grep handle
- U _Z20register_msg_handlerPKcSt8functionIFbSt17basic_string_viewIcSt11char_traitsIcEEEE
- 0000000000000000 W _ZN10PayHandler6handleESt17basic_string_viewIcSt11char_traitsIcEE
- 0000000000000000 W _ZNSt17_Function_handlerIFbSt17basic_string_viewIcSt11char_traitsIcEEEPS4_E10_M_managerERSt9_Any_dataRKS7_St18_Manager_operation
- 0000000000000000 W _ZNSt17_Function_handlerIFbSt17basic_string_viewIcSt11char_traitsIcEEEPS4_E9_M_invokeERKSt9_Any_dataOS3_
(3)使用c++filt命令还原改编之前的符号,确定最终的符号
- $ c++filt _ZN10PayHandler6handleESt17basic_string_viewIcSt11char_traitsIcEE
- PayHandler::handle(std::basic_string_view<char, std::char_traits<char> >)
最终的CMakeList.txt如下:
- cmake_minimum_required (VERSION 3.24.0)
- project(main)
-
-
- add_library(payhandler STATIC pay_handler.cpp)
- add_library(msghandler STATIC msg_handler.cpp)
-
-
- add_executable(${PROJECT_NAME} main.cpp)
-
-
- target_link_libraries(
- ${PROJECT_NAME}
- msghandler
- payhandler
- -u_ZN10PayHandler6handleESt17basic_string_viewIcSt11char_traitsIcEE
- )
两个方面分析:
跨编译器
cmake内置支持--whole-archive
,所以可以跨编译器,而本篇介绍的选项需要指定符号,而符号是跟具体编译器相关的,所以不具备跨编译器的能力。
对文件大小的影响
--whole-archive
会将静态库的所有目标文件链接进来,最终的输出文件可能会比较大;而本篇介绍的选项则只加载符号所依赖的目标文件。
所以要综合考虑以上两个方面选择适合的选项。
msg_handler.h
- #include
- #include
-
-
- using MsgHandler = std::function<bool(std::string_view msg_data)>;
-
-
- // 注册消息处理器
- void register_msg_handler(const char *msg_type, MsgHandler handler);
-
-
- // 获取指定消息类型的处理器
- MsgHandler* get_msg_handler(const char *msg_type);
msg_handler.cpp
- #include
- #include
-
-
- #include "msg_handler.h"
-
-
- static std::map
& get_map() { - static std::map
map_handlers; - return map_handlers;
- }
-
-
- void register_msg_handler(const char *msg_type, MsgHandler handler) {
- get_map()[msg_type] = handler;
- }
-
-
- MsgHandler* get_msg_handler(const char *msg_type) {
- auto& m = get_map();
- auto it = m.find(msg_type);
- if (it != m.end()) {
- return &it->second;
- }
- else {
- return nullptr;
- }
- }
pay_handler.cpp
- #include "msg_handler.h"
- #include
-
-
- class PayHandler {
- public:
- PayHandler() {
- register_msg_handler("pay", PayHandler::handle);
- }
-
-
- static bool handle(std::string_view msg_data) {
- printf("pay handle\n");
- return true;
- }
- };
-
-
- static PayHandler pay_handler
main.cpp
- #include "msg_handler.h"
- #include
-
-
- int main() {
- MsgHandler* handle = get_msg_handler("pay");
- if (handle) {
- (*handle)("test data");
- }
- else {
- printf("not found\n");
- }
-
-
- return 0;
- }