前文提到了 CMake 学习

文末基本涵盖了我遇到的编译问题,但是在得到一个编译好的 .lib 文件后,还需要放到项目中引用成功后才算真正的完成静态库的编译

嗯,我之所以说这些是因为我在项目中链接静态库时出现了 LNK2019 经典错误

错误如下:

Libraryd.lib(at_exit.obj) : error LNK2019: unresolved external symbol "void __cdecl logging::MakeCheckOpValueString<class base::AtExitManager *>(class std::basic_ostream<char,struct std::char_traits<char> > *,class base::AtExitManager *)
Libraryd.lib(rand_util.obj) : error LNK2019: unresolved external symbol "void __cdecl logging::MakeCheckOpValueString<double>(class std::basic_ostream<char,struct std::char_traits<char> > *,double)"
Libraryd.lib(rand_util.obj) : error LNK2019: unresolved external symbol "void __cdecl logging::MakeCheckOpValueString<unsigned __int64>(class std::basic_ostream<char,struct std::char_traits<char> > *,unsigned __int64)"
Libraryd.lib(file_path.obj) : error LNK2019: unresolved external symbol "void __cdecl logging::MakeCheckOpValueString<wchar_t const *>(class std::basic_ostream<char,struct std::char_traits<char> > *,wchar_t const *)"

我们先看 .h 和 .cc 文件中对 MakeCheckOpValueString 的声明和定义吧

.h 文件

// Provide an overload for functions and function pointers. Function pointers
// don't implicitly convert to void* but do implicitly convert to bool, so
// without this function pointers are always printed as 1 or 0. (MSVC isn't
// standards-conforming here and converts function pointers to regular
// pointers, so this is a no-op for MSVC.)
template <typename T>
inline typename std::enable_if<
std::is_function<typename std::remove_pointer<T>::type>::value,
void>::type
MakeCheckOpValueString(std::ostream* os, const T& v) {
(*os) << reinterpret_cast<const void*>(v);
} // We need overloads for enums that don't support operator<<.
// (i.e. scoped enums where no operator<< overload was declared).
template <typename T>
inline typename std::enable_if<
std::is_enum<T>::value,
void>::type
MakeCheckOpValueString(std::ostream* os, const T& v) {
(*os) << static_cast<typename std::underlying_type<T>::type>(v);
} // We need an explicit overload for std::nullptr_t.
void MakeCheckOpValueString(std::ostream* os, std::nullptr_t p); template <typename T>
void MakeCheckOpValueString(std::ostream* os, T p);

.cc 文件

void MakeCheckOpValueString(std::ostream* os, std::nullptr_t p) {
(*os) << "nullptr";
} template <typename T>
void MakeCheckOpValueString(std::ostream* os, T p) {
(*os) << p;
}

调用的地方:

// Build the error message string.  This is separate from the "Impl"
// function template because it is not performance critical and so can
// be out of line, while the "Impl" code should be inline. Caller
// takes ownership of the returned string.
template<class t1, class t2>
std::string* MakeCheckOpString(const t1& v1, const t2& v2, const char* names) {
std::ostringstream ss;
ss << names << " (";
MakeCheckOpValueString(&ss, v1);
ss << " vs. ";
MakeCheckOpValueString(&ss, v2);
ss << ")";
std::string* msg = new std::string(ss.str());
return msg;
}

可以看出 MakeCheckOpValueString  的声明和定义都是完整的

MakeCheckOpValueString 有四个重载,有两个 inline 声明的,直接在头文件中定义了,还有两个普通的 void 型,声明和定义是分开的

函数的声明和定义是没有问题的,有问题的话,在编译静态库时就应该出错了

后面研究后,怀疑是没有给外部工程暴露接口,也就是缺少 BASE_EXPROT 宏

这个宏的写法如下:

#ifdef BASE_EXPORT
#undef BASE_EXPORT
#endif #ifdef _WIN32
// 定义 BASE_EXPORT 宏为适当的导出关键字,以便在 Windows 平台上导出函数
#define BASE_EXPORT __declspec(dllexport)
#else
// 定义 BASE_EXPORT 宏为适当的导出关键字,以便在其他平台上导出函数
#define BASE_EXPORT __attribute__((visibility("default")))
#endif

