C++ DLL相關知識

一、基本概念

若要導出一個全局函數,就用關鍵字__declspec(dllexport)來聲明

在Dll項目中建立一個全局函數

__declspec(dllexport)int Add(int a, int b)
{
	return a + b;
}

DLL項目設置:

  1. 取消"預編譯頭文件”
  2. 改爲"/MTd"編譯
  3. 修改輸出Dll名字

 編譯得到dll和lib。

.lib:包含一個列表,表明dll中含有哪些符號,每個符號對應在dll中的位置

 

簡單調用dll:

#include <stdio.h>

//使用庫
#pragma comment(lib,"TestDLL.lib")

//聲明:此函數需要從dll導入
__declspec(dllimport) int Add(int a, int b);

int main()
{
	int result = Add(10, 11);
	return 0;
}

Dll放置位置:(其中第二條是指在調試的時候,項目的目錄)

二、DLL加載和卸載

加載

在exe文件中有一些標識信息,表示exe依賴哪些dll文件,操作系統根據此去尋找,加載相應的dll文件。

 

當dll被加載時,代碼段只被加載一次,是公有的;

數據段被每個程序各自拷貝一份,是私有的。

當動態庫中定義了一個全局變量static int value =0;2個進程同時調用一個Dll,value的值互相不影響。

 

卸載

當所有調用進程都退出後,該Dll被卸載,這時候Dll纔可以刪除。

不同進程間不能比較變量的地址(因爲是虛擬地址)

三、動態內存管理

在Dll中malloc申請的內存,必須在Dll中free,這是微軟的規定,記住即可。

四、使用頭文件

原則:

在Dll項目中,將函數聲明爲__declspec(dllexport)

在APP項目中,將函數聲明爲__declspec(dllimport)

使用條件編譯,Dll部分:

//mydll.h
#ifndef _MYDLL_H
#define _MYDLL_H

#ifdef MYDLL_EXPORTS
#define MYDLL __declspec(dllexport)
#else
#define MYDLL __declspec(dllimport)
#endif

MYDLL int Add(int a, int b);

#endif
//mydll.cpp
#include <stdio.h>

#define MYDLL_EXPORTS
#include "mydll.h"

int Add(int a,int b)
{
    return a+b;
}

調用部分:

#include <stdio.h>
#include "mydll.h"

#pragma comment(lib,"my.lib")

int main()
{
    int result = Add(10,1);
    return 0;
}

五、導出類

要將類導出,只要在類前面加個標識符,如:

class MYDLL CMyClass

{ };

六、靜態庫

僅一個.lib文件,靜態庫中直接就含有代碼段和數據段,在鏈接過程中,是直接把裏面的東西鏈接過來,形成完整的可執行程序。

exe運行的時候不再依賴lib文件。

lib例子

//mylib.h
#ifndef _MYLIB_H
#define _MYLIB_H

int Add(int a, int b);

#endif
//mylib.cpp

#include "mylib.h"

int Add(int a, int b)
{
	return a + b;
}

測試lib例子:

#include <stdio.h>
#include "mylib.h"

#pragma comment(lib,"TestDLL_Static.lib")

int main()
{
	int result = Add(10, 14);
	printf("result: %d\n", result);
	
	return 0;
}

七、靜態庫和動態庫對比

靜態庫優點:最後可執行程序執行對這個庫不再依賴(已經把符號鏈接過來了)

靜態庫缺點:很多,一般使用Dll

動態庫優點:便於升級更新,只要保持接口不變,更換Dll進行升級

八、動態庫自動加載與手動加載(也叫動態加載)

自動加載:在編譯的時候指定dll,當exe程序啓動運行時,首先加載相關的dll。

 

手動加載:運行時候調用LoadLibrary來加載dll;使用FreeLibrary來卸載dll。動態加載增加了變成的靈活性。

手動加載對DLL的要求:

  1. 要求待調用的函數按"C"方式編譯(符號名即爲函數名)
  2. dll文件放在可被系統搜索到的文件夾

 例如:

C編譯形式Dll

//mydll.h
#ifndef _MYDLL_H
#define _MYDLL_H

#ifdef MYDLL_EXPORTS
#define MYDLL __declspec(dllexport)
#else
#define MYDLL __declspec(dllimport)
#endif

extern "C" MYDLL int Add(int a, int b);

#endif
//mydll.cpp
#include <stdio.h>

#define MYDLL_EXPORTS
#include "mydll.h"

int Add(int a, int b)
{
	return a + b;
}

動態調用:

#include <stdio.h>

#include <WinSock2.h>
#include <Windows.h>

