用C++封裝四則運算的dll,以及三種引用方法

一、生成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

https://blog.csdn.net/u012273127/article/details/55805183

https://blog.csdn.net/geshuai1992/article/details/20218535

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