動態鏈接庫編寫與使用(VC6)

靜態鏈接庫編寫與使用
與靜態鏈接相比,動態鏈接更靈活,修改動態鏈接庫的代碼不需要重新編譯exe程序,如果說靜態鏈接庫是編譯階段的模塊化,那麼動態鏈接就是真正的運行時模塊化。

下面將演示使用VC6編寫動態鏈接庫(dll),然後在另一個程序中鏈接使用。

一、創建動態鏈接庫工程

選擇WIN32動態鏈接庫
在這裏插入圖片描述
simple DLL project

在這裏插入圖片描述在這裏插入圖片描述

二、編寫代碼

創建頭文件和CPP文件,分別用於聲明和定義函數

MyDll.h

#if !defined(AFX_MYDLL_H__EF374ACC_12D5_44C8_A50E_2B61E8E3FADD__INCLUDED_)
#define AFX_MYDLL_H__EF374ACC_12D5_44C8_A50E_2B61E8E3FADD__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

extern "C" _declspec(dllexport) __cdecl int Plus(int x, int y);

#endif // !defined(AFX_MYDLL_H__EF374ACC_12D5_44C8_A50E_2B61E8E3FADD__INCLUDED_)

其中,extern “C” 表示以C語言方式導出,這樣就不會修改函數名,如果不加說明,則會以C++方式導出,C++爲了實現函數重載,會把修改函數名。
_declspec(dllexport) 表示這是導出函數,__cdecl C語言默認的調用約定,表示由調用者負責平衡參數堆棧,相對的,windows api 的調用約定一般是 __stdcall,表示由函數自己負責平衡堆棧。在這裏,我將演示兩種調用約定的區別,先用默認的 __cdecl.

MyDll.cpp

int __cdecl Plus(int x, int y)
{
	return x + y;
}

編譯後生成若干文件,我們把 TestDll.dll 和 TestDll.lib 拿出來備用。

在這裏插入圖片描述

這裏說明一下 __cdecl 和 __stdcall 對導出函數名的影響,我們現在使用的是默認的 __cdecl,則函數名和源文件中一樣。使用dependency walker 工具打開dll,可以查看內部的函數名:

在這裏插入圖片描述

可以看到,函數名是Plus,和源文件中定義的一樣。現在我把調用約定修改成 __stdcall,觀察變化。

extern "C" _declspec(dllexport) __stdcall int Plus(int x, int y);

生成DLL,用dependency walker打開,發現函數名變成了 _Plus@8,其中8表示參數的大小,兩個int剛好是8.
在這裏插入圖片描述

如果增加一個int參數,變成3個參數求和:

extern "C" _declspec(dllexport) __stdcall int Plus(int x, int y, int z);

生成的函數名被修改成 _Plus@12 是顯而易見的。
在這裏插入圖片描述

弄清楚兩種調用約定的區別後,下一步就要研究怎麼在其他工程中鏈接DLL了。注意,現在我的DLL使用的調用約定是默認的 __cdecl。如果要使用__stdcall,則要注意聲明函數指針類型時也要指定調用約定爲__stdcall,以及函數名要相應地修改成_Plus@12(舉例)
下面將演示兩種鏈接方式。

三、將 lib 和 dll 複製到工程目錄

隱式鏈接需要用到 lib 和 dll,而顯式鏈接則只需要 dll。
在這裏插入圖片描述

四、隱式鏈接

代碼如下:

#include "stdafx.h"

#pragma comment(lib, "TestDll.lib")
extern "C" __declspec(dllimport) __cdecl int Plus(int x, int y, int z);

int main(int argc, char* argv[])
{
	printf("%d\n", Plus(1,2,3));
	return 0;
}

lib的作用是告訴編譯器函數在哪,真正的代碼在dll裏面。注意函數聲明和之前編寫dll的時候聲明的是一樣的。
在這裏插入圖片描述

五、顯式鏈接

調用LoadLibrary函數獲取dll模塊,再調用GetProcAddress獲取函數指針。
代碼如下:

#include "stdafx.h"

int main(int argc, char* argv[])
{
	HINSTANCE hModule = LoadLibrary("TestDll.dll");
	int (__cdecl *Plus)(int, int, int);
	Plus = (int (__cdecl *)(int, int, int)) GetProcAddress(hModule, "Plus");
	printf("%d\n", Plus(1,2,3));
	return 0;
}

在這裏插入圖片描述

六、發佈

不管是隱式鏈接還是顯式鏈接,發佈程序時都要將 dll 和 exe 一同發佈,lib 只在編譯鏈接階段用到。
在這裏插入圖片描述

如果沒有dll,會報錯!

在這裏插入圖片描述

七、__stdcall

如果使用 __stdcall,則要注意導入函數時,函數名和dll工程的函數名是不一樣的,這個可以用 dependency walker 確認。
現在我將dll函數的調用約定修改爲 __stdcall

extern "C" _declspec(dllexport) __stdcall int Plus(int x, int y, int z);

隱式鏈接
由於隱式鏈接是通過 lib 來獲取函數位置的,所以可以使用和DLL中定義的相同的函數名。

// Helloworld.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#pragma comment(lib, "TestDll.lib")
extern "C" __declspec(dllimport) int __stdcall Plus(int, int, int);

int main(int argc, char* argv[])
{
	printf("%d\n", Plus(1,2,3));
	return 0;
}

在這裏插入圖片描述

顯式鏈接
注意修改了調用約定和函數名

// Helloworld.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

int main(int argc, char* argv[])
{
	HINSTANCE hModule = LoadLibrary("TestDll.dll");
	int (__stdcall *Plus)(int, int, int);
	Plus = (int (__stdcall *)(int, int, int)) GetProcAddress(hModule, "_Plus@12");
	printf("%d\n", Plus(1,2,3));
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章