程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 如何在Visual Studio中使用C++創建和使用DLL

如何在Visual Studio中使用C++創建和使用DLL

編輯:關於C語言
 

什麼是DLL(動態鏈接庫)?

DLL是一個包含可由多個程序同時使用的代碼和數據的庫。例如:在Windows操作系統中,Comdlg32 DLL執行與對話框有關的常見函數。因此,每個程序都可以使用該DLL中包含的功能來實現“打開”對話框。這有助於促進代碼重用和內存的有效使用。這篇文章的目的就是讓你一次性就能了解和掌握DLL。

為什麼要使用DLL(動態鏈接庫)?

代碼復用是提高軟件開發效率的重要途徑。一般而言,只要某部分代碼具有通用性,就可以將它構造成相對獨立的功能模塊並在之後的項目中重復使用。比較常見的例子是各種應用程序框架,它們都以源代碼的形式發布。由於這種復用是源代碼級別的,源代碼完全暴露給了程序員,因而稱之為“白盒復用”。白盒復用有以下三個缺點:

  1. 暴露源代碼,多份拷貝,造成存儲浪費;
  2. 容易與程序員的本地代碼發生命名沖突;
  3. 更新模塊功能比較困難,不利於問題的模塊化實現;

為了彌補這些不足,就提出了“二進制級別”的代碼復用了。使用二進制級別的代碼復用一定程度上隱藏了源代碼,對於“黑盒復用”的途徑不只DLL一種,靜態鏈接庫,甚至更高級的COM組件都是。

使用DLL主要有以下優點:

  1. 使用較少的資源;當多個程序使用同一函數庫時,DLL可以減少在磁盤和物理內存中加載的代碼的重復量。這不僅可以大大影響在前台運行的程序,而且可以大大影響其它在Windows操作系統上運行的程序;
  2. 推廣模塊式體系結構;
  3. 簡化部署與安裝。

創建DLL

打開Visual Studio 2012,創建如下圖的工程:

果凍想 | 一個原創文章分享網站

輸入工程名字,單擊[OK];

果凍想 | 一個原創文章分享網站

單擊[Finish],工程創建完畢了。

現在,我們就可以在工程中加入我們的代碼了。加入MyCode.h和MyCode.cpp兩個文件;在MyCode.h中輸入以下代碼:

#ifndef _MYCODE_H_
#define _MYCODE_H_
#ifdef DLLDEMO1_EXPORTS
#define EXPORTS_DEMO _declspec( dllexport )#else#define EXPORTS_DEMO _declspec(dllimport)#endifextern "C" EXPORTS_DEMO int Add (int a , int b);#endif

在MyCode.cpp中輸入以下代碼:

#include "stdafx.h"#include "MyCode.h"int Add ( int a , int b ){
       return ( a + b );}

編譯工程,就會生成DLLDemo1.dll文件。在代碼中,很多細節的地方,我稍後進行詳細的講解(工程下載)。

使用DLL

當我們的程序需要使用DLL時,就需要去加載DLL,在程序中加載DLL有兩種方法,分別為加載時動態鏈接和運行時動態鏈接。

  1. 在加載時動態鏈接中,應用程序像調用本地函數一樣對導出的DLL函數進行顯示調用。要使用加載時動態鏈接,需要在編譯和鏈接應用程序時提供頭文件和導入庫文件(.lib)。當這樣做的時候,鏈接器將向系統提供加載DLL所需的信息,並在加載時解析導出的DLL函數的位置;
  2. 在運行時動態鏈接中,應用程序調用LoadLibrary函數或LoadLibraryEx函數以在運行時加載DLL。成功加載DLL後,可以使用GetProcAddress函數獲得要調用的導出的DLL函數的地址。在使用運行時動態鏈接時,不需要使用導入庫文件。

在實際編程時有兩種使用DLL的方法,那麼到底應該使用那一種呢?在實際開發時,是基於以下幾點進行考慮的:

  1. 啟動性能如果應用程序的初始啟動性能很重要,則應使用運行時動態鏈接;
  2. 易用性在加載時動態鏈接中,導出的DLL函數類似於本地函數,我們可以方便地進行這些函數的調用;
  3. 應用程序邏輯在運行時動態鏈接中,應用程序可以分支,以便按照需要加載不同的模塊。

下面,我將分別使用兩種方法調用DLL動態鏈接庫。

加載時動態鏈接:

#include <windows.h>#include <iostream>//#include "..\\DLLDemo1\\MyCode.h"using namespace std;#pragma comment(lib, "..\\debug\\DLLDemo1.lib")extern "C" _declspec(dllimport) int Add(int a, int b);int main(int argc, char *argv[]){
      cout<<Add(2, 3)<<endl;
      return 0;}

