首页 > 技术文章 > 使用msvc命令行编译静态库和动态库

wangbingbing 2021-05-26 17:53 原文

因为最近园子在审核,先发在了CSDN。但我更喜欢博客园,现在搬运过来

编写一个静态库

编写要打包为静态库的函数,内容如下:

// jclib.cpp
int func(int a, int b)
{
    return a + b;
}

msvc开发人员命令提示符中执行

cl /c jclib.cpp

生成jclib.obj,这是编译产生的中间文件。然后使用lib工具将其打包为静态链接库*.lib文件。

lib jclib.obj

产生jclib.lib
写个头文件声明函数,供其他模块调用。

// jclib.h
int func(int,int);

接下来写个程序调用静态库中的func函数。

// demo.cpp
#include "jclib.h"
#include <stdio.h>
#pragma comment(lib,"./jclib.lib")//这里表明要链接的库,也可以在编译选项中指定
int main()
{
    int a =1;
    int b = 3;
    int c = 3;
    printf("c=%d\n",c);
    c = func(a,b);//这个函数就在静态库中
    printf("c=%d\n",c);
    return 0;
}

编译链接测试例子

cl /EHsc demo.cpp

如果在demo.cpp中没有写#pragma comment语句,则执行如下编译命令

cl /EHsc demo.cpp jclib.lib

编写一个动态库

写法一

编写动态库中的函数

//jcdll.cpp
__declspec(dllexport) int add(int a, int b){
    return a + b;
}
__declspec(dllexport) int subtract(int a, int b){
    return a - b;
}

__declspec(dllexport)表示函数要导出给其他模块使用,如果代码里不写这个声明,那么就需要后边编译的时候用.def文件来说明要导出哪些函数。(导出的意思是,有些函数在dll中是私有的,并不想被外界访问,那么就不导出这些函数)。
编写动态库头文件,声明函数,供其他模块调用

// jcdll.h
int __cdecl add(int,int);
int __cdecl subtract(int,int);

编译生成动态库

cl /LD jcdll.cpp

产生jcdll.dll,jcdll.lib,jcdll.exp
编写测试程序,调用jcdll中的函数

// demo.cpp
#include "jcdll.h"
#include <stdio.h>
#pragma comment(lib,"./jcdll.lib")
int main()
{
    int d = 1;
    int e = 4;
    int f = add(d,e);
    printf("f=%d\n",f);
}

编译测试程序

cl /EHsc demo.cpp

写法二

这里仅说明和方法一操作不一样的地方
jcdll.cpp中不写__declspec
在编译时使用.def文件来定义要导出的函数,分号开头是注释

; jcdll.def
; this is comment
LIBRARY jcdll.dll
EXPORTS
	add		@1
	subtract	@2

EXPORTS字段可以这样写:

; 只导出函数名
func1 
; 导出函数名和序号
func2 @2 
; 只导出序号
func3 @3 noname  

编译动态库

cl /LD /DEF: jcdll.def jcdll.cpp

注意:使用def文件定义导出函数和使用__declspec(dllexport)导出,产生的导出符号是不太一样的,使用dumpbin /exports jcdll.dll查看导出符号,发现前者导出符号和函数名完全一样,后者会被编译器打乱一点,变成?add@@YAHHH@Z?subtract@@YAHHH@Z,对于隐式链接来说无妨,编译器会处理,对于显式链接,也就是用GetProcAddress来获取函数指针时,要写?add@@YAHHH@Z才能获取到函数指针。

另外,在dll中可以定义DllMain来对dll加载和释放时进行操作。这个函数在加载dll加载和卸载时将被调用。这里就不展开了,有兴趣可以查阅其他资料。

#include <windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
// https://blog.csdn.net/a7055117a/article/details/47733247

显式加载dll

// 加载动态库,如果动态库没有资源文件,只有代码,就没必要用LoadLibraryEx, 用LoadLibrary就行了,参数少
// 写在这里只是记下它的用法
// 需要包含<windows.h>

//HINSTANCE hDll = LoadLibrary(TEXT("jcdll.dll"));
HINSTANCE hDll = LoadLibraryEx(TEXT("jcdll.dll"), NULL, 0);
typedef int (*FUNCTYTE)(int, int); //函数指针
if (hDll != NULL)
{
	// 建议使用def文件定义导出函数,这样的话只要写"add"就可,不必搞这些
	//乱七八糟的函数名
    FUNCTYTE add = (FUNCTYTE)GetProcAddress(hDll, "?add@@YAHHH@Z");
    if (add)
        printf("add(1,2)=%d\n", add(1, 2));
    else
        printf("GetProcAddress() failed: %d\n",GetLastError());
}

FreeLibrary(hDll);

推荐阅读