当前位置: 首页 > news >正文

【C++】C++库:如何链接外部库、静态链接和动态链接,以及如何自建库并使用

十三、C++库:如何链接外部库、静态链接和动态链接,以及如何自建库并使用

本篇讲C++库,先讲如何在项目中使用外部库,包括静态链接和动态链接的实现;再讲如何在VisualStudio中自建模块或库项目,让所有项目都能使用。先讲牵扯到的理论,再展示实操。

我们使用其他编程语言,比如python时,其包管理器非常好用,添加库是一件非常简单的事情。先pip安装,然后import引入即可,简单易用。感兴趣的同学可以参考我之前写的python博文: https://blog.csdn.net/friday1203/article/details/138354754?spm=1001.2014.3001.5501 中的最后一个小标题。

但是到了C++,情况就完全不同了。当我们引用C++外部库时,基本上我们理想的项目设置是:如果你要迁出别人的远程程序库(比如github上的代码仓库),那你就应该,在你的存储库中有所有你需要的东西,以便你可以直接编译和运行项目的应用程序,而不需要考虑使用包管理器去下载其他你需要的外部库。所以,对于C++来说,最好的方法就是克隆存储库、然后编译和运行就ok了。也所以,我们一般在实际解决方案中的实际项目文件夹中,保留使用的库的版本,也就是我实际上是有那些库的代码的副本或物理二进制文件的

但是实际情况是:对于一些严肃的项目,我们推荐你一定要取得外部库的源代码(就是代码的副本),然后自己编译源代码。如果你使用的是visualstudio,你可以添加另一个项目,该项目包含你的依赖库的源代码,然后将其编译为静态库或动态库,供你的项目使用。也就是本篇第5小标题讲的内容。
但是,如果你拿不到源代码,或者你的项目只是一个不重要的项目,而且你也不想费时费力,此时你手里有的就是外部库所有者发布的、已经编译好的、各种版本(不同平台和系统的)的二进制文件(pre-compiled binaries),那此时你使用别人已经编译好的二进制文件也是可以的,而且还更快更容易。也就是本篇第3、4小标题讲的内容。

1、以GLFW库为例,简单介绍一下外部库
GLFW全称是"Graphics Library Framework",是一个开源、跨平台的C语言库,主要用于管理输入,包括键盘、鼠标输入等。用于在计算机上创建和管理图形窗口,是OpenGL和现代OpenGL(即OpenGL ES)上下文的扩展库。本篇的静态链接和动态链接外部库都以它为例。

(1)从GLFW官网上下载GLFW库

先进入GLFW官网,先不用下载源代码(上图2处),下载windows版本的预编译文件(上图4处),因为我要拿它展示如何静态和动态的链接外部二进制库文件。

(2)下载的GLFW二进制库文件介绍

下载完毕后是一个压缩包,打开压缩包(上图5处),这是一个C++库的典型组织结构,这里我们先简单介绍一下这些文件:

第一个文件夹docs:打开基本都是html页面文件、js前端页面格式文件、png等页面中的图片等这些文件。直白的讲,就是这些文件构成了这个我们下载的 glfw-3.4.bin/win32.zip 这个文件的页面介绍。这个文件夹可有可无,对我们代码运行毫无关系。只是如果你以后想修改外部库或微小改动外部库时,这是个参考文档。

第二个文件夹include:include的中文翻译是"包含"的意思,所以后面我们都叫它包含目录。这个目录里面(如上图5处所示)是2个.h头文件。头文件里面都是库GLFW所涉及到的变量、函数、类、结构体等对象符号(就是名称)的声明。至于头文件是干啥的有啥用,可以参考我另外一篇博文https://blog.csdn.net/friday1203/article/details/139637472?spm=1001.2014.3001.5501 编译原理,以及https://blog.csdn.net/friday1203/article/details/139737861?spm=1001.2014.3001.5501 中的"头文件"知识点,你就更加不惑了。