顺便看看 dllexport 和 dllimport 的区别,看看 GPT 的回答

dllexportdllimport 是用于在 Windows 平台上定义和使用动态链接库(DLL)中的函数和数据的关键字。

  • dllexport 用于在 DLL 中定义函数或数据,并指示编译器将这些符号导出到 DLL 中,以便其他模块(如应用程序)可以使用它们。当使用 dllexport 修饰符时,编译器会生成导出符号表,并在链接时使其可见。
  • dllimport 用于在应用程序中声明从 DLL 中导入的函数或数据。它告诉编译器该符号将在运行时从 DLL 中导入,而不是在当前模块中定义。使用 dllimport 修饰符可以让应用程序在编译时知道要导入的符号的位置和类型。

关键区别在于:

  • dllexport 用于在 DLL 中定义符号,即导出符号。
  • dllimport 用于在应用程序中声明从 DLL 中导入的符号。

这两个关键字的使用可以帮助实现动态链接库的功能,并支持在不同的模块之间共享函数和数据。在 Windows 平台上,它们是确保 DLL 的正确导出和导入的重要机制。

于是我们添加了 BASE_EXPORT 宏,并在 MakeCheckOpValueString 声明前都添加了该宏,

// We need an explicit overload for std::nullptr_t.
BASE_EXPORT void MakeCheckOpValueString(std::ostream* os, std::nullptr_t p); template <typename T>
BASE_EXPORT void MakeCheckOpValueString(std::ostream* os, T p);

重新编译静态库,并在项目中链接库,还是出现同样的问题

没办法,BASE_EXPORT 似乎不起作用,项目链接函数时仍然找不到函数对应的符号文件,只能从 inline 下手了,直接在头文件中内联,并删除 .cc 文件中的函数的定义

改成这样:

// We need an explicit overload for std::nullptr_t.
inline void MakeCheckOpValueString(std::ostream* os, std::nullptr_t p) {
(*os) << "nullptr";
} template <typename T>
inline void MakeCheckOpValueString(std::ostream* os, T p) {
(*os) << p;
} 

inline 关键词可以保证函数不会出现重复定义的错误,另外 inline 还有命名空间管理的特性

看看 inline 这两个细节的介绍

  1. 防止重定义(Multiple Definitions):在 C++ 中,如果同一个函数或变量在多个编译单元(源文件)中被定义,会导致重定义错误。为了解决这个问题,可以将函数或变量的定义放在头文件中,并使用 inline 关键字修饰,以便在多个编译单元中使用。这样,每个编译单元中对该函数或变量的定义都会被视为内联定义,避免了重定义错误。
  2. 命名空间的内联定义:在命名空间中定义的函数和变量,其声明和定义通常是分开的,即声明放在头文件中,定义放在源文件中。为了避免在每个源文件中都包含头文件,可以将命名空间中的函数和变量的定义放在头文件中,并使用 inline 关键字修饰。这样,每个源文件中包含头文件时,就相当于将命名空间中的定义直接内联到该源文件中,避免了多个源文件的重复定义。

最终,在重新编译静态库后,并在项目中链接它,没有出错,一切顺利

