2501,編寫dll

DLL的優點

簡單的說,dll以下幾個優點:

1)節省內存.同一個軟件模塊,若是源碼重用,則會在不同可執行程序編譯,同時運行這些exe時,會在內存重復加載這些模塊的二進制碼.

如果使用dll,則只在內存加載一次,所有使用該dll的進程會共享此塊內存(當然,每個進程復制一份的dll中的全局變量).

2)不需編譯軟件系統升級,若一個軟件系統使用了dll,則改變該dll(函數名不變)時,系統升級只需要切換此dll即可,不需要重新編譯整個系統.

3)多種語言可使用Dll庫,如用c編寫的dll可在vb中調用.DLL還做得很不夠,因此在dll的基礎上發明了COM技術,更好的解決一系列問題.

最簡單的dll

最簡單的dll并不比c的helloworld難,只要一個DllMain函數即可,包含objbase.h頭文件(支持COM技術的一個頭文件).

該頭文件名字難記,則用windows.h也可以.源碼如下:dll_nolib.cpp

#include <objbase.h>
#include <iostream.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:cout<<"Dll is attached!"<<endl;g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:cout<<"Dll is detached!"<<endl;g_hModule=NULL;break;}return true;
}

其中DllMain是每個dll的入口函數,如同c的函數一樣.DllMain帶三個參數,hModule表示本dll實例句柄,dwReason表示dll當前所處的狀態,如DLL_PROCESS_ATTACH表示dll剛剛被加載一個進程中,DLL_PROCESS_DETACH表示剛剛從一個進程中卸載dll.

當然還有表示加載到線程中和從線程中卸載的狀態,這里省略.最后參數一個保存參數.

如上,在一個進程中加載dll時,dll打印"Dllisattached!"語句;當從進程中卸載dll時,打印"Dllisdetached!"語句.

編譯dll需要以下兩條命令:

cl /c dll_nolib.cpp

這條命令會按obj文件編譯cpp,若不使用/c參數,則cl還會繼續鏈接objexe,但是這里是一個dll,沒有函數,因此會報錯.不要緊,繼續使用鏈接命令.

Link /dll dll_nolib.obj

這條命令會生成dll_nolib.dll.

加載DLL(顯式調用)

一般有兩個方式使用dll,顯式調用和隱式調用.這里首先介紹顯式調用.編寫一個客戶程序:dll_nolib_client.cpp

#include <windows.h>
#include <iostream.h>
int main(void)
{//加載的`dll`HINSTANCE hinst=::LoadLibrary("dll_nolib.dll");if (NULL != hinst){cout<<"dll loaded!"<<endl;}return 0;
}

注意,調用dll使用LoadLibrary函數,它的參數就是dll路徑和名字,返回值dll句柄.使用如下命令編譯鏈接客戶:

Cl dll_nolib_client.cpp

并執行dll_nolib_client.exe,得到如下結果:
Dllisattached!
dllloaded!
Dllisdetached!

以上結果表明客戶已加載dll.但是這樣僅可在內存加載dll,不能找到dll中的函數.

使用dumpbin命令查看DLL中的函數

Dumpbin命令可查看一個dll中的輸出函數符號名,輸入如下命令:

Dumpbin -exports dll_nolib.dll

查看發現dll_nolib.dll并沒有輸出函數.

如何在dll中定義輸出函數

總體來說兩個方法,一個添加一個def定義文件,在此文件中定義dll要輸出的函數;第二個是在源碼中,待輸出的函數前加上__declspec(dllexport)關鍵字.

Def文件

首先寫一個帶輸出函數dll,源碼如下:dll_def.cpp

#include <objbase.h>
#include <iostream.h>
void FuncInDll (void)
{cout<<"FuncInDll is called!"<<endl;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:g_hModule=NULL;break;}return TRUE;
}

dlldef文件如下:dll_def.def

;
//`dll_def`模塊定義文件
;
LIBRARY         dll_def.dll
DESCRIPTION     '(c)2007-2009 Wang Xuebin'
EXPORTSFuncInDll @1 PRIVATE

def的語法很簡單,首先是關鍵字,指定dll的名字;然后一個可選描述關鍵字.

最后是導出關鍵字,后面寫上dll中所有要輸出的函數名或變量名,然后接上@及依次編號的數字(從1到N),最后接上修飾符.