第三个文件夹lib-mingw-w64:MinGW是GNU工具(包括编译器GCC和GNU binutils和调试器GDB等)在Win32上的一个移植,是从Cygwin里fork出来的,当初只考虑32位。MinGW-W64是从MinGWork出来的提供扩充x64支持。
我们打开这个文件夹可以看到是一个libglfw3.a文件、一个glfw.dll文件和一个libglfw3dll.a文件。
.a文件是由GLFW的一个或多个相关源文件->通过GCC编译后->生成的一个或多个.o文件->然后使用ar命令将这些.o文件打包成的一个静态库文件.a文件。所以libglfw3.a就是静态链接库文件,也所以libglfw3.a文件里面都是GLFW中实现功能的变量、函数、类等、并且是已经被编译成了二进制的文件。
‌.dll文件是Dynamic Link Library(动态链接库)文件的缩写‌。当你动态链接外部库时需要用到这个文件。它是你的源代码编译后生成的.exe程序在执行时需要调用的文件。因为这样可以减小你的程序内存占用过大问题,使得你的程序更轻量。所以glfw.dll文件就是GLFW库的动态链接库文件,供使用者动态链接的,里面也是实现功能的变量、函数、类等、并且已经被编译成了二进制了。
libglfw3dll.a文件则是和glfw.dll搭配一起使用的文件,也是用于动态链接。因为它里面都是一堆指向动态库(glfw.dll库文件)中的所有变量和函数的指针,就是都是一堆变量和函数的位置信息,这样链接器就可以直接链接到这些变量和函数了。

第四个文件夹lib-static-ucrt:是Microsoft Visual Studio中用于链接静态库的Universal C Runtime (UCRT)库。在Visual Studio的编译和链接过程中,选择使用静态库(Static Library)时会涉及到这个库。也就是,当你选择使用静态链接方式编译代码时,Visual Studio会包含这个库以确保程序的正确运行。所以当你选择静态链接方式意味着将UCRT库直接嵌入到最终的可执行文件中,这样可以在没有安装UCRT的环境中运行程序,但会增加可执行文件的大小‌。

后面的lib-vcxxxx等5个文件夹:你首先要了解的是,Visual Studio‌是一由‌微软公司开发的集成开发环境(IDE),支持多种编程语言,包括‌C++、‌C#、‌Visual Basic等。不甚了解的同学可以参考https://blog.csdn.net/friday1203/article/details/139568486?spm=1001.2014.3001.5501 中的"写C++程序的基本流程",以及https://blog.csdn.net/friday1203/article/details/139737861?spm=1001.2014.3001.5501 中的"VS项目设置"这些知识点。而VC++‌(Visual C++)也是微软公司的C++开发工具,是Visual Studio中的一个组成部分,随着Visual Studio版本的更新,VC++也随之更新。例如,Visual Studio 2010中包含了VC2010,Visual Studio 2015中包含了VC2015。因此,VC++实际上是Visual Studio中的一个特定于C++的开发环境。
所以,这5个文件夹是针对不同VS版本编译的库文件夹,而且每个版本都支持静态链接和动态链接两种方式。下面我们针对每个文件一一说明:
如果你选择静态链接,那你使用glfw3.lib这个静态链接库文件即可。此后的小标题3展示的就是这种方式。
如果你选择动态链接,那你得使用glfw3.dll和glfw3dll.lib两个文件。glfw3.dll是动态链接库文件,是库GLFW所涉及到的变量、函数、类、结构体等对象符号(就是名称)的定义。是你的.exe程序在执行时动态调用的文件。所以是和你的.exe程序文件放在一起的。而glfw3dll.lib文件实际上是一个静态库,是联合glfw3.dll一起使用的,因为glfw3dll.lib中是一堆glfw3.dll中的所有函数、符号的位置,可供链接器在编译时链接它们。但是如果没有glfw3dll.lib这个文件,那我们就需要通过函数名来访问glfw3.dll中的函数了。所以说二者是搭配使用的,也所以叫"静态的"动态库版本。此后的小标题4展示的就是这种方式。
最后还有一个glfw3_mt.lib文件,这个文件是支持多线程操作的静态库文件,它可以在多线程环境中安全地使用。意思就是这个文件是将GLFW的源代码编译成支持多线程环境,可以在多线程应用程序中使用的二进制库文件。这种库文件通常用于提高程序的并发性能和响应速度,特别是在需要同时处理多个任务或请求的应用场景中。