int main()
{
	HINSTANCE handle = LoadLibrary(L"TestDLL_CDll.dll");
	if(handle)
	{
		//定義要找的函數原型
		typedef int(*DLL_FUNCTION_ADD) (int, int);
		//找到目標函數的地址
		DLL_FUNCTION_ADD dll_func = (DLL_FUNCTION_ADD)GetProcAddress(handle, "Add");
		if(dll_func)
		{
			int result = dll_func(10, 20);
			printf("result:%d\n", result);
		}
		//卸載
		FreeLibrary(handle);
	}
}

九、動態編譯和靜態編譯/MT /MD

d:debug

M:多線程multi-threading

T:text代碼

D:dynamic動態庫

靜態編譯:/MT /MTd

                  是指使用libc和msvc相關的靜態庫(lib)

                  文件會比較大

動態編譯:/MD /MDd

                  是指使用相應的DLL版本編譯

                   會導致目標機器沒有對應的動態庫而運行不了

 

1、/MT /MD選擇:

爲什麼選擇/MD,不選/MT?

         (1)程序就不需要靜態鏈接運行時庫,可以減小軟件的大小;

         (2)所有的模塊都採用/MD,使用的是同一個堆,不存在A堆申請,B堆釋放的問題。

爲什麼選擇/MT,不選擇/MD?

          (1)有些系統可能沒有程序所需要版本的運行時庫,程序必須把運行時庫靜態鏈接上

 多個模塊,必須選擇相同類型的運行時庫,不要混合使用。

2.選擇/MT需要解決的堆空間釋放問題:

不同的模塊各自有一份C運行時庫代碼,各個C運行庫會有各自的堆,導致了各個模塊會有各自的堆。如果在A堆中申請空間,到B堆中釋放就會有崩潰,在模塊A申請的空間,必須在模塊A中釋放。

         附件(下載地址:http://files.cnblogs.com/cswuyg/Test_MD_and_MT.rar)的DLL以及DLLUser代碼,以STL的string爲例,通過修改編譯選項驗證了這個問題。(string在賦值的時候需要釋放掉原來的空間,然後再申請新的空間存儲新的內容。)

        string在賦值的時候需要釋放掉原來的內存空間,然後再申請新的內存空間存儲新的內容,如果跨模塊了,釋放的時候就存在“A模塊申請B模塊釋放”的問題,導致程序崩潰。

   (跨模塊釋放內存導致崩潰的內容,在《windows核心編程》第五版Page511談DLL和進程的地址空間時有談到)

3.選擇/MD需要注意多個模塊使用不同版本運行時庫的問題:

     多個dll被一個exe LoadLibrary加載,如果這些dll使用的運行時庫是不同的,那麼可能出現加載失敗,原因可能是舊版本的運行時庫已經在了,而某個dll它需要的是新版本的運行時庫,舊版本不符合要求。

     如果工程裏所有的模塊都是自己寫的或者可以完全控制的,那麼這個問題不難解決,只需要在工程設置裏都設置/MD,然後在相同的環境下編譯一次就行。但是假如這個模塊是外界提供的呢?

     可能存在這種情況:A動態庫使用了B靜態庫,B靜態庫使用了C動態庫,B靜態庫是外界提供的,我們要使用它,但無法修改它,我們也無法接觸到C動態庫。如果C動態庫使用的運行時庫版本跟編譯A動態庫的本地使用的不一致,那麼A動態庫裏的嵌入信息就會記錄兩個不同版本的運行時庫,它被加載的時候,可能會選擇版本新的。假設A動態庫被一個exe LoadLibrary加載,而這個exe本身的運行時庫是舊的,這樣就會導致A動態庫加載失敗,即便把新的運行時庫拷貝到目錄下也不行,因爲exe這個進程已經加載了那個舊的運行時庫。這時候必須使用manifest文件指定嵌入到A動態庫裏的運行時庫爲某個版本,忽略掉C動態庫使用的運行時庫版本。

     這個問題挺複雜的,我心思沒去驗證windows的PE文件加載會對運行時庫做什麼樣的優先選擇、運行時庫在靜態庫裏的記錄…。只要記住,給外界使用的組件版本儘量避免使用/MD(這樣會導致膨脹嗎?據說,安裝包可以做字節流式壓縮)。

  附上另一個問題:靜態庫的依賴關係:exe-->libA-->libB,現在不想讓exe接觸到libB,於是把libA的librarian選項-->General選項-->Link Library Dependencies設置爲Yes,這樣即可,libA會包含libB,exe只需要接觸libA。另外需要特別注意,libA對libB的依賴只需要且只能在Solution的Project Dependencies裏設置,如果在libA的代碼裏寫了”#pragma comment(lib, "libB.lib")”,會導致exe在link libA的時候提示找不到libA。如果exe還出現link錯誤,那一定是VS抽筋了:)

vc++編譯時運行庫選擇(/MT、/MTd、/MD、/MDd)https://blog.csdn.net/lwwl12/article/details/77045717

VS項目屬性中C/C++運行庫 、MT /MTd /MD /MDdhttps://blog.csdn.net/u010059658/article/details/51026662

 

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