如下命令編譯鏈接帶def文件的dll:

Cl /c dll_def.cpp
Link /dll dll_def.obj /def:dll_def.def

再調用dumpbin查看生成的dll_def.dll:

Dumpbin -exports dll_def.dll

得到結果.
觀察這一行.
1000001000FuncInDll
會發現該dll輸出了FuncInDll函數.

顯式調用DLL中的函數

寫一個dll_def.dll的客戶程序:dll_def_client.cpp

#include <windows.h>
#include <iostream.h>
int main(void)
{//定義一個函數指針typedef void (* DLLWITHLIB )(void);//定義一個函數指針變量DLLWITHLIB pfFuncInDll = NULL;//加載`dll`HINSTANCE hinst=::LoadLibrary("dll_def.dll");if (NULL != hinst){cout<<"dll loaded!"<<endl;}//找到`dll`的`FuncInDll`函數pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll");//調用`dll`里的函數if (NULL != pfFuncInDll){(*pfFuncInDll)();}return 0;
}

兩個地方值得注意,第一是定義和使用函數指針;第二是使用GetProcAddress,來查找dll中的函數地址.
第一個參數DLL句柄,即LoadLibrary返回的句柄,第二個參數dll中的函數名,即dumpbin輸出的函數名.
注意,這里的函數名指的是編譯后的函數名,不一定等于dll源碼中的函數名.

編譯鏈接客戶程序,執行得到:
dllloaded!
FuncInDlliscalled!

即客戶成功調用dll中的FuncInDll函數.

__declspec(dllexport)

為每個dlldef顯得很麻煩,當前def使用已比較少了,更多的是在源碼中,使用__declspec(dllexport)定義dll輸出函數.

Dll寫法同上,去掉def文件,并在每個要輸出的函數前面加上__declspec(dllexport)聲明,如:

__declspec(dllexport) void FuncInDll (void)

這里提供一個dlldll_withlib.cpp源程序,然后編譯鏈接.鏈接時不需要指定/DEF:參數,直接加/DLL參數即可,

Cl /c dll_withlib.cpp
Link /dll dll_withlib.obj

然后使用dumpbin命令查看,得到:

1    0 00001000  FuncInDll@@YAXXZ

可知編譯后的函數名FuncInDll@@YAXXZ.
可用extern"C"指令來命令c++編譯器按c編譯器的方式來命名該函數.如下:

extern "C" __declspec(dllexport) void FuncInDll (void)

dumpbin命令結果:
1000001000 FuncInDll
這樣,顯式調用時只需查找函數名FuncInDll函數即可成功.

隱式調用DLL

顯式調用顯得非常復雜,每次都要LoadLibrary,并且每個函數都必須使用GetProcAddress來得到函數指針,對大量使用dll函數的客戶是個困擾.

隱式調用可像使用c函數庫一樣使用dll中的函數,非常方便快捷.

下面是一個隱式調用的示例:dll包含兩個文件dll_withlibAndH.cppdll_withlibAndH.h.
代碼如下:dll_withlibAndH.h

extern "C" __declspec(dllexport) void FuncInDll (void);
//dll_withlibAndH.cpp
#include <objbase.h>
#include <iostream.h>
#include "dll_withLibAndH.h"//看到沒有,這就是增加的頭文件
extern "C" __declspec(dllexport) void FuncInDll (void)
{cout<<"FuncInDll is called!"<<endl;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:g_hModule=NULL;break;}return TRUE;
}

編譯鏈接命令:

Cl /c dll_withlibAndH.cpp
Link /dll dll_withlibAndH.obj

隱式調用時,需要在客戶引入頭文件,并在鏈接時指明dll對應的lib文件(dll只要有函數輸出,則鏈接時會產生一個與dll同名的lib文件)位置和名.

然后如同調用api函數庫中的函數一樣調用dll中的函數,不需要顯式LoadLibraryGetProcAddress.使用最方便.

客戶代碼如下:dll_withlibAndH_client.cpp

#include "dll_withLibAndH.h"
//注意路徑,加載`dll`的或`項目`|設置|`鏈接`設置里
#pragma comment(lib,"dll_withLibAndH.lib")
int main(void)
{FuncInDll();//只要這樣就可調用`dll`里的函數了return 0;
}