编译静态库遇到的 LNK2019 报错的更多相关文章

  1. android ndk-build 编译静态库libxx.a 以及Android studio openssl 静态库配置(cmake)

    android ndk-build 编译静态库libxx.a 需求场景: 目前有安卓编码好的现在的openssl的两个.a,我们需要调用openssl的函数,并把功能再封装成.a; 这样使用时,在an ...

  2. 【iOS】编译静态库

    与java和.net一样,objc也由类库的概念,不过在在objc上一般叫库,库表示程序代码集合,可以共享给其他程序使用,库是编译后的二进制文件,因此不能看到源代码,多用于一些开放sdk(如百度地图s ...

  3. 关于使用Visual编译静态库动态库及其使用的问题

    本文主要讲述了如何使用Visual Studio 2013 编译静态库和动态库,并使用. 一.静态库 1.  编写静态库 若要创建将引用并使用刚创建的静态库的应用程序,请从“文件”菜单中选择“新建”, ...

  4. 【转】TI DSP C6657学习之——编译静态库.lib

    熟悉C++开发的的小伙伴都知道,我们一般代码中往往要引入许多第三方编译好的库,有些是静态链接库static library, 有些是动态链接库dll.引入库的目的一是减少代码的编译时间,二是只提供函数 ...

  5. Makefile 编译静态库文件及链接静态库

    本文为原创文章,转载需指明该文链接 1.代码目录结构如下: comm/ comm/inc/apue.h  3 atexit.c Makefile  5 staticlib/lib/ staticlib ...

  6. SAP 对HU做转库操作,系统报错 - 系统状态HUAS是活动的 - 分析

    SAP 对HU做转库操作,系统报错 - 系统状态HUAS是活动的 - 分析 近日收到业务团队报的问题,说是对某个HU做转库时候,系统报错.如下图示: HU里有是三个序列号, 1191111034011 ...

  7. opencv编译静态库时选择MD模式无效的原因

    在Cmake-gui上看到的明明是MD运行库依赖,生成MS项目时却变成了MT运行库依赖. 原因在于编译静态库时内部做了自动替换.

  8. 编译静态库的方式使用spdlog和fmt

    前言 spdlog++库,而且支持header only方式,但header only的使用方式会造成编译时长增加,所以这里简单描述一下,其编译静态库的方式. 又因为spdlog还依赖另一个开源库fm ...

  9. Xcode 之自己编译静态库

    今天介绍下,如何利用Xcode,新建一个静态库,以及如何编译成i386.armv7.armv7s 等平台架构. 开发环境:MAC OS X 10.9.4 + Xcode 5.0.2 背景知识:库分两种 ...

  10. GCC实现多文件编译,静态库,动态库

    一 代码 //add.h int add(int a, int b); //add.c int add(int a, int b) {     return a+b; } //main.c #incl ...

随机推荐

  1. [转帖]经典的 Fork 炸弹解析

    https://linux.cn/article-5685-1.html Jaromil 在 2002 年设计了最为精简的一个 Linux Fork 炸弹,整行代码只有 13 个字符(包括空格在内,空 ...

  2. [转帖]TiKV 缩容不掉如何解决?

    TiKV节点缩容不掉,通常遇到的情况: 1.经常遇到的情况是:3个节点的tikv集群缩容肯定会一直卡着,因为没有新节点接受要下线kv的region peer. 2.另外就是除缩容tikv外,剩下的KV ...

  3. [转帖]SIMD指令集 SSE/AVX

    SIMD指令集 SSE/AVX 概述 参考手册 Intel Intrinsics Guide Tommesani.com Docs Intel 64 and IA-32 Architectures S ...

  4. [转帖]linux 内核协议栈 TCP time_wait 原理、配置、副作用

    https://my.oschina.net/u/4087916/blog/3051356   0. 手把手教你做中间件.高性能服务器.分布式存储技术交流群 手把手教你做中间件.高性能服务器.分布式存 ...

  5. Python学习之十九_程序运行时间的验证

    Python学习之十九_程序运行时间的验证 背景 最近一段时间比较忙. 而且还遇到了一个lua脚本优化redis访问的场景. 想着自己还在学习python(时断时续) 所以想借着这个场景,学习一下py ...

  6. CS231N Assigenment1 two_layer_net笔记

    two_layer_net.ipynb 之前对 x.reshape(x.shape[0], -1)语句的输出结果理解一直有误: 1 x = [[1,4,7,2],[2,5,7,4]] 2 x = np ...

  7. 【贪心】AGC018C Coins

    Problem Link 现在有 \(X+Y+Z\) 个人,第 \(i\) 个人有三个权值 \(a_i,b_i,c_i\),现在要求依次选出 \(X\) 个人,\(Y\) 个人和 \(Z\) 个人(一 ...

  8. http 中使用 gzip 输出内容时,如何预先压缩前一半页面?

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 背景是这样:要输出一个很大的动态页面,不开 gzip 压缩 ...

  9. 【JS 逆向百例】某音 X-Bogus 逆向分析,JSVMP 纯算法还原

    声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...

  10. JS 逆向之 Hook,吃着火锅唱着歌,突然就被麻匪劫了!

    关注微信公众号:K哥爬虫,QQ交流群:808574309,持续分享爬虫进阶.JS/安卓逆向等技术干货! 什么是 Hook? Hook 中文译为钩子,Hook 实际上是 Windows 中提供的一种用以 ...