这些基本上就是我们在官网下载的、适用于window平台的、预编译后的、二进制的、glfw压缩包,里面的所有东西了。

(3)小结:
上面的文件夹基本上就是三类:第一类是库的介绍文件;第二类是库的头文件;第三类是针对不同平台(32位还是64位)、不同系统(不同的编译器编译)的、用于静态链接方式的静态库文件,和用于动态链接方式的动态库文件。当然你想静态链接还是动态链接,那得看你的项目适合哪种方式,因为两种方式各有利弊、各有优缺点。

include包含目录中的.h文件,是头文件,是你自己项目预编译时使用的。
静态库文件.lib文件(真实的变量和函数定义所在的文件),是你自己项目在静态编译和静态链接阶段要使用的。
动态库文件(.dll文件和dll.lib文件),其中dll.lib文件是你自己项目在静态编译和静态链接阶段要使用的,而.dll文件则是你项目的.exe可执行文件在执行过程中需要动态链接的。

清楚这些后,以后你要引用别的外部库时,你要下载什么包(源码包还是二进制包)?从你下载的包中拷贝哪些适用于你项目的文件?你心里就有底了。

2、再强调一些基本概念
如果前面讲的内容你不是太明白,本小标题就是针对上面的查漏补缺,或者是针对上面的重复强调。

(1)首先我想说的是,上文中几个链接一定要先吃透,尤其是编译原理部分。编译原理各个子流程你不懂,后面你肯定没法懂。

(2)一般我们下载外部库,其实我们就是需要:include和library,包含目录和库目录:
包含目录(就是include)里面是一堆我们需要使用的头文件。头文件的作用就是提供声明,告诉编译器哪些函数是可用的,就是告诉编译器你放心编译吧,源码中的这些函数在库文件中都是有定义的。所以有了这些头文件,我们自己的源代码中使用这个库中函数或者类啥的,至少就可以预编译了,否则你预编译阶段都过不了。当然你也可以选择不要第三方库的头文件,而是自己写它们的函数声明,这也是可以的,后面静态链接实操中会有相关展示。

库目录(就是.lib文件或者.dll文件)是一个已经预先编译了的二进制库文件。这两个东西才是最最有价值的东西,因为二者都是真实的变量和函数定义所在的文件,只不过一个是用于静态链接,一个是用于动态链接。

还要说明的是,头文件是同时支持静态和动态链接的。当预编译完毕后,就进入真正的编译阶段,此阶段源程序就会被超级细颗粒的打散,然后词法分析、语法分析、语义分析,生成目标代码,或者说最后生成汇编码。所以此时的汇编码也是零散的。所以下一个阶段就是链接阶段。当你是静态链接时,链接器是把编译的所有颗粒链接到一起,这样就可以正确执行了。当你是动态链接时,链接操作不仅发生在代码静态编译,还会发生在程序被加载时以及程序执行时。但是不管发生在什么阶段,链接操作的本质都是找到每个符号和函数在哪里,并把它们连接起来。

(3)什么是静态链接、动态链接?
静态链接意味着这个静态库(.lib库文件)会被放到你的可执行文件中,它是嵌入在你的exe文件中、或者其他操作系统下的,和你的程序合并成一个可执行文件了。

而动态链接则意味着这个动态库(.dll库文件)是在你自己的应用程序运行时被调用的、是独立于你的应用程序之外的、是你的应用程序运行时,随用随调的、肩并肩作战的。所以.dll库文件一般是和你的.exe文件放在同一个文件夹下面的。那我的.exe文件如何能随用随调.dll?方法一是,你的应用程序启动时,就加载与.dll文件搭配的dll.lib文件,就是上文说的静态的动态库。dll.lib中都是.dll中的函数的地址,就直接按照地址执行了。方法二是,如果你没有dll.lib文件,那你得有一个叫loadLibrary的函数或其他类似功能的函数。当你的应用程序运行时,loadLibrary函数就载入.dll动态库,从中拉出函数,然后再调用函数。

(4)静态链接和动态链接的区别
静态链接和动态链接的主要区别就是,库文件是否被编译到exe文件中或链接到exe文件中,还是只是一个单独的文件,在运行时你把它放在你的exe文件旁边或某个地方,然后你的exe文件就可以加载它。
也所以,动态链接是链接发生在运行时,静态链接是在编译时发生的。现实中很多情况我们是更想静态链接而不是动态链接。所以即使你想动态链接,你也得清楚静态链接是怎么回事。
静态链接是发生在编译阶段,程序加载及执行时就不用链接了。
动态链接是发生在运行时,就是只有当你真正启动你的可执行文件时,你的动态链接库才会被加载。所以它实际上不是可执行文件的一部分。 当你启动一个普通的可执行文件时,这个可执行文件就会被加载到内存,然而如果此时有一个动态链接库,这就意味着你的可执行文件在运行中有一个功能是,链接另外一个外部二进制文件,这就是动态的链接。这样当你运行你的可执行程序时,就会实现将一个额外的文件加载到内存中。那现在你的可执行程序的工作方式就变了,因为它在运行时需要某个外部库、某些动态库、某些外部文件。所以在你的可执行程序运行前,你要具备这些外部库、文件等东西,否则你的程序无法顺利运行。也所以我们会经常看到这样的场景:当你启动一个应用程序时,它弹出一个错误消息,比如没有找到dll,或者说需要dll等信息,而不能顺利启动这个程序。这就是动态链接的一种形式。所以之所以有动态链接方式,是因为可执行程序是知道有动态链接库的存在的,动态链接库是可执行程序顺利运行的必须。但是动态链接库也是一个单独的文件、或者是一个单独的模块,并且是在运行时加载的。所以一旦你加载失败,可执行程序就得被迫中断。
也所以你也可以动态的加载动态库,这样可执行文件就与动态库完全没有任何关系了,此时你可以启动你的可执行文件,也就是你的应用程序,它甚至不会要求你包含一个特定的动态库。但是在你的可执行文件中,你可以写代码去查找并运行时加载某些动态库,然后获得函数指针或者其他任何你需要的那个动态库中的东西,也就是使用那个动态库。也所以有些第三方发布的动态库,有时是会是一个"静态的"动态库版本!就是我的应用程序现场需要这个动态链接库,我已经知道里面有什么函数,我可以使用什么。
而另外的版本则是,我想任意加载这个动态库,我甚至都不知道里面有什么,但我就是想从那里取出一些东西,或者我就是想用它做我的事情。 这两种动态库,他们都有自己特定的用途。我们后面小标题4的示例就是第一种"静态的"动态库。就是我知道我的应用程序需要这个库,但我将动态地链接它。

(5)静态链接和动态链接的优缺点
静态链接和动态链接是有实际的性能差异的。静态链接在技术上更快,因为编译器和链接器可以看到全部的、需要链接的变量和函数,此时编译器或链接器实际上还可以执行链接时优化之类的操作。所以静态链接在技术上可以产生更快的应用程序,因为有很多优化方法可以被自动应用。
而对于动态库,编译器和链接器不知道后面会发生什么事情,就只能保存全部的变量和函数,只有当动态链接库被运行时的程序装载时,程序才被补充完整,此时就已经错过很多优化的机会。

事物总是有它的相反一面,使用静态链接会使你的程序更大、更占用内存。而动态链接更节省内存占用。所以要看你自己项目的情况。

(6)在visual studio项目中:
对于编译器,我们必须把它指向头文件(.h文件),也就是我们就有了包含目录中的变量和函数的符号声明,就是编译器就知道哪些变量名和函数名是可用的。也就是引入了名字。
对于链接器,我们必须把它指向库文件(不管是静态库还是动态库都要这样操作),就是告诉链接器,这是我的库文件,里面有变量名和函数名的定义,你把它们链接起来。这样我们的程序就能执行正确的变量和函数的定义。只不过在静态链接方式下,链接操作只发生在静态编译和静态链接阶段。而在动态链接方式下,链接操作除了静态编译阶段发生,还发生在项目程序的加载和执行阶段。
链接的本质不就是根据名称、参数、返回值类型三要素来匹配实现链接的。或者说通过这三要素来找到正确的函数的。

3、静态链接glfw二进制库文件的过程展示
上面的理论部分都清晰了后,我们开始实操如何添加二进制外部静态库。

(1)下图是对项目的介绍和一些前期的准备:

(2)设置编译器的相关设置

下一步我们就开始设置编译器的相关设置,也就是把GLFW库的include包含目录和.lib库文件的实际地址告诉编译器,让编译器可以找到GLFW库的头文件和静态库文件:

这样编译器就可以找到glfw的头文件了,有没有成功,我们看看代码有没有报错即可:

编译器可以顺利找到glfw库的头文件了,也就是glfw库的各种函数各种类等的声明编译器都可以用了,编译器就可以顺利编译了。

(3)设置链接器的相关设置
下面我们开始设置链接器的相关设置,让链接器可以顺利找到glfw这个外部库的glfw3.lib静态库文件,这样我们就可以调用glfw库中的函数和类等的定义了:

我们调用一个glfw库中的函数,看看链接器是否可以顺利链接上glfw库中的函数:

至此我们就成功静态链接了GLFW库,也就是可以正常使用GLFW库了。

(4)最后再澄清的一些事情:Name-mangling问题

上述两种情况,我们都可以顺利编译、顺利链接!那是因为语句A实际上只是提供了函数glfwInit()的声明,而我也可以自己写函数glfwInit()的定义,就是上图的B处, 甚至我也可以A都不要,完全自己写一个glfwInit()定义(就是情况1)就可以编译和链接了。所以我们可以看出,上述的两种情况其实根本就没有调用GLFW库中真正的glfwInit()函数的定义。因为真正的glfwInit()函数返回的是1(上上图所示)。
GLFW库实际上是一个C语言库,上面两种情况就是我们用C++混淆了名字(Name-mangling)。

也所以,其实我们是可以手动添加glfwInit函数的定义:

也所以,头文件和库的链接的作用都只是将项目的所有组成部分链接在一起。
也就是,头文件的作用就是提供声明,告诉编译器哪些函数是可用的,就是告诉编译器你放心编译吧,我源码中的这些函数在库文件中都是有定义的。
当编译器编译完毕后,链接器就开始上场,链接器要顺利链接到这些函数。这样当源代码中有调用这些函数的时候,就正确跳到了这些函数的定义代码,也就是正确执行了这些函数的功能。

4、动态链接GLFW二进制库文件的过程展示
上面的静态链接,我们包括了头文件、添加了静态库。所以此处的动态链接,头文件我们就不需要更改了,只需要把静态库改成动态库就行了。具体步骤如下:
(1)拷贝文件:把静态库改成动态库

我们要确保把glfw.dll文件放在一个可访问的地方。你可以放在整个应用程序中,然后设置库搜索位置。但是放在和可执行文件同一个文件夹里,是一定没有问题的,因为这是首选的自动搜索路径。

(2)设置链接器的相关设置
因为头文件是不需要改的,因为头文件是同时支持静态和动态链接的。所以我们就不需要设置编译器的相关设置了,只要设置链接器即可,而且只要添加文件名即可,路径还用静态链接时的路径:

glfw3.dll文件就是动态库文件,里面都是GLFW库中的变量、函数、类等的定义。
glfw3dll.lib文件基本上就是一堆指向.dll文件的指针。这样我们就不用在运行时去检索所有东西的位置。
说明:我们一定要同时编译这两个文件!因为如果你尝试使用不同的静态库,在运行时链接到dll,你可能会得到不匹配的函数和错误类型的内存地址,函数指针不会正常工作,所以这两个文件是由glfw发行的,所以它们是同时编译的,它俩是直接相关的,是不能分开的。

(3)看看我们的是否链接成功:

至此,我们也演示了如何使用GLFW动态库。

5、在VisualStudio中自建模块或库项目,并进行静态链接
本小标题讲如何在VisualStudio中创建多个项目,并且再创建一个库,让所有项目都能使用。
如果你的项目规模很大时,我们这样做就是创建模块或库,就可以多次重用这些代码了,而且还允许我们混合语言。本部分我们展示,继续使用C++,如何创建一个当作库的项目,尤其是当作一个静态库,然后把它链接到一个可执行文件中。

‌(1)了解Visual Studio的解决方案(Solution)和项目(Project)的关系
Visual Studio的解决方案(Solution)是一种组织和管理多个相关项目的方式。‌它包含了所有相关项目的引用,以及这些项目间的依赖关系、配置和构建过程。在Visual Studio中,解决方案可以看作是一个容器,它将一组关联的项目集合在一起。这些项目可能是控制台应用程序、类库、Web应用程序或者其他类型的应用程序,它们可能相互依赖,或者共享一些资源。因此,解决方案使得对多个项目进行统一管理成为可能。
一个Visual Studio解决方案由一个.sln文件和一个或多个项目文件(如.vcxproj、.vbproj等)组成。.sln文件记录了项目之间的关系,以及它们的构建顺序,而项目文件则描述了每个项目的内容,包括源代码文件、资源文件、引用等。通过这种方式,开发者可以同时处理多个项目,确保它们之间的协调和一致性。

所以,我们应该是在一个Solution下面创建两个Project,其中一个project当作库来用,另外一个project就是我们的项目的源文件。

本例我们给Solution取名GameDeve,库项目取名Engine,开发项目取名Game,下面是两个项目的创建过程:

(2)创建项目

(3)解决方案和两个项目我们现在都创建完毕了,下面就该开发代码、设置项目属性、编译运行了,一般情况下我们的是如下思路:
说明:虽然我不推荐下面的方法,但下面的方法是我们传统的方法,是理解事物本质的一个过程,所以给大家展示一下,如果已经迷糊了的同学可以略过这一小部分:

说明:
一是,上面的步骤6、7、9是不分先后的,你也可以先设置7、9再开发6。
二是,对于Engine项目,我们只需设置其为静态库即可。对于Game项目,我们要设置2处,一处是把外部库名添加上,二处是把外部库的路径添加上即可顺利运行。

三是,上面步骤虽然是可以顺利运行出结果的,但是如果我们移动文件夹,或者重命名项目之类的操作,一切就会崩溃!比如上图的9.1处,就是写死的库名,当库名一变,就各种报错了。所以最好的解决办法就是尽量都用宏定义。
四是,我们在开发Game项目时,应用Engine.h非常不方便!如果项目Egine的头文件目录一变,Game中的文件就得也跟着变,否则又是各种报错!
五是,当我们把6处的代码都开发完毕后,一定要先对Engine项目进行编译,编译完毕后就是一个静态库,如上图8处所示。然后我们再编译Game项目,才能正常运行出结果(上图10处)。因为Engine是Game的依赖,Engine发生变化了,我们就得重新编译Game。也就是我们得先手动构建Engine,然后再手动构建Game。因为Game需要Engine才能工作,因为Engine引用了Game。这就非常不方便!
六是,上面的方法其实非常不自动化的,也不联动,而且编译后的Game.exe文件是不能单独运行的,因为缺少外部依赖Engine。

(4)针对上面的弊端,我们下面一一化解:
也就是本部分承接(2)小标题。
首先对于(3)中的步骤7我们还是要保留的。就是还是要先把Engine项目编译类型改为static library(.lib)的。

其次,对于上面的"四是",就是Application.cpp文件中的include太麻烦问题,我们采取下面的设置:

这样我们的包含文件就不用写长长的路径了,此时我们的开发文档可以写成下面这样:

说明,上图中的Application.cpp文件中的include虽然也可以用<>也可以,但是不建议用。因为尖括号一般表示包含了一些VS解决方案之外的东西(比如标准库),也就是与项目无关的外部依赖项。如果engine.h的代码实际上是解决方案中的东西,还是建议写双引号,这样代码可读性更好些。

代码也开发完毕,最后一步就是开始设置Engine项目和Game项目的关联。因为Engine和Game是在一个Solution里面,所以我们可以通过reference按钮,设置更加自动化、更加联动的解决方案:

这样设置的好处是:
一是不需要我们手动处理Game的链接设置。(如上面的(3)中的设置)
二是如果我们把Engine改成Core什么的其他名字,使用reference就没事,就不用回头再去Game链接处设置把Engine.lib改成Core.lib了。(如上面的(3)中的设置)
三是,也是最重要的,我们不需要提前编译Engine了,直接编译Game就行。因为reference给我们创建了一个完整的依赖关系图,所以一切都能正常工作。
四是,编译完毕后,生成的Game.exe文件可以单独运行了。因为本例中我们是静态链接,所有东西都被放入Game.exe文件中了,就没有了外部文件依赖,当然可以单独顺利运行了。

这种设置是非常有用的,比如如果我们真的要建造一个游戏引擎,我们的解决方案中肯定会有好几个项目,这些项目都需要引用一个引擎文件,此时我们就非常有必要进行模块化。就是将所有核心代码都集中在一个项目中,然后将其他所有的应用(可执行程序)链接到这个集中项目上即可。

至此我们就创建了一个一个解决方案中包含2个项目(当然你可以依葫芦画瓢创建多个项目),其中一个项目就是当作库或者模块来使用的,其他项目就可以使用这个库或模块(也就是engine这个命名空间)中的所有东西了。

说明:本例中的Engine库也是静态链接的,而且通常情况下我们都是采用静态链接。如果你非想动态链接,动态链接还是和静态链接有很多不同的地方的,可能以后有时间和精力再继续探索吧。。。。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 这些主流的销售管理系统,能够有效提升客户管理效率!
  • Python范例总结
  • Nginx 限流实战教程和技巧
  • Apache Airflow如何使用
  • 如何在算家云搭建text-generation-webui(文本生成)
  • uniapp中<map>地图怎么实现点位聚合?
  • 【Qwen2-VL】通义多模态新作速读
  • 创建游戏暂停菜单
  • 力扣(leetcode)每日一题 LCR 187 破冰游戏(还是考的约瑟夫环)
  • UWA支持鸿蒙HarmonyOS NEXT
  • 【Spring】条件装配 @ConditionalOnClass @ConditionalOnBean
  • 【Midjourney中文版】智能绘画,高效便捷
  • python日志搜集分析系统
  • 系统架构笔记-3-信息系统基础知识
  • 关于 NLP 应用方向与深度训练的核心流程
  • [case10]使用RSQL实现端到端的动态查询
  • C++类的相互关联
  • ES6语法详解(一)
  • gcc介绍及安装
  • Invalidate和postInvalidate的区别
  • JavaScript HTML DOM
  • java中具有继承关系的类及其对象初始化顺序
  • JWT究竟是什么呢?
  • Mac转Windows的拯救指南
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • 初识MongoDB分片
  • 翻译:Hystrix - How To Use
  • 关于Flux,Vuex,Redux的思考
  • 嵌入式文件系统
  • 悄悄地说一个bug
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 阿里云重庆大学大数据训练营落地分享
  • # 数仓建模:如何构建主题宽表模型?
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • $.proxy和$.extend
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (不用互三)AI绘画:科技赋能艺术的崭新时代
  • (二)延时任务篇——通过redis的key监听,实现延迟任务实战
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (转)mysql使用Navicat 导出和导入数据库
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET MVC第三章、三种传值方式
  • .Net 知识杂记
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • @Autowired 和 @Resource 区别的补充说明与示例
  • [2016.7 test.5] T1