說說DLL

轉載地址:http://qiusuoge.com/8228.html

   自從微軟推出第一個版本的Windows操作系統以來,動態鏈接庫(DLL)一直是Windows操作系統的基礎。動態鏈接庫通常都不能直接運行,也不能接收消息。它們是一些獨立的文件,其中包含能被可執行程序或其它DLL調用來完成某項工作的函數。只有在其它模塊調用動態鏈接庫中的函數時,它才發揮作用。

    Windows API中的所有函數都包含在DLL中。其中有3個最重要的DLL,Kernel32.dll,它包含用於管理內存、進程和線程的各個函數;User32.dll,它包含用於執行用戶界面任務(如窗口的創建和消息的傳送)的各個函數;GDI32.dll,它包含用於畫圖和顯示文本的各個函數。 
靜態庫和動態庫 
    靜態庫:函數和數據被編譯進一個二進制文件(通常擴展名爲.LIB)。在使用靜態庫的情況下,在編譯鏈接可執行文件時,鏈接器從庫中複製這些函數和數據並把它們和應用程序的其它模塊組合起來創建最終的可執行文件(.EXE文件)。 
    在使用動態庫的時候,往往提供兩個文件:一個引入庫和一個DLL。引入庫包含被DLL導出的函數和變量的符號名,DLL包含實際的函數和數據。在編譯鏈接可執行文件時,只需要鏈接引入庫,DLL中的函數代碼和數據並不複製到可執行文件中,在運行的時候,再去加載DLL,訪問DLL中導出的函數。 
    使用動態鏈接庫的好處 
可以採用多種編程語言來編寫。 
增強產品的功能。 
提供二次開發的平臺。 
簡化項目管理。 
可以節省磁盤空間和內存。 
有助於資源的共享。 
有助於實現應用程序的本地化。 
動態鏈接庫被多個進程訪問

動態鏈接庫加載的兩種方式

預備知識

:::::::::::::::::::::::::::

進程的虛擬地址空間:

在32爲系統中,系統爲沒個進程分配2^32的地址空間

具體可參看《windows核心編程》

預處理命令:

沒有什麼可以說的了

看看代碼就明白了!!

環境變量:(環境變量的概念我就不介紹了,具體的可以參看windows 核心編程,上面有很詳細的說明)把DLL放到當前任意的環境變量中就可以加載

定義函數指針

格式:typedef int (*proc)(int a,int b);

注意:proc是一個函數指針類型而不是一個變量

然後我們可以用這個指針類型去定義變量

Proc myproc;這裏的myproc就是一個指針變量

要是實在無聊 內心空虛 沒事可做的話 可以去這個網站看看函數的指針http://ufo.tyedu.com/study/programmer/language_C/200412/1472.html

函數的調用約定(可以不必瞭解,但是理解後可以讓你理解DLL的調用更爲深刻)

函數的調用約定:函數調用約定是函數調用者和被調用的函數體之間關於參數傳遞、返回值傳遞、堆棧清除、寄存器使用的一種約定; 
    它是需要二進制級別兼容的強約定,函數調用者和函數體如果使用不同的調用約定,將可能造成程序執行錯誤,必須把它看作是函數聲明的一部分;

常見的函數調用約定:

VC6中的函數調用約定:

調用約定        堆棧清除    參數傳遞 
        __cdecl         調用者      從右到左,通過堆棧傳遞 
        __stdcall       函數體      從右到左,通過堆棧傳遞 
        __fastcall      函數體      從右到左,優先使用寄存器(ECX,EDX),然後使用堆棧 
        thiscall        函數體      this指針默認通過ECX傳遞,其它參數從右到左入棧

__cdecl是C/C++的默認調用約定; VC的調用約定中並沒有thiscall這個關鍵字,它是類成員函數默認調用約定; 
C/C++中的main(或wmain)函數的調用約定必須是__cdecl,不允許更改; 
默認調用約定一般能夠通過編譯器設置進行更改,如果你的代碼依賴於調用約定,請明確指出需要使用的調用約定;

常見的函數調用約定中,只有cdecl約定需要調用者來清除堆棧; 
C/C++中的函數支持參數數目不定的參數列表,比如printf函數;由於函數體不知道調用者在堆棧中壓入了多少參數, 
所以函數體不能方便的知道應該怎樣清除堆棧,那麼最好的辦法就是把清除堆棧的責任交給調用者; 
這應該就是cdecl調用約定存在的原因吧;

VB一般使用的是stdcall調用約定;(ps:有更強的保證嗎) 
Windows的API中,一般使用的是stdcall約定;(ps: 有更強的保證嗎) 
建議在不同語言間的調用中(如DLL)最好採用stdcall調用約定,因爲它在語言間兼容性支持最好;