運行時動態鏈接:

#include <windows.h>#include <iostream>using namespace std;typedef int (*AddFunc)(int a, int b);int main(int argc, char *argv[]){
      HMODULE hDll = LoadLibrary("DLLDemo1.dll");
      if (hDll != NULL)
      {
            AddFunc add = (AddFunc)GetProcAddress(hDll, "Add");
            if (add != NULL)
            {
                  cout<<add(2, 3)<<endl;
            }
            FreeLibrary(hDll);
      }}

上述代碼都在DLLDemo1工程中。(工程下載)。

DllMain函數

Windows在加載DLL時,需要一個入口函數,就像控制台程序需要main函數一樣。有的時候,DLL並沒有提供DllMain函數,應用程序也能成功引用DLL,這是因為Windows在找不到DllMain的時候,系統會從其它運行庫中引入一個不做任何操作的默認DllMain函數版本,並不意味著DLL可以拋棄DllMain函數。

根據編寫規范,Windows必須查找並執行DLL裡的DllMain函數作為加載DLL的依據,它使得DLL得以保留在內存裡。這個函數並不屬於導出函數,而是DLL的內部函數,這就說明不能在客戶端直接調用DllMain函數,DllMain函數是自動被調用的。

DllMain函數在DLL被加載和卸載時被調用,在單個線程啟動和終止時,DllMain函數也被調用。參數ul_reason_for_call指明了調用DllMain的原因,有以下四種情況:

DLL_PROCESS_ATTACH:當一個DLL被首次載入進程地址空間時,系統會調用該DLL的DllMain函數,傳遞的ul_reason_for_call參數值為DLL_PROCESS_ATTACH。這種情況只有首次映射DLL時才發生;

DLL_THREAD_ATTACH:該通知告訴所有的DLL執行線程的初始化。當進程創建一個新的線程時,系統會查看進程地址空間中所有的DLL文件映射,之後用DLL_THREAD_ATTACH來調用DLL中的DllMain函數。要注意的是,系統不會為進程的主線程使用值DLL_THREAD_ATTACH來調用DLL中的DllMain函數;

DLL_PROCESS_DETACH:當DLL從進程的地址空間解除映射時,參數ul_reason_for_call參數值為DLL_PROCESS_DETACH。當DLL處理DLL_PROCESS_DETACH時,DLL應該處理與進程相關的清理操作。如果進程的終結是因為系統中有某個線程調用了TerminateProcess來終結的,那麼系統就不會用DLL_PROCESS_DETACH來調用DLL中的DllMain函數來執行進程的清理工作。這樣就會造成數據丟失;

DLL_THREAD_DETACH:該通知告訴所有的DLL執行線程的清理工作。注意的是如果線程的終結是使用TerminateThread來完成的,那麼系統將不會使用值DLL_THREAD_DETACH來執行線程的清理工作,這也就是說可能會造成數據丟失,所以不要使用TerminateThread來終結線程。以上所有講解在工程DLLMainDemo(工程下載)都有體現。

函數導出方式

在DLL的創建過程中,我使用的是_declspec( dllexport )方式導出函數的,其實還有另一種導出函數的方式,那就是使用導出文件(.def)。你可以在DLL工程中,添加一個Module-Definition File(.def)文件。.def文件為鏈接器提供了有關被鏈接器程序的導出、屬性及其它方面的信息。

對於上面的例子,.def可以是這樣的:

LIBRARY     "DLLDemo2"
EXPORTS
Add @ 1 ;Export the Add function

Module-Definition File(.def)文件的格式如下:

  1. LIBRARY語句說明.def文件對應的DLL;
  2. EXPORTS語句後列出要導出函數的名稱。可以在.def文件中的導出函數名後加@n,表示要導出函數的序號為n(在進行函數調用時,這個序號有一定的作用)。

使用def文件,生成了DLL,客戶端調用代碼如下:

#include <windows.h>#include <iostream>using namespace std;typedef int (*AddFunc)(int a, int b);int main(int argc, char *argv[]){
      HMODULE hDll = LoadLibrary("DLLDemo2.dll");
      if (hDll != NULL)
      {
            AddFunc add = (AddFunc)GetProcAddress(hDll, MAKEINTRESOURCE(1));
            if (add != NULL)
            {
                  cout<<add(2, 3)<<endl;
            }
            FreeLibrary(hDll);
      }}

可以看到,在調用GetProcAddress函數時,傳入的第二個參數是MAKEINTRESOURCE(1),這裡面的1就是def文件中對應函數的序號。(工程下載)  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved