一、生成DLL
1.新建dll項目
新建 “Visual C++”-“Windows桌面”-“動態鏈接庫(DLL)”
或者:
新建 “Visual C++”-“Win32”-“Win32 控制檯應用程序”
再右鍵 自己的項目:屬性,然後在 常規->配置類型 改爲動態庫(.dll)
比如我建了一個叫“dllmain”的工程。
2.怎麼去寫代碼
舉個簡單例子,做一個能加減乘除+-*/的dll接口:
創建DLL文件目錄下現在用得到的有:dllmain.h 以及dllmain.cpp
(新建項目的其他自動生成的文件不要動,不然無法成功生成dll……)
在“dllmain.cpp”文件裏添加實現功能的代碼:
#include "dllmain.h"
#include <stdexcept> //標準的異常類,除數爲0時使用
double Add(double a, double b)
{ return a + b;}
double Subtract(double a, double b)
{ return a - b;}
double Multiply(double a, double b)
{ return a * b;}
double Divide(double a, double b)
{ if (b == 0) throw std::invalid_argument("除數不能是 0!");
return a / b;
}
然後在“dllmain.h”頭文件裏添加函數聲明代碼:
這是我們平時的函數聲明:
double Add(double a, double b);
double Subtract(double a, double b);
double Multiply(double a, double b);
double Divide(double a, double b);
在dll裏爲了能被使用,前面添加:extern "C" __declspec(dllexport)
extern "C" __declspec(dllexport) double Add(double a, double b);
extern "C" __declspec(dllexport) double Subtract(double a, double b);
extern "C" __declspec(dllexport) double Multiply(double a, double b);
extern "C" __declspec(dllexport) double Divide(double a, double b);
爲了高逼格一點,可以使用宏定義
#define MathFuncDll_API __declspec(dllexport)
extern "C" MathFuncDll_API double Add(double a, double b);
extern "C" MathFuncDll_API double Subtract(double a, double b);
extern "C" MathFuncDll_API double Multiply(double a, double b);
extern "C" MathFuncDll_API double Divide(double a, double b);
_declspec(dllexport) 意爲指定需要導出的目標(生成dll用的)
extern "C",意爲將C語言下的程序導出爲DLL(這麼做的目的見文末”被其他語言調用注意事項“)
在.cpp和.h裏添加過代碼後,”Ctrl+Shift+B“ 或者 ”生成解決方案“都可以,
生成成功,然後DeBug目錄下找到 那個生成的 “dllmain.dll”
二、調用
網上搜了一下,這方面還真是有大不相同的好幾種方法。。
動態鏈接庫(Dynamic Link Library)DLL文件與EXE文件一樣也是可執行文件,但是DLL也被稱之爲庫,因爲裏面封裝了各種類啊,函數啊之類的東西,就像是一個庫一樣,存儲着很多東西,主要是用來調用的。調用方式主要分爲兩種:隱式(通過lib文件與頭文件) 與 顯式(只通過DLL文件)【顯式調用又分爲:靜態(需要lib)、動態】。
摘自:https://blog.csdn.net/qq_34097715/article/details/79540933
方法一:隱式調用(項目設置中調用)
編譯程序時需要頭文件、lib文件,運行時需要DLL文件,並且運行過程中DLL文件一直被佔用。但是可以像正常使用一樣直接
用,而不用調用API!
這個時候我們需要三個文件,頭文件(.h)、導入庫文件(.lib)、動態鏈接庫(.dll)把生成的.dll和.lib兩個文件拷入控制檯程序(exe)的Debug文件夾下
添加dll、lib、h三者的引用:
DLL 引用方式
右鍵項目——屬性——鏈接器——常規——附加庫目錄——添加.dll所在目錄【選擇你dllmain.dll所在文件夾】
LIB 引用方式
右鍵項目——屬性——配置屬性——VC++目錄——庫目錄:添加.lib所在的文件夾(確認對應libpath的文件夾)
右鍵項目——屬性——鏈接器——輸入——附加依賴項——添加.lib【輸入dllmain.lib】
(也可以 僅僅右鍵項目,添加,現有項,把.lib先引用進來。否則會報錯“無法打開xxxx.lib”!)
H 引用方式
右鍵項目——屬性——配置屬性——VC++目錄——包含目錄:添加.h所在的文件夾(確認對應需要include的文件夾)
在代碼裏輸入#include"dllmain.h"(見有篇博客說要把extern "C" __declspec(dllexport)前綴刪掉 ,因爲那是生成dll用的)——添加.h
#include<iostream>
#include"dllmain.h" //這裏引用
using namespace std;
int main()
{
//隱式加載 dll以及lib,h
cout << Add(2, 3) << endl; //5
cout << Subtract(2, 3) << endl; //-1
cout << Multiply(2, 3) << endl; //6
cout << Divide(2, 3) << endl; //0.666
system("pause");//這個例子不加這個會一閃而過
return 0;
}
發現沒有,這跟平時include一個.h的函數一模一樣!完全沒有別的多餘的東西!!!
顯式調用(代碼中調用)
一種是LIB包含了函數所在的DLL文件和文件中函數位置的信息(入口),代碼由運行時加載在進程空間中的DLL提供,稱爲動態鏈接庫dynamic linklibrary。
一種是LIB包含函數代碼本身,在編譯時直接將代碼加入程序當中,稱爲靜態鏈接庫static link library。
共有兩種鏈接方式:
動態鏈接使用動態鏈接庫,允許可執行模塊(.dll文件或.exe文件)僅包含在運行時定位DLL函數的可執行代碼所需的信息。
靜態鏈接使用靜態鏈接庫,鏈接器從靜態鏈接庫LIB獲取所有被引用函數,並將庫同代碼一起放到可執行文件中。
// 動態,只用DLL
typedef int(_stdcall *pGetMaxN)(int, int); //定義一個函數指針類型
typedef void(_stdcall *pShowMsg)(char *, char *);
// 靜態, 用LIB和DLL
extern "C" __declspec(dllimport) int _stdcall GetMaxNumber(int, int);
extern "C" __declspec(dllimport) void _stdcall ShowMsg(char *, char*);
方法二:動態顯式調用
僅僅把生成的dllmain.dll 放到debug文件夾裏。
#include <windows.h>
#include<iostream>
using namespace std;
typedef double(*func)(double a, double b);
void main()
{
//動態加載 dll
HMODULE hModule = LoadLibrary(L"dllmain.dll");
if (!hModule)
{
cout << "Error!" << endl;
}
func Add = func(GetProcAddress(hModule, "Add"));
func Subtract = func(GetProcAddress(hModule, "Subtract"));
func Multiply = func(GetProcAddress(hModule, "Multiply"));
func Divide = func(GetProcAddress(hModule, "Divide"));
if (Add != NULL)
{
cout << Add(2, 3) << endl; //5
cout << Subtract(2, 3) << endl; //-1
cout << Multiply(2, 3) << endl; //6
cout << Divide(2, 3) << endl; //0.666
}
//釋放
FreeLibrary(hModule);
}
其中 L"dllmain.dll"的L是”使用多字節字符集“的意思
也可以”不打L“,然後 右鍵工程—>屬性—>常規—>字符集—>使用多字節字符集。
此方法需要函數指針和WIN32 API函數(引入了windows.h)中的LoadLibrary、GetProcAddress裝載。
1、如果在沒有導入庫文件(.lib),而只有頭文件(.h)與動態鏈接庫(.dll)時,我們才需要顯式調用,如果這三個文件都全的話,我們就可以使用簡單方便的隱式調用。
2、通常Windows下程序顯示調用dll的步驟分爲三步(三個函數):LoadLibrary()、GetProcAdress()、FreeLibrary()
其中,LoadLibrary() 函數用來載入指定的dll文件,加載到調用程序的內存中(DLL沒有自己的內存!)
GetProcAddress() 函數檢索指定的動態鏈接庫(DLL)中的輸出庫函數地址,以備調用
FreeLibrary() 釋放dll所佔空間
(摘自:https://blog.csdn.net/u012150179/article/details/12346555)
方法三:靜態顯式調用
採用lib文件調用DLL(採用Lib文件的調用方式又被稱爲靜態調用)
注意,要把 dllmain.dll 和dllmain.lib放到 生成的.exe 同一目錄下(比如DeBug文件夾)(如果不放這裏,工程裏設置、代碼裏“#pragma comment(lib,"dllmain.lib的完整路徑")”也是可以的)
需要先 右鍵項目,添加,現有項,把.lib先引用進來。否則會報錯“無法打開xxxx.lib”!
#include<iostream>
using namespace std;
#pragma comment(lib,"dllmain.lib")
extern "C" __declspec(dllimport) double Add(double, double);
extern "C" __declspec(dllimport) double Subtract(double, double);
extern "C" __declspec(dllimport) double Multiply(double, double);
extern "C" __declspec(dllimport) double Divide(double, double);
void main()
{
//靜態加載 dll
cout << Add(2, 3) << endl; //5
cout << Subtract(2, 3) << endl; //-1
cout << Multiply(2, 3) << endl; //6
cout << Divide(2, 3) << endl; //0.666
}
#pragma comment(lib,"dllmain.lib");
表示鏈接dllmain.lib這個庫,和在工程設置裏寫上鍊入dllmain.lib的效果一樣(兩種方式等價,或說一個隱式(工程文件裏設置)一個顯式(寫在代碼裏)調用)。
extern "C" __declspec(dllimport) double Add(double, double);
對比之前的函數導出聲明【extern "C" __declspec(dllexport) double Add(double a, double b);】,這個是從dll導入的聲明。
ps: 如果代碼輸入加了_stdcall,會發生錯誤
extern "C" __declspec(dllimport) double _stdcall Divide(double, double);
LNK2019 無法解析的外部符號 __imp__Divide@16,該符號在函數 _main 中被引用
被其他語言調用注意事項
關於__stdcall問題,.def文件。這篇博客寫的很清楚了:https://blog.csdn.net/songyi160/article/details/50754705
這部分粗略總結就是:
“C和C++的編譯器的函數名修飾規則不一致,爲了確保導出函數名及入口點函數不變,此時需添加.def文件 ”
“用def文件導出的動態庫DLL既可以保證函數名不變也可以保證動態庫DLL的入口點函數名不變,同時在.cpp文件中函數定義中加入__stdcall就可以實現導出的DLL被其它語言調用,此時.h頭文件的作用僅僅打包給開發者,供其查看導出的函數名及相應參數而已。”
其他參考:
…………………………………………
百度經驗(幫助很大!):https://jingyan.baidu.com/article/ff42efa92c49cfc19e2202fd.html
https://jingyan.baidu.com/article/3065b3b6a60d88becef8a462.html
https://blog.csdn.net/songyi160/article/details/50754705
https://blog.csdn.net/nimaqusia/article/details/53835876
https://blog.csdn.net/kencaber/article/details/50642679
https://blog.csdn.net/u012150179/article/details/12346555
截圖流程,簡單粗暴:https://blog.csdn.net/m0_37170593/article/details/76445972
https://blog.csdn.net/sya_inn/article/details/53981440
…………………………………………
三、相關知識:
DLL
作用:函數可執行文件
DLL文件中存放封裝的函數和類,當程序需要調用DLL所定義的功能時,需要先載入DLL文件,然後取得函數的地址,最後進行調用。 通過DLL來調用功能,可實現代碼的封裝與複用,去除功能之間的耦合,有利於模塊化。降低應用難度的同時,也可以實現知識產權的保護。
摘自:https://blog.csdn.net/ezhchai/article/details/78784572
LIB
作用:二進制函數實現代碼或函數在dll文件中的索引地址
lib庫有兩種:
(1)靜態鏈接庫(Static Libary,簡稱“靜態庫”)
(2)導入庫(Import Libary,簡稱“導入庫”)of 動態連接庫(DLL,簡稱“動態庫”)
靜態庫本身就包含了實際執行代碼、符號表等等……而對於導入庫而言,其實際的執行代碼位於動態庫中,導入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。導入庫文件的作用:告訴鏈接器調用的函數在哪個DLL中,函數執行代碼在DLL中的什麼位置。這也就是爲什麼需要在工程屬性的“附加依賴項”中填入.LIB文件,它起到橋樑的作用。如果生成靜態庫文件,則沒有DLL ,只有lib,這時函數可執行代碼部分也在lib文件中。
共有兩種鏈接方式:
動態鏈接使用動態鏈接庫,允許可執行模塊(.dll文件或.exe文件)僅包含在運行時定位DLL函數的可執行代碼所需的信息。
靜態鏈接使用靜態鏈接庫,鏈接器從靜態鏈接庫LIB獲取所有被引用函數,並將庫同代碼一起放到可執行文件中。
H頭文件
作用:聲明函數接口
.h用於編譯階段的審覈,比如函數聲明的與調用的參數個數、類型是不是對應。
dll、lib、h三者關係
.h頭文件是編譯時必須的,lib庫是鏈接時需要的,dll動態鏈接庫是運行時需要的。
若生成了DLL,則肯定也生成 LIB文件。如果要完成源代碼的編譯和鏈接,有頭文件和lib就夠了。如果也使動態連接的程序運行起來,有dll就夠了。在開發和調試階段,當然最好都有。
綜上所述: .h和lib文件是編譯器,比如VS2012,在編譯的時候調用的,而dll是生成的可執行的文件,比如.exe文件,運行的時候需要調用的。
DLL與LIB的區別 :
1.DLL是一個完整程序,其已經經過鏈接,即不存在同名引用,且有導出表,與導入表lib是一個代碼集(也叫函數集)他沒有鏈接,所以lib有冗餘,當兩個lib相鏈接時地址會重新建立,當然還有其它相關的不同,用lib.exe就知道了;
2.在生成dll時,經常會生成一個.lib(導入與導出),這個lib實際上不是真正的函數集,其每一個導出導入函數都是跳轉指令,直接跳轉到DLL中的位置,這個目的是外面的程序調用dll時自動跳轉;
3.實際上最常用的lib是由lib.exe把*.obj生成的lib。(引用這裏)
關於lib和dll的區別如下:
(1)lib是編譯時用到的,dll是運行時用到的。如果要完成源代碼的編譯,只需要lib;如果要使動態鏈接的程序運行起來,只需要dll。
(2)如果有dll文件,那麼lib一般是一些索引信息,記錄了dll中函數的入口和位置,dll中是函數的具體內容;如果只有lib文件,那麼這個lib文件是靜態編譯出來的,索引和實現都在其中。使用靜態編譯的lib文件,在運行程序時不需要再掛動態庫,缺點是導致應用程序比較大,而且失去了動態庫的靈活性,發佈新版本時要發佈新的應用程序才行。
(3)動態鏈接的情況下,有兩個文件:一個是LIB文件,一個是DLL文件。LIB包含被DLL導出的函數名稱和位置,DLL包含實際的函數和數據,應用程序使用LIB文件鏈接到DLL文件。在應用程序的可執行文件中,存放的不是被調用的函數代碼,而是DLL中相應函數代碼的地址,從而節省了內存資源。DLL和LIB文件必須隨應用程序一起發行,否則應用程序會產生錯誤。如果不想用lib文件或者沒有lib文件,可以用WIN32 API函數LoadLibrary、GetProcAddress裝載。
代碼相關
#pragma once
——意爲“只編譯一次“
#define MathFuncDll_API __declspec(dllexport)
——【#define 標識符 字符串】意爲 編譯時把”MathFuncDll_API“替換爲eclspec(dllexport) “(僅僅是字符層面上)
typedef double(*func)(double a, double b);
——爲一種數據類型定義一個新名字
(1)當“extern”關鍵字修飾在函數或全局變量的定義中時,表示該函數或全局變量任何文件可以訪問,“extern”關鍵字可以省略不寫,缺省下就是”extern”
當“extern”關鍵字修飾在函數聲明或全局變量聲明中時,表示限定當前文件只能引用用“extern”關鍵字修飾定義的函數或全局變量.
(2)當”static”關鍵字修飾在函數或全局變量的定義中時,表示該函數或全局變量只能由本文件中加了”static”關鍵字修飾的函數聲明或全局變量聲明來引用.
當”static”關鍵字修飾在函數聲明或全局變量聲明中時,表示限定當前文件只能引用用“static”關鍵字修飾定義的函數或全局變量.
(3)在CPP源文件的函數和全局變量定義中加了個”C”表示允許C源文件訪問該函數和全局變量.如果是C++源文件訪它們的話則可加可不加.注意這”C”要大寫.
工程相關
在Windows中,定義在dll中的變量、函數和類,如果希望讓別的程序能夠訪問。必須通過manifest文件指定導出目標(變量、函數或類),
或者通過__declspec(dllexport)關鍵字指定需要導出的目標,然後在使用dll的程序中通過__declspec(dllimport)關鍵字指定導入的目標。
(摘自:https://jingyan.baidu.com/article/3065b3b6a60d88becef8a462.html)
應用程序如何找到DLL文件?
使用LoadLibrary顯式鏈接,那麼在函數的參數中可以指定DLL文件的完整路徑;如果不指定路徑,或者進行隱式鏈接,Windows將遵循下面的搜索順序來定位DLL:
(1)包含EXE文件的目錄
(2)工程目錄
(3)Windows系統目錄
(4)Windows目錄
(5)列在Path環境變量中的一系列目錄
其他的的一些參考:
http://www.cnblogs.com/tswcypy/p/4554041.html
https://www.cnblogs.com/azbane/p/7364060.html