三:函數返回值傳遞方式 
其實,返回值的傳遞從處理上也可以想象爲函數調用的一個out形參數; 函數返回值傳遞方式也是函數調用約定的一部分; 
有返回值的函數返回時:一般int、指針等32bit數據值(包括32bit結構)通過eax傳遞,(bool,char通過al傳遞,short通過 ax傳遞),特別的__int64等64bit結構(struct) 通過edx,eax兩個寄存器來傳遞(同理:32bit整形在16bit環境中通過dx,ax傳遞); 其他大小的結構(struct)返回時把其地址通過eax返回;(所以返回值類型不是1,2,4,8byte時,效率可能比較差) 
   參數和返回值傳遞中,引用方式的類型可以看作與傳遞指針方式相同; 
   float/double(包括Delphi中的extended)都是通過浮點寄存器st(0)返回;

具體的分析參看:http://blog.csdn.net/avalonbbs/archive/2004/12/25/229300.aspx

::::::::::::::::::::::::::::::::::::

隱式鏈接

本文現在對隱式鏈接不作具體的說明,只是做一個大概的介紹(下次再做具體的說明).當進程運行的時候,所有的相關的DLL都被加載到內存,然後映射到進程的地址空間,當一個進程要調用很多個DLL的時候,這種方法就顯得特別浪費內存,所以在加載很多個DLL的時候,最好用顯示加載的方式

在調用DLL裏面的函數時候

要用extern 聲明是外部變量

比如 extern int add(int num1,int num2);

但是還應該注意的就是:在編譯DLL時,要把編譯的LIB文件放到執行文件的目錄下,並且在編譯執行文件的時候要連接LIB文件。

在寫DLL的時候要導出的函數也就是在能被外部程序調用的函數前面加上

一般大型項目在開發DLL中,要進行預定義聲明的

======================================

在定義DLL的時候要定義導出函數就要在該函數前面加__declspec(DLLexport)時,C++編譯器爲了支持函數的重載會進行函數名字改編,當可執行模塊執行該函數時由於找不到該函數的名字,於是調用就會出現錯誤!當使用extern “C”時就可以告訴編譯器不必修改函數名和變量名。這樣就可以用C++或C編譯器程序調用該函數。

以上操作只有在VC++創建的的可執行模塊來調用該DLL,如果使用其他的編譯器的模塊來調用該DLL,就需要執行一些額外的操作。

C編譯在進行編譯的時候也會進行名字的改編,當函數使用_stdcall(WINAPI)調用規則時,MS編譯器就會改編函數的名稱。

比如:__declspec(DLLexport) LONG __stdcall Proc(int a ,int b);

編譯器會改編爲__Proc@8

因此當非C++或非C編譯器調用該DLL中的函數Proc時,就會出現找不到函數的情況。

這樣我們就可以定義DEF文件來解決,並且在DEF文件加上下面的EXPORTS:

EXPORTS

        Proc

Def模塊執行原理:當連接程序分析這個DEF文件時,它發現Proc和__Proc@8都被輸出,由於這兩個函數是相互匹配的,因此連接程序使用Proc來輸出該函數,根本不使用__Proc@8來輸出函數名

=======================================

下面是def的具體使用方法

----------------------------------------------------------------------------------------------------------------------模塊定義文件(.DEF)是一個或多個用於描述DLL屬性的模塊語句組成的文本文件,每個DEF文件至少必須包含以下模塊定義語句:

* 第一個語句必須是LIBRARY語句,指出DLL的名字;

* EXPORTS語句列出被導出函數的名字;將要輸出的函數修飾名羅列在EXPORTS之下,這

個名字必須與定義函數的名字完全一致,如此就得到一個沒有任何修飾的函數名了。

* 可以使用DESCRIPTION語句描述DLL的用途(此句可選);

* ";"對一行進行註釋(可選)。

----------------------------------------------------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////dlltest.h

#ifdef DLL1_API

#else

#define DLL1_API extern "C" _declspec(dllimport)

#endif

DLL1_API int _stdcall add(int a,int b);

DLL1_API int _stdcall subtract(int a,int b);

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////dlltest.cpp

#define DLL1_API extern "C" _declspec(dllexport)

#include "Dll1.h"

#include <stdio.h>

int _stdcall add(int a,int b)

{

return a+b;

}

int _stdcall subtract(int a,int b)

{

return a-b;

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// def文件

LIBRARY dlltest

EXPORTS

add

subtract

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

有了上面的那些文件之後就可以在如何地方調用這些函數了

void CDllTestDlg::OnBtnSubtract()

{

// TODO: Add your control notification handler code here

     CString str;

     str.Format("5-3=%d",subtract(5,3));

     MessageBox(str);

}

void CDllTestDlg::OnBtnOutput()

{

// TODO: Add your control notification handler code here

     Point pt;

     pt.output(5,3);

}下面具體介紹顯示加載

顯示加載

VC++編譯器在編譯DLL的時候函數會發生名字改編;主要在非C++環境中就不能識別該函數了,所以這裏應該定義模塊文件類型DEF,主要就方便了非C++程序可以調用該DLL裏面的函數

再使用顯示加載前必須要注意的是名字的改編問題,因爲再動態加載中名字改編後在加載就得不原來的函數名字了,這樣加載就會失敗。但是可以用另外一種方法加載:MSDN上對GetProAddress中的第二個參數是這樣說明的Pointer to a null-terminated string that specifies the function or variable name, or the function's ordinal value.也就是說可以使用函數的序號來調用該函數,具體使用方法是ProcAdd = (MYPROC) GetProcAddress(hinstLib, MakeIntResource(i)); (i代表函數在DLL中的序號,可以用DUMPBIN工具查看),但是一般的都不用這種轉換序號的方式來取得函數的地址,因爲這樣非常的不直觀!下面就用模塊定義文件(DEF)來避免DLL中函數的名字的改編問題

顯示加載DLL

//MSDN上的對DLL進程顯示加載的DEMO

Using Run-Time Dynamic Linking

You can use the same DLL in both load-time and run-time dynamic linking. The following example uses theLoadLibrary function to get a handle to the Myputs DLL (see Creating a Simple Dynamic-Link Library). If LoadLibrary succeeds, the program uses the returned handle in the GetProcAddressfunction to get the address of the DLL's myPuts function. After calling the DLL function, the program calls the FreeLibrary function to unload the DLL.

Because the program uses run-time dynamic linking, it is not necessary to link the module with an import library for the DLL.

This example illustrates an important difference between run-time and load-time dynamic linking. If the DLL is not available, the application using load-time dynamic linking must simply terminate. The run-time dynamic linking example, however, can respond to the error.

// A simple program that uses LoadLibrary and

// GetProcAddress to access myPuts from Myputs.dll.

#include <stdio.h>

#include <windows.h>

typedef int (*MYPROC)(LPTSTR);

VOID main(VOID)

{

    HINSTANCE hinstLib;

    MYPROC ProcAdd;

    BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;

// Get a handle to the DLL module.

hinstLib = LoadLibrary(TEXT("DllTest"));

// If the handle is valid, try to get the function address.

if (hinstLib != NULL)

    {

        ProcAdd = (MYPROC) GetProcAddress(hinstLib, TEXT("Proc"));

// If the function address is valid, call the function.

if (NULL != ProcAdd)

        {

            fRunTimeLinkSuccess = TRUE;

            (ProcAdd) (TEXT("Message via DLL function/n"));

        }

// Free the DLL module.

fFreeResult = FreeLibrary(hinstLib);

    }

// If unable to call the DLL function, use an alternative.

if (! fRunTimeLinkSuccess)

       printf("Message via alternative method/n");

}

對以上的幾個函數作一些必要的說明:

LoadLibrary:加載指定的DLL,加載方式是先在當前目錄中查找,如果找不到再再環境變量目錄下查找;

還是看MSDN上的說明

The LoadLibrary function maps the specified executable module into the address space of the calling process.

HMODULE LoadLibrary(

LPCTSTR lpFileName

);

Parameters

lpFileName

[in] Pointer to a null-terminated string that names the executable module (either a .dll or .exe file). The name specified is the file name of the module and is not related to the name stored in the library module itself, as specified by the LIBRARY keyword in the module-definition (.def) file.

GetProcAddress:是取得已知的DLL中的函數,返回指定函數的地址

MSDN上的說明

This function returns the address of the specified exported DLL function.

FARPROC GetProcAddress(

HMODULE hModule,

LPCWSTR lpProcName

);

Parameters

hModule

[in] Handle to the DLL module that contains the function.

The LoadLibrary or GetModuleHandle function returns this handle.

lpProcName

[out] Pointer to a null-terminated string containing the function name, or specifies the function's ordinal value.

If this parameter is an ordinal value, it must be in the low-order word; the high-order word must be zero.

The lpProcName parameter must be in Unicode.

Remark:

The GetProcAddress function is used to retrieve addresses of exported functions in DLLs.

The spelling and case of the function name pointed to by lpProcName must be identical to that in the EXPORTS statement of the source DLL's module-definition (.def) file.

The exported names of Win32 APIs might differ from the names you use when calling these functions in your code. This difference is hidden by macros used in the SDK header files.


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