项目类型

VS在建Win32项目时,有以下选项:

  • windows应用程序

  • 控制台应用程序

  • DLL

  • 静态库

最后两个类型:DLL和静态库,这两种项目类型是不可以单独运行的,必须在Windows应用程序调用他们执行,是提供的库函数而已。

Windows 下的特点

Linux下是ELF格式,即 Executable and Linkable Format 在ELF之下,共享库中所有的全局函数和变量在默认情况下都可以被其它模块使用,即ELF默认导出所有的全局符号。

Windows下面是PE格式的文件,即 Portable Executable Format DLL本质上也是PE文件,DLL需要显式地“告诉”编译器需要导出某个符号,否则编译器默认所有的符号都不导出。Windows下的DLL文件和EXE文件实际上是一个概念,都是PE格式的二进制文件。

静态库(.lib)

函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。

动态库(.lib文件和.dll文件)

在使用动态库的时候,编译后往往提供两个文件:一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。当然到了后面会告诉你如果只提供一个DLL文件,使用显示连接的方式也可以调用,只是稍加麻烦而已。

虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质的区别。对一个DLL文件来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不可复制到可执行文件,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。这时,在发布产品时,除了发布可执行文件以外,同时还需要发布该程序将要调用的动态链接库。

只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。如果DLL不在内存中,系统就将其加载到内存中。

当链接Windows程序以产生一个可执行文件时,你必须链接由编程环境提供的专门的 “引入库(import library)”。这些引入库包含了动态链接库名称和所有Windows函数调用的引用信息。链接程序使用该信息在.EXE文件中构造一个表,当加载程序时,Windows使用它将调用转换为Windows函数。(结合函数调用就是代码跳转来理解)。

引入库LIb和静态库Lib的区别

引入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于引入库而言,其实际的执行代码位于动态库中,引入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。但是引入库文件的引入方式和静态库一样,要在链接路径上添加找到这些.lib的路径。

动态dll的使用

动态链接库的使用需要库的开发者提供生成的.lib文件和.dll文件。或者只提供dll文件。使用时只能使用dll中导出的函数,未导出的函数只能在dll内部使用。Dll的调用有显示连接和隐式连接两种:隐式连接需要三个东西,分别是*.h头文件,lib库(动态的),DLL库;显示连接只需要.dll文件即可。

隐式链接

隐式链接需要三个东西,分别是.h头文件,lib库(动态的),DLL库,而这里的*lib库仅是编译的时候用,运行时候不用,运行时只用Dll。

  1. // dll 的头文件
  2. __declspec(dllexport) int myAdd(int a, int b);
  3. // dll 的源码
  4. #include "mylib.h"
  5. int myAdd(int a, int b) {
  6. return a + b;
  7. }

程序会通过 lib 库,在编译阶段将调用动态库函数的代码之前对加上动态库进行加载入内存的代码。

引入库文件包含被DLL导出的函数的名称和位置,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,lib文件是编译时期连接到应用程序中的,而dll文件才是运行时才会被调用的。利用链接信息加载DLL函数代码并在内存中将其链接入调用程序的执行空间中

显示链接

隐式链接虽然实现较简单,但除了必须的.dll文件外还需要DLL的.h文件和.lib文件,在那些只提供.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。

  1. 一:显示链接
  2. 调用的 DLL的主工程的 main文件中代码如下:
  3. #include <stdio.h>
  4. #include <Windows.h>
  5. #include <tchar.h>
  6. int main()
  7. {
  8. HMODULE hModule = NULL;
  9. typedef int (*Func)(int a, int b);
  10. // 动态加载 DLL 文件
  11. hModule = LoadLibrary(_TEXT("..//Debug//FuncDll.dll" ));
  12. // 获取 add 函数地址
  13. Func fAdd = (Func)GetProcAddress(hModule, "add" );
  14. // 使用函数指针
  15. printf("%d/n" , fAdd(5, 2));
  16. // 最后记得要释放指针
  17. FreeLibrary(hModule);
  18. return 0;
  19. }
  20. 二:隐式链接:
  21. 调用的 DLL的主工程的 main文件中代码如下:
  22. #include <stdio.h>
  23. #include <Windows.h>
  24. #include <tchar.h>
  25. // 先把 lib 链接进来
  26. #pragma comment (lib , "..//Debug//FuncDll.lib" )
  27. // 外部声明的 add 函数
  28. extern "C" _declspec (dllimport )
  29. int add(int a, char b);
  30. int main()
  31. {
  32. // 直接调用 add 函数
  33. printf("%d/n" , add(5, 2));
  34. return 0;
  35. }

image.png