程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 在Visual Studio頂用C++說話創立DLL靜態鏈接庫圖文教程

在Visual Studio頂用C++說話創立DLL靜態鏈接庫圖文教程

編輯:關於C++

在Visual Studio頂用C++說話創立DLL靜態鏈接庫圖文教程。本站提示廣大學習愛好者:(在Visual Studio頂用C++說話創立DLL靜態鏈接庫圖文教程)文章只能為提供參考,不一定能成為您想要的結果。以下是在Visual Studio頂用C++說話創立DLL靜態鏈接庫圖文教程正文


甚麼是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)
#endif
extern "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文件中對應函數的序號。(工程下載)

extern “C”

為何要應用extern “C”呢?C++之父在設計C++時,斟酌到其時曾經存在了年夜量的C代碼,為了支撐本來的C代碼和曾經寫好的C庫,須要在C++中盡量的支撐C,而extern “C”就是個中的一個戰略。在聲明函數時,留意到我也應用了extern “C”,這裡要具體的說說extern “C”。

extern “C”包括兩層寄義,起首是它潤飾的目的是”extern”的;其次,被它潤飾的目的才是”C”的。先來講說extern;在C/C++中,extern用來注解函數和變量感化規模(可見性)的症結字,這個症結字告知編譯器,它聲名的函數和變量可以在本模塊或其它模塊中應用。extern的感化總結起來就是以下幾點:

1.在一個文件內,假如內部變量不在文件的開首界說,其有用規模只限制在從界說開端到文件的停止處。假如在界說前須要援用該變量,則要在援用之前用症結字”extern”對該變量做”內部變量聲明”,表現該變量是一個曾經界說的內部變量。有了這個聲明,便可以從聲明處起公道地應用該變量了,例如:

/*
** FileName     : Extern Demo
** Author       : Jelly Young
** Date         : 2013/11/18
** Description  : More information
*/
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
      extern int a;
      cout<<a<<endl;
}
int a = 100;

2.在多文件的法式中,假如多個文件都要應用統一個內部變量,不克不及在各個文件中各界說一個內部變量,不然會湧現“反復界說”的毛病。准確的做法是在隨意率性一個文件中界說內部變量,其它文件用extern對變量做“內部變量聲明”。在編譯和鏈接時,體系會曉得該變量是一個曾經在別處界說的內部變量,並把另外一文件中內部變量的感化域擴大到本文件,如許在本文件便可以正當地應用該內部變量了。寫過MFC法式的人都曉得,在在CXXXApp類的頭文件中,就應用extern聲清楚明了一個該類的變量,而該變量的現實界說是在CXXXApp類的完成文件中完成的;

3.內部函數,在界說函數時,假如在最左端加症結字extern,表現此函數是內部函數。C說話劃定,假如在界說時省略extern,則隱含為內部函數。而外部函數必需在後面加static症結字。在須要挪用此函數的文件中,用extern對函數出聲明,注解該函數是在其它文件中界說的內部函數。

接著說”C”的寄義。我們都曉得C++經由過程函數參數的分歧類型支撐重載機制,編譯器依據參數為每一個重載函數發生分歧的外部標識符;然則,假如碰到了C++法式要挪用曾經被編譯後的C函數,那該怎樣辦呢?好比下面的int Add ( int a , int b )函數。該函數被C編譯器後在庫中的名字為_Add,而C++編譯器則會生成像_Add_int_int之類的名字用來支撐函數重載和類型平安。因為編譯後的名字分歧,C++法式不克不及直接挪用C函數,所以C++供給了一個C銜接交流指定符號extern “C”來處理這個成績;所以,在下面的DLL中,Add函數的聲明格局為:extern “C” EXPORTS_DEMO int Add (int a , int b)。如許就告知了C++編譯器,函數Add是個C銜接的函數,應當到庫中找名字_Add,而不是找_Add_int_int。當我們將下面DLL中的”C”去失落,編譯生成新的DLL,應用Dependency Walker對象檢查該DLL,如圖:

請留意導出方法為C++,並且導出的Add函數的名字添加了許多的器械,當應用這類方法導出時,客戶端挪用時,代碼就是上面如許:


#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@@YAHHH@Z");
          if (add != NULL)
          {
               cout<<add(2, 3)<<endl;
          }
          FreeLibrary(hDll);
     }
}

請留意GetProcAddress函數的第二個參數,該參數名就是導出的函數名,在編碼時,寫如許一個名字是否是很奇異啊。當我們應用extern “C”方法導出時,截圖以下:

留意導出方法為C,並且函數名如今就是通俗的Add了。我們再應用GetProcAddress時,便可以直接指定Add了,而不消再加那一長串奇異的名字了。

DLL導出變量

DLL界說的全局變量可以被挪用過程拜訪;DLL也能夠拜訪挪用過程的全局數據。(工程下載)

DLL導出類

DLL中界說的類,也能夠被導出。具體工程代碼,請拜見(工程下載)

總結

對DLL的講授就到此停止,因為MFC在如今的情況下應用較少,此處不予講授,假如今後做項目碰到了MFC的DLL相干常識,我再做總結。最初,願望年夜家給我的博客提出一些中肯的建議。

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