__declspec(dllexport)__declspec(dllimport)配對使用

事實上不使用extern"C"可行的,這時會按c++的符號串編譯函數,如(FuncInDll@@YAXH@Z,FuncInDll@@YAXXZ),當客戶也是c++時,也能正確隱式調用.

這時要考慮一種情況:若DLL1.CPP是源,DLL2.CPP使用了DLL1中的函數,但同時DLL2也是一個DLL,也要輸出一些函數供Client.CPP使用.

則在DLL2中如何聲明所有的,既包含了從DLL1引入的函數,還包括自己要輸出的函數的函數.此時就需要同時使用__declspec(dllexport)__declspec(dllimport)了.

前者用來裝飾dll中的輸出函數,后者用來裝飾從其它dll引入的函數.

所有的源碼包括DLL1.H,DLL1.CPP,DLL2.H,DLL2.CPP,Client.cpp.
值得關注的是DLL1DLL2中都使用的一個編碼方法,見DLL2.H

#ifdef DLL_DLL2_EXPORTS
#define DLL_DLL2_API __declspec(dllexport)
#else
#define DLL_DLL2_API __declspec(dllimport)
#endif
DLL_DLL2_API void FuncInDll2(void);
DLL_DLL2_API void FuncInDll2(int);

頭文件中這樣定義DLL_DLL2_EXPORTSDLL_DLL2_API宏,可確保DLL端的函數用__declspec(dllexport)裝飾,而客戶的函數__declspec(dllimport)裝飾.

當然,記得在編譯dll時加上參數/D "DLL_DLL2_EXPORTS",或干脆就在dllcpp文件第一行加上#define DLL_DLL2_EXPORTS.

DLL中的全局變量和對象

解決了重載函數的問題,則dll中的全局變量和對象都不是問題了,只是有一點語法注意.如源碼所示:dll_object.h

#ifdef DLL_OBJECT_EXPORTS
#define DLL_OBJECT_API __declspec(dllexport)
#else
#define DLL_OBJECT_API __declspec(dllimport)
#endif
DLL_OBJECT_API void FuncInDll(void);
extern DLL_OBJECT_API int g_nDll;
class DLL_OBJECT_API CDll_Object {
public:CDll_Object(void);show(void);//`待辦`:在此處添加你的方法.
};

Cpp文件dll_object.cpp如下:

