首页 > 其他分享 >编译静态库遇到的 LNK2019 报错

编译静态库遇到的 LNK2019 报错

时间:2023-05-25 19:22:58浏览次数:52  
标签:std LNK2019 定义 void 编译 报错 ostream os MakeCheckOpValueString

前文提到了 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 关键字修饰。这样,每个源文件中包含头文件时,就相当于将命名空间中的定义直接内联到该源文件中,避免了多个源文件的重复定义。

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

 

标签:std,LNK2019,定义,void,编译,报错,ostream,os,MakeCheckOpValueString
From: https://www.cnblogs.com/strive-sun/p/17432629.html

相关文章

  • Nvm 安装node报错: The system cannot find the path specified.
    解决思路:1.确保你安装nvm之前node.js已经删除干净了。这一步如果不会请移步:https://blog.csdn.net/m0_51945510/article/details/127710792这个是要删除的。 2.确保你点击的安装路径中,没有空格和中文,并且确定存在这个目录(安装时,不会帮你新建文件夹)。  上面两张图只......
  • 记一次windows装docker,然后nacos连接宿主机mysql报错问题
    之前一直用linux装docker,这两天有空研究下windows上装DockerDesktop。安装步骤就不一一细说了,记录几个容易忘得地方。设置docker镜像存储位置//打包现有镜像wsl--exportdocker-desktop-data"D:\\work\\other-tools\\docker\\docker-desktop-data.tar"//注销镜像wsl--......
  • 报错问题:谷粒商城关于pubsub、publish报错,无法发送查询品牌信息的请求
    1、npminstall--savepubsub-js2、在src下的main.js中引用:①importPubSubfrom'pubsub-js'②Vue.prototype.PubSub=PubSub ......
  • OEM报错"Compliance score 51% is below critical threshold"
     OEM报错"Compliancescore51%isbelowcriticalthreshold" 具体邮件报警如下:点击链接,打开oem网页端,点击“查看相容性标准结果”: 选中其中一个安全建议,可以看到下边一般信息中建议打上安全补丁31720783,该补丁是linux版本的OracleDB最后一个PSU补丁(11.2.0.4.201020......
  • jupyter 报错 500 : internal server error
    之前代码搬迁服务器出了如下问题:jupyter报错500:internalservererror老服务器charset-normalizer的版本是3.0.1,但是看知乎有个方法如下:pipinstall--force-reinstallcharset-normalizer==3.1.0也可以解决问题,就没重装3.0.1......
  • UE4 AirSim Windows项目交叉编译Linux包
    1、从Linux环境下AirSim文件夹导入相关.a库至对应路径 2、项目插件中禁用VR相关插件libopenvr_api.soissuewhenbuildingprojectagainstUE4.25·Issue#2889·microsoft/AirSim(github.com) ......
  • stm32 编译出的bin文件一定是4字节的倍数吗?
    最近在研究固件升级,在烧写内部FLASH时突然产生一个问题编译出的bin文件一定是4字节的倍数吗?如果不是那么以bin文件总长度除以4的方式写入flash就有可能舍掉了最后的余数。在stackoverflow上得到的答案是:正常情况下编译产生的bin文件是4的倍数,但是并不一定是4字节的倍数,4字节对齐......
  • 如果tomcat报错怎么办
      勾选服务器块中第二栏......
  • windows下将Pikafish编译为安卓可执行文件
    下载AndroidNDKhttps://developer.android.com/ndk/downloads?hl=zh-cn下载Pikafish源码https://github.com/official-pikafish/Pikafish编译在Pikafish的src目录下创建如下bat文件setclang=D:\android-ndk-r25c\toolchains\llvm\prebuilt\windows-x86_64\bin\aar......
  • spring booot arthas报错
    Causedby:org.springframework.beans.BeanInstantiationException:Failedtoinstantiate[com.taobao.arthas.agent.attach.ArthasAgent]:Factorymethod'arthasAgent'threwexception;nestedexceptionisjava.lang.IllegalStateException:java.lang.ref......