1. 程式人生 > >c++中建立與呼叫dll

c++中建立與呼叫dll


好處想必不用說了,所謂的黑盒複用,實現模組化的同時避免原始碼暴露等。可以將某一通用功能做成模組,方便複用,同時軟體更新時如果只更新了幾個模組,可以更換dll即可,無需完整地更新,便於程式拓展。以下教程基於VS2015,其他的版本類似。文章參考連結

1.dll的建立

  1. 建立專案。在vs中新建win32應用程式,名稱為任意給定,如MyDll之類的,概述點選下一步,應用程式設定中的應用程式型別選擇DLL(D),完成。

  2. 新增標頭檔案。在專案-新增新項-**Visaul C++**中選擇標頭檔案,設定檔名如MyDll.h。

  3. 在標頭檔案中新增如下程式碼:

    #ifdef MYDLL_EXPORTS
    #define MYDLL_API __declspec(dllexport)//注意decl前面是兩個下劃線
    #else
    #define MYDLL_API __declspec(dllimport)
    #endif
    
    namespace MyDllSpace
    {
    	//匯出類
    	class MyDllClass
    	{
    	private:
    		double a;
    	public:
    		//匯出的函式
    		MYDLL_API MyDllClass();
    		MYDLL_API MyDllClass(double a_);
    		MYDLL_API double multiply(double b);
    		MYDLL_API void display();
    		MYDLL_API static void conbine(MyDllClass m1, MyDllClass m2);
    	};
    }
    

    注意: 因為該標頭檔案既被dll的原始檔包含,又被使用方包含,所以開頭使用條件編譯——當被dll原始檔包含時(#ifdef MYDLL_EXPORTS,因為vs生成的DLL專案預設都定義了DLL名稱_EXPORTS這個巨集),匯出的函式前要加__declspec(dllexport)巨集;如果是被使用方包含,則匯出函式前要加__declspec(dllimport)巨集

  4. 在原始檔中新增如下程式碼:

    #include "MyDll.h"
    #include <iostream>
    
    MyDllSpace::MyDllClass::MyDllClass()
    {
    	a = 0;
    }
    
    MyDllSpace::MyDllClass::MyDllClass(double a_)
    {
    	a = a_;
    }
    
    MYDLL_API double MyDllSpace::MyDllClass::multiply(double b)
    {
    	return a*b;
    }
    
    MYDLL_API void MyDllSpace::MyDllClass::display()
    {
    	std::cout << "a=" << a << std::endl;
    }
    
    MYDLL_API void MyDllSpace::MyDllClass::conbine(MyDllClass m1, MyDllClass m2)
    {
    	std::cout << "(" << m1.a << "," << m2.a << ")" << std::endl;
    }
    

    注意: 這裡的函式是通過在標頭檔案對函式快速操作在原始檔裡自動生成的定義(當然函式體還是得自己寫),自己碼的話可以用名稱空間MyDllSpace把這些定義括起來,這樣不必每次都用名稱空間限定是哪一個空間裡的函式定義了

  5. 點選選單欄的生成-生成解決方案,即建立dll完成,在專案的Debug目錄下可以看到生成的dll和lib檔案。

2.dll的使用

  1. 點選檔案-新建-專案,選擇win32控制檯應用程式,名稱任意設,如UseMyDll,確定,下一步,在應用程式設定中將附加選項中的預編譯頭多選框去除選中,完成。

  2. 將上一步生成的dll、lib、.h標頭檔案複製到專案目錄中,在解決方案資源管理器

    中新增現有項將其加入專案。

  3. 在UseMyDll.cpp檔案中新增如下程式碼:

    #include <iostream>
    #include "MyDll.h"//加入模組的標頭檔案
    
    using namespace std;
    
    int main()
    {
    	double a = 8;
    	MyDllSpace::MyDllClass c(4);//建立模組裡的類例項
    	MyDllSpace::MyDllClass c2;
    	c.display();//使用例項方法
    	cout << "8*4=" << c.multiply(a) << endl;
    	MyDllSpace::MyDllClass::conbine(c, c2);//使用模組裡的類的靜態方法
    	system("pause");//暫停以便能看到輸出
        return 0;
    }
    

    輸出如下:
    在這裡插入圖片描述

注意:

  1. 如果新建專案時選擇將專案新增到解決方案,即在dll解決方案下新建方案,則可以新增dll的引用——直接右擊解決方案資源管理器裡的引用,新增引用,將之前建立的dll新增引用(這也是官方教程裡的用法)——而不用將dll放到當前資料夾下,並新增到專案裡。但是因為我們假設的是更一般的情況,複用的程式碼要求對方非得在自己專案下不太現實。
  2. 標頭檔案也不是必須新增到當前專案裡,只是為了方便找到。也可以在專案-屬性-C/C++-常規下的附加包含目錄裡新增標頭檔案所在目錄,甚至可以把標頭檔案放在系統包含目錄裡,只要包含標頭檔案時能找到就可以。另外.lib檔案也不是必須加到專案下,也可以通過新增.lib所在資料夾為庫目錄,然後將.lib檔案新增為附加依賴項。

3.僅使用dll(顯式連結)

上述方法為使用自己寫的dll,但是如果只有別人給的一個dll不含其它任何資訊怎麼使用?步驟1,2中DLL呼叫方式為隱式連結(呼叫),另外有一種顯式連結,不需要標頭檔案和lib檔案。顯式連結可以在程式執行過程中隨時載入DLL也可以隨時解除安裝,更具有靈活性,適合解釋型語言,當然使用也要更麻煩些。VS在VC\bin目錄下提供了一個dumpbin.exe程式來檢視DLL檔案中的函式結構。參考連結

  1. 如步驟1建立一個dll專案

    標頭檔案程式碼如下:

    //FuncOnlyDll.h
    #ifdef FUNCONLYDLL_EXPORTS
    #define FUNCONLYDLL_API __declspec(dllexport)
    #else
    #define FUNCONLYDLL_API __declspec(dllimport)
    #endif
    
    namespace Funcs
    {
    	FUNCONLYDLL_API double minValue(double a, double b);
    	FUNCONLYDLL_API double maxValue(double a, double b);
    	FUNCONLYDLL_API double add(double a, double b);
    	FUNCONLYDLL_API double subtract(double a, double b);
    }
    

    原始碼如下:

    //FuncOnlyDll.cpp
    #include "FuncOnlyDll.h"
    
    FUNCONLYDLL_API double Funcs::minValue(double a, double b)
    {
    	return a < b ? a : b;
    }
    
    FUNCONLYDLL_API double Funcs::maxValue(double a, double b)
    {
    	return a > b ? a : b;
    }
    
    FUNCONLYDLL_API double Funcs::add(double a, double b)
    {
    	return a + b;
    }
    
    FUNCONLYDLL_API double Funcs::subtract(double a, double b)
    {
    	return a - b;
    }
    

    點選選單欄中生成-生成解決方案,開啟專案目錄生成dll所在的資料夾,使用dumpbin.exe檢視下dll中函式結構,如下圖:
    在這裡插入圖片描述

    可以看到有四個函式,但是顯示的就像亂碼,頂多只能看出每個函式叫啥,這樣對於使用的使用者來說是不友好的,因為看一堆亂碼是無法知道這些函式是幹啥的,有哪些引數和返回值,這樣是無法使用的,我們可以在選單欄專案-新增新項-Visaul C++-程式碼選擇模組定義檔案(.def),設定檔名,在新增的def檔案中給出如下程式碼:

    LIBRARY
    EXPORTS
    [email protected]@@[email protected]
    [email protected]@@[email protected]
    [email protected]@@[email protected]
    [email protected]@@[email protected]
    

    其中後面的亂碼對應之前dump出來的dll函式名亂碼,寫這個def檔案相當於把亂碼重新定義名字,讓它更好識別,這裡我定義的規則是返回型別_函式名_引數列表型別,當然你可以定義自己的規則,這樣使用者一看就知道這個函式幹啥的,有啥引數和返回值,再dump一下dll看看:

在這裡插入圖片描述

發現後面四個函式輸出了我們想要的結果,但是前面仍然有四個亂碼函式(其實是同樣四個函式),可以將標頭檔案中的*#define FUNCONLYDLL_API __declspec(dllexport)改成#define FUNCONLYDLL_API*這樣就不會輸出前面的亂碼(但是這樣會使得dll使用隱式連結出錯):

在這裡插入圖片描述

至此,dll建立完畢。

  1. 新建一個專案使用建立的dll

    顯式連結是不用配置專案屬性的,直接將dll檔案放在專案目錄下,或是其他能查詢到的目錄比如系統path路徑之類的,程式碼如下:

    #include <iostream>
    //為了使用LoadLibrary和FreeLibrary等函式以及LPCWSTR和LPCSTR型別加入的標頭檔案
    #include <Windows.h>
    using namespace std;
    //定義要載入的各種函式指標,通過dumpbin檢視dll函式中函式簽名從而得知,或者檢視API
    typedef double(*pMin)(double, double);
    typedef double(*pMax)(double, double);
    typedef double(*pAdd)(double, double);
    typedef double(*pSub)(double, double);
    int main()
    {
    	LPCWSTR dllName = TEXT("FuncOnlyDll.dll");
    	LPCSTR funName1 = "double_minValue_double_double";
    	LPCSTR funName2 = "double_maxValue_double_double";
    	LPCSTR funName3 = "double_add_double_double";
    	LPCSTR funName4 = "double_subtract_double_double";
    	HMODULE hDll = LoadLibrary(dllName);//載入dll
    	if (hDll != NULL) {
    		pMin fp1 = (pMin)(GetProcAddress(hDll, funName1));//載入函式
    		if (fp1 != NULL) {
    			cout << "min value of 5 and 6:" << fp1(5, 6) << endl;
    		}
    		else {
    			cout << "函式min載入失敗" << endl;
    		}
    		pMax fp2 = (pMax)(GetProcAddress(hDll, funName2));
    		if (fp2 != NULL) {
    			cout << "max value of 5 and 6:" << fp2(5, 6) << endl;
    		}
    		else {
    			cout << "函式max載入失敗" << endl;
    		}
    		pAdd fp3 = (pAdd)(GetProcAddress(hDll, funName3));
    		if (fp3 != NULL) {
    			cout << "sum value of 5 and 6:" << fp3(5, 6) << endl;
    		}
    		else {
    			cout << "函式add載入失敗" << endl;
    		}
    		pSub fp4 = (pSub)(GetProcAddress(hDll, funName4));
    		if (fp2 != NULL) {
    			cout << "difference of 5 and 6:" << fp4(5, 6) << endl;
    		}
    		else {
    			cout << "函式sub載入失敗" << endl;
    		}
    		FreeLibrary(hDll);//解除安裝dll,一般用完就解除安裝,避免佔用記憶體
    	}
    	else {
    		cout << "載入dll檔案失敗" << endl;
    	}
    	system("pause");
        return 0;
    }
    

    從上述使用方法可以看出,因為我們之前建立dll檔案的時候貼心地把匯出的函式名設定了下,才可以在dumpbin中檢視到比較有意義的函式名資訊,所以如果第三方給的dll沒有這麼做,那麼你就根據dump出來的函式名亂碼猜吧,祝你好運。

  2. 顯式連結的不足

    顯然,如果只給了dll沒給介面(標頭檔案或者相應的API文件),而且dump出來函式名是亂碼,是沒法複用dll的。另外dll的顯式連結是不支援匯出類和變數的,至少沒人建議這麼做。

4.一點小的建議

經過看完網上各種教程強烈建議如果dll複用的話一定使用.h,.lib,.dll三件套,即隱式連結方式

其他任何解決辦法都不好使,因為dll隱藏了檔案中的函式結構,不知道函式結構是沒法用的

即使通過dumbin.exe也只能探知到一些看起來像亂碼的函式名,是無法瞭解這個函式具體幹啥的

除非已經知道了介面,比如隱式連結,使用了dll對應原始碼的標頭檔案,顯然是暴露介面的一種方式

相關推薦

c++建立呼叫dll

好處想必不用說了,所謂的黑盒複用,實現模組化的同時避免原始碼暴露等。可以將某一通用功能做成模組,方便複用,同時軟體更新時如果只更新了幾個模組,可以更換dll即可,無需完整地更新,便於程式拓展。以下教程基於VS2015,其他的版本類似。文章參考連結 1.dll的

C#呼叫C++dllC++char*C#型別的對應關係

最近在編寫C#上位機應用程式,需要呼叫C++的dll,期間遇到dll介面庫中char*型別糾結了很久,試過string,StringBuilder,StringBuilder結果都以失敗告終,通過查詢部落格等資料最後找到了救命稻草---IntPtr。例子如下: C++dll

c/c++_stdcalldll動態呼叫

1._stdcall在動態dll呼叫中的注意事項 為了用vc寫的dll能被其它語言的寫的程式使用,即實現跨語言。我們在dll的函式呼叫約定中使用__stdcall . 但當用GetProcAddress呼叫是卻失敗了. 用dumpbin工具檢視匯出的函式名可以看到

codeblocks建立呼叫動態連結庫(dll)

一、建立C語言動態連結庫 1.建立。 File->New->Projects->Dynamic Link library->Go 給專案命名為:Dynamic librar

Codeblocks建立呼叫DLL動態連結庫(C語言)

建立一個最簡單的只有一個get_id() 函式的DLL庫  一、建立C語言動態連結庫 1.新建一個動態庫的工程 File - New - Project - DLL - Go 新建的工程原來的main.cpp和main.h刪除,新建兩個檔案simple.h, simple

VC++6.0 動態庫的建立呼叫(非MFC的dll

非MFC動態庫的建立。。。。 lib.cpp#ifndef LIB_H #define LIB_H //宣告add為dll的匯出函式. extern "C" int _declspec(dllexport)add(int x,int y); #endif /*lib.

C#StructClass的區別

而是 適用於 ack 定義 cts 多態 支持 關鍵字 for class和struct最本質的區別是class是引用類型,而struct是值類型,它們在內存中的分配情況有所區別。 什麽是class? class(類)是面向對象編程的基本概念,是一種自定義數據結構類型,通

C#outref區別

erro 變量 但是 color 賦值 運行 網上 ann amp 一、ref(參考)與out區別 1、out(只出不進) 將方法中的參數傳遞出去,在方法中將該參數傳遞出去之前需要在該方法起始賦初值;在方法外傳遞的該參數可以不用賦值; 簡單理解就是:將一個東西拋出去之前必須

如何在VMware系統的ubuntu16.04建立win7系統的共享文件夾

alt hgfs vmware 結果 vmw 設置 too 得到 image 點擊虛擬機設置一次得到如圖所示 系統默認放在了 /mnt/hgfs文件夾 點擊虛擬機安裝vmware tools 解壓vmware tools壓縮包 運行sudo ./vmwar

C#靜態非靜態方法比較

通過 bsp 不同 oss 分配 htm 不能 size tar C#靜態方法與非靜態方法的區別不僅僅是概念上的,那麽他們有什麽具體的區別呢?讓我們通過本文向你做一下解析。 C#的類中可以包含兩種方法:C#靜態方法與非靜態方法。那麽他們的定義有什麽不同呢?他們在使用上

C#結構的區別實例分析

類與結構 main bds nbsp 模擬鼠標 指向 img adding 區別 類與結構是C#程序設計中基本的數據類型,而初學者往往不能很好的分清二者之間的區別。本文就以附帶實例形式加以說明。具體如下: 一、基本概念: 類: 引用類型,存儲在堆中,棧中存儲引用地址

C#PredicateFunc泛型委托的用法實例

public pan html 加水印 pre wid bcf 委托 ora 本文以實例形式分析了C#中Predicate<T>與Func<T, bool>泛型委托的用法,分享給大家供大家參考之用。具體如下: 先來看看下面的例子:static vo

C++繼承抽象類

post dog urn 針對 delet rtu 繼承 prot virt 繼承語法格式如下: class 子類名稱 : 繼承方式(public private protected 三種) 父類名稱 純虛函數格式: virtual 返回值類型 函數名(參數列表)= 0;含

C++compilebuild的區別

build com www ads upload 文件 font mil .com 我在前面的博文就提到了GCC編譯器工作的四個階段:預處理、編譯、匯編、鏈接。 感興趣的同學可以參考:http://www.cnblogs.com/mlgjb/p/7708007.html

【轉】C#FuncAction的理解

.net ring UNC 簡單 代碼 操作 不必要 返回值 tps 原文地址:https://www.cnblogs.com/ultimateWorld/p/5608122.html Action 與 Func是.NET類庫中增加的內置委托,以便更加簡潔方便的使用委托。最

C#ArrayList string、string[]數組 的轉換

list string ray AR nbsp 數組 arr spl oar 1、ArrarList 轉換為 string[] :   ArrayList list = new ArrayList();   list.Add("aaa");   list

c#委託多執行緒的實質

delegate(委託)的概念,.Net的委託本質上就是指向函式的指標,只不過這種指標是經過封裝後型別安全的。委託和執行緒是兩個不同的概念,執行緒是動態的,委託就是一個或一組記憶體地址,是靜態的。執行緒執行時如果遇到了指向函式的指標就執行這個函式。.Net為了方便程式設計,給委託賦予了兩種方式以供呼

c++ char string 之間的相互轉換

第一部分: 將  char *    或者    char []   轉換為  string 可以直接賦值,轉換。       第二部分: 將  &n

C++繼承組合的區別

物件和類是C++中的重要內容,物件(Object)是類(Class)的一個例項(Instance)。面向物件設計的重點是類的設計,而不是物件的設計。對於C++程式而言,設計孤立的類是比較容易的,難的是正確設計基類及其派生類。這就和“繼承”(Inheritance)和“組合”(Composition)

C#boolBoolean有什麼區別

    首先結論:bool和Boolean(System.Boolean)是完全一樣的  MSDN中解釋bool與Boolean的關係是:bool關鍵字是System.Boolean的別名。實際使用無任何差別。它們的成員也是一樣的。