#define DLL_OBJECT_EXPORTS
#include <objbase.h>
#include <iostream.h>
#include "dll_object.h"
DLL_OBJECT_API void FuncInDll(void)
{cout<<"FuncInDll is called!"<<endl;
}
DLL_OBJECT_API int g_nDll = 9;
CDll_Object::CDll_Object()
{cout<<"ctor of CDll_Object"<<endl;
}
CDll_Object::show()
{cout<<"function show in class CDll_Object"<<endl;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{HANDLE g_hModule;switch(dwReason){case DLL_PROCESS_ATTACH:g_hModule = (HINSTANCE)hModule;break;case DLL_PROCESS_DETACH:g_hModule=NULL;break;}return TRUE;
}

編譯鏈接完后Dumpbin一下,可見輸出了5個符號:

1    0 00001040   0CDll_Object@@QAE@XZ2    1 00001000   4CDll_Object@@QAEAAV0@ABV0@@Z3    2 00001020  FuncInDll@@YAXXZ4    3 00008040  g_nDll@@3HA5    4 00001069  show@CDll_Object@@QAEHXZ

它們分別代表CDll_Object類,類的構造器,FuncInDll函數,g_nDll全局變量和類的顯示成員函數.下面是客戶代碼:dll_object_client.cpp

#include "dll_object.h"
#include <iostream.h>
//注意路徑,加載`dll`的或`項目`|設置|`鏈接`設置里
#pragma comment(lib,"dll_object.lib")
int main(void)
{cout<<"call dll"<<endl;cout<<"call function in dll"<<endl;FuncInDll();//只要這樣就可調用`dll`里的函數了cout<<"global var in dll g_nDll ="<<g_nDll<<endl;cout<<"call member function of class CDll_Object in dll"<<endl;CDll_Object obj;obj.show();return 0;
}

運行該客戶可見:
calldll
callfunctionindll
FuncInDlliscalled!
globalvarindllg_nDll=9
callmemberfunctionofclassCDll_Objectindll
ctorofCDll_Object
functionshowinclassCDll_Object

可知,客戶成功的訪問了dll中的全局變量,并創建了dll中定義的C++對象,還調用該對象的成員函數.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/894746.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894746.shtml
英文地址,請注明出處:http://en.pswp.cn/news/894746.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python----Python高級(并發編程:進程Process,多進程,進程間通信,進程同步,進程池)

一、進程Process 擁有自己獨立的堆和棧&#xff0c;既不共享堆&#xff0c;也不共享棧&#xff0c;進程由操作系統調度&#xff1b;進程切換需要的資源很最大&#xff0c;效率低。 對于操作系統來說&#xff0c;一個任務就是一個進程&#xff08;Process&#xff09;&#xff…

在Mapbox GL JS中“line-pattern”的使用詳解

在Mapbox GL JS中&#xff0c;line-pattern 是一種用于在地圖上繪制帶有圖案的線條的樣式屬性。通過 line-pattern&#xff0c;你可以使用自定義的圖像作為線條的圖案&#xff0c;而不是使用純色或漸變。 1. 基本概念 line-pattern: 該屬性允許你指定一個圖像作為線條的圖案。…

C++ Primer 算術運算符

歡迎閱讀我的 【CPrimer】專欄 專欄簡介&#xff1a;本專欄主要面向C初學者&#xff0c;解釋C的一些基本概念和基礎語言特性&#xff0c;涉及C標準庫的用法&#xff0c;面向對象特性&#xff0c;泛型特性高級用法。通過使用標準庫中定義的抽象設施&#xff0c;使你更加適應高級…

【大數據技術】本機PyCharm遠程連接虛擬機Python

本機PyCharm遠程連接虛擬機Python 注意:本文需要使用PyCharm專業版。 pycharm-professional-2024.1.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso寫在前面 本文主要介紹如何使用本地PyCharm遠程連接虛擬機,運行Python腳本,提高編程效率。 注意: …

堆(Heap)的原理與C++實現

1. 什么是堆&#xff1f; 堆&#xff08;Heap&#xff09;是一種特殊的樹形數據結構&#xff0c;通常用于實現優先隊列。堆可以分為兩種類型&#xff1a; 最大堆&#xff08;Max Heap&#xff09;&#xff1a;每個節點的值都大于或等于其子節點的值。最小堆&#xff08;Min H…

移除元素-雙指針(下標)

題目 給你一個數組 nums 和一個值 val&#xff0c;你需要 原地 移除所有數值等于 val 的元素。元素的順序可能發生改變。然后返回 nums 中與 val 不同的元素的數量。 假設 nums 中不等于 val 的元素數量為 k&#xff0c;要通過此題&#xff0c;您需要執行以下操作&#xff1a…

log4j2日志配置文件

log4j2配置文件每個項目都會用到,記錄一個比較好用的配置文件,方便以后使用時調取,日志輸出級別為debug,也可以修改 <?xml version"1.0" encoding"UTF-8"?> <Configuration monitorInterval"180" packages""><prope…

高等代數筆記—映射與線性空間

映射 映射&#xff1a; σ : M → M ′ \sigma: M \to M σ:M→M′ σ ( a ) a ′ , a ∈ M , a ′ ∈ M ′ \sigma(a)a, a\in M, a \in M σ(a)a′,a∈M,a′∈M′ a ′ a a′是 a a a在 σ \sigma σ下的像&#xff0c; a a a是 a ′ a a′在 σ \sigma σ下的原像 σ : …

提示詞實踐總結

目錄 一、要求創建SqlServer表&#xff08;ChatGpt&#xff09; 二、要求生成多層架構代碼&#xff08;Cursor&#xff09; 三、要求修改方法返回值類型&#xff08;Cursor&#xff09; 四、要求修改方法入參&#xff08;Cursor&#xff09; 五、復雜的多表關聯生成&#…

java進階文章鏈接

java 泛型&#xff1a;java 泛型詳解-絕對是對泛型方法講解最詳細的&#xff0c;沒有之一 Java 泛型&#xff0c;你了解類型擦除嗎&#xff1f; java 注解&#xff1a;深入理解Java注解類型 秒懂&#xff0c;Java 注解 &#xff08;Annotation&#xff09;你可以這樣學 jav…

MyBatis-Plus筆記-快速入門

大家在日常開發中應該能發現&#xff0c;單表的CRUD功能代碼重復度很高&#xff0c;也沒有什么難度。而這部分代碼量往往比較大&#xff0c;開發起來比較費時。 因此&#xff0c;目前企業中都會使用一些組件來簡化或省略單表的CRUD開發工作。目前在國內使用較多的一個組件就是…

Maven jar 包下載失敗問題處理

Maven jar 包下載失敗問題處理 1.配置好國內的Maven源2.重新下載3. 其他問題 1.配置好國內的Maven源 打開??的 Idea 檢測 Maven 的配置是否正確&#xff0c;正確的配置如下圖所示&#xff1a; 檢查項?共有兩個&#xff1a; 確認右邊的兩個勾已經選中&#xff0c;如果沒有請…

Spring 核心技術解析【純干貨版】- IX:Spring 數據訪問模塊 Spring-Jdbc 模塊精講

在現代企業級應用中&#xff0c;數據訪問層的穩定性和高效性至關重要。為了簡化和優化數據庫操作&#xff0c;Spring Framework 提供了 Spring-JDBC 模塊&#xff0c;旨在通過高度封裝的 JDBC 操作&#xff0c;簡化開發者的編碼負擔&#xff0c;減少冗余代碼&#xff0c;同時提…

探秘AI的兩大核心:決策式AI與生成式AI?

目錄 一、引言 二、從定義上來看 1. 決策式AI&#xff08;Discriminative AI&#xff09; 2. 生成式AI&#xff08;Generative AI&#xff09; 三、從技術原理上來看 1. 決策式AI&#xff08;Discriminative AI&#xff09; 2. 生成式AI&#xff08;Generative AI&#…

2.5學習

misc buuctf-假如給我三天光明 下載附件后得到了一個壓縮包和一個圖片&#xff0c;壓縮包為加密壓縮包&#xff0c;需要解出密碼&#xff0c;然后注意到這個圖片并非簡單的一個封面&#xff0c;在下方還有諸多點&#xff0c;有黑有灰。經過搜索&#xff0c;發現這是盲文通過與…

sed變量中特殊字符/處理方式

個人博客地址&#xff1a;sed變量中特殊字符/處理方式 | 一張假鈔的真實世界 如果變量值中包含斜杠&#xff08;/&#xff09;特殊字符&#xff0c;在使用sed命令的做行內字符串替換時可以使用井號&#xff08;#&#xff09;做為sed語法分隔符&#xff0c;如下&#xff1a; G…

java進階1——JVM

java進階——JVM 1、JVM概述 作用 Java 虛擬機就是二進制字節碼的運行環境&#xff0c;負責裝載字節碼到其內部&#xff0c;解釋/編譯為對 應平臺上的機器碼指令行&#xff0c;每一條 java 指令&#xff0c;java 虛擬機中都有詳細定義&#xff0c;如怎么取操 作數&#xff0c…

搭建集成開發環境PyCharm

1.下載安裝Python&#xff08;建議下載并安裝3.9.x&#xff09; https://www.python.org/downloads/windows/ 要注意勾選“Add Python 3.9 to PATH”復選框&#xff0c;表示將Python的路徑增加到環境變量中 2.安裝集成開發環境Pycharm http://www.jetbrains.com/pycharm/…

vue2-v-if和v-for的優先級

vue2-v-if和v-for的優先級 1.v-if和v-for的作用 v-if是條件渲染&#xff0c;只有條件表達式true的情況下&#xff0c;才會渲染v-for是基于一個數組來渲染一個列表&#xff0c;在v-for的時候&#xff0c;保證給每個元素添加獨一無二的key值&#xff0c;便于diff算法進行優化 …

通過C/C++編程語言實現“數據結構”課程中的鏈表

引言 鏈表(Linked List)是數據結構中最基礎且最重要的線性存儲結構之一。與數組的連續內存分配不同,鏈表通過指針將分散的內存塊串聯起來,具有動態擴展和高效插入/刪除的特性。本文將以C/C++語言為例,從底層原理到代碼實現,手把手教你構建完整的鏈表結構,并深入探討其應…