dll編程 01

1.

  先來述一下DLL(Dynamic Linkable Library)的概念,你可以簡單的把DLL看成一種倉庫,它提供你一些可以直接拿來用的量、函數或。在倉庫展史上經歷-靜態鏈動態鏈代。靜態鏈動態鏈都是共享代的方式,如果採用靜態鏈你願不願意,lib中的指令都被直接包含在最生成的EXE文件中了。但是若使用DLLDLL不必被包含在最EXE文件中,EXE文件可以動態地引用和卸載這個與EXE獨立的DLL文件。靜態鏈動態鏈的另外一個區在於靜態鏈中不能再包含其他的動態鏈或者靜態庫,而在動態鏈可以再包含其他的動態或靜態鏈

對動態鏈,我們還需建立如下概念:
      
1DLL 制與具體的言及編譯器無

  只要遵循定的DLL接口範和調用方式,用各種語寫的DLL都可以相互調用。譬如Windows提供的系DLL(其中包括了WindowsAPI),在任何開發環境中都能被調用,不在乎其是Visual BasicVisual C++Delphi

  (2動態鏈

  我Windows下的system32文件中會看到kernel32.dlluser32.dllgdi32.dllwindows的大多數API都包含在DLL中。kernel32.dll中的函數主要理內存管理和調度;user32.dll中的函數主要控制用界面;gdi32.dll中的函數則負責圖形方面的操作。

  一般的程序都用過類MessageBox的函數,其它就包含在user32.dll動態鏈中。由此可DLL並不陌生。


          (3)VC動態鏈的分

  Visual C++支持三DLLNon-MFC DLL(非MFC動態庫)、MFC Regular DLLMFC規則DLL)、MFC Extension DLLMFCDLL)。

  非MFC動態庫不採用MFC類庫結構,其出函數爲標準的C接口,能被非MFCMFC寫的用程序所調用;MFC規則DLL 包含一個承自CWinApp,但其無消息循MFCDLL採用MFC動態鏈接版本建,它只能被用MFC類庫寫的用程序所調用。

   當然看懂本文不是者的最目的,應親踐才能真正掌握DLL的奧妙。


  2.態鏈

  態鏈解不是本文的重點,但是在具體DLL之前,通一個靜態鏈的例子可以快速地幫助我建立的概念。VC++6.0new一個名稱libTeststatic library工程,並新建lib.hlib.cpp兩個文件,lib.hlib.cpp的源代如下

 

//文件:lib.h

#ifndef LIB_H
#define LIB_H
extern "C" int add(int x,int y);
   //聲明C編譯接方式的外部函數
#endif

//
文件:lib.cpp

#include "lib.h"
int add(int x,int y)
{
 return x + y;
}

       編譯這個工程就得到了一個.lib文件,個文件就是一個函數,它提供了add的功能。文件和.lib文件提交後,用就可以直接使用其中的add函數了。

       [充,如果出如下錯誤按此方法解決]

Alt+F7入當前工程的Settings選擇C/C++選項卡,從Category合框中Precompiled Headers選擇Not Using Precompiled headers。確定。
如果錯誤的文件原本是工程中的,則檢查該文件部有沒有#i nclude "stdafx.h"句,沒有的添加。如果不行,也有可能是定構體等最後忘了加分號,注意一下

  Turbo C2.0中的C函數(我用來的scanfprintfmemcpystrcpy等)就來自這種態庫

  下面來看看怎使用,在libTest工程所在的工作區內new一個libCall工程。libCall工程包含一個main.cpp文件,它演示了靜態鏈調用方法,其源代如下:

 

#include <stdio.h>
#include "../lib.h"
#pragma comment( lib, "..//debug//libTest.lib" )
 //指定與靜態庫一起

int main(int argc, char* argv[])
{
 printf( "2 + 3 = %d", add( 2, 3 ) );
}

 

       態鏈調用就是這麼簡單,或們每天都在用,可是我沒有明白個概念。代#pragma comment( lib , "..//debug//libTest.lib" )的意思是指本文件生成的.obj文件libTest.lib一起接。如果不用#pragma comment指定,可以直接在VC++,依次選擇toolsoptionsdirectorieslibrary files選項,填入文件路徑。中加圈的部分添加的libTest.lib文件的路徑。

 

         個靜態鏈的例子至少明白了函數是怎回事,它是哪來的。我們現在有下列模糊認識了:

  (1不是個怪物,的程序和寫一般的程序區不大,只是不能行;

  (2提供一些可以給別的程序調用的東東的程序要調用它必以某方式指明它要調用之。

  以上從靜態鏈分析而得到的對庫懂概念可以直接引申到動態鏈中,動態鏈與靜態鏈寫和調用上的不同體的外部接口定調用方式略有差異。

 

3.調試

  在具體入各DLL詳細闡述之前,有必要對庫文件的調試看方法行一下介,因從下一節開始我將面大量的例子工程。

  由於文件不能行,因而在按下F5debug模式行)或CTRL+F5(運行),其出如3所示的對話框,要求用戶輸入可行文件的路徑來啓動庫函數的行。候我們輸入要調該庫EXE文件的路徑就可以對庫進調試了,其調試技巧與一般用工程的調試
通常有比上述做法更好的調試途徑,那就是工程和用工程(調的工程)放置在同一VC工作區,只對應用工程調試,在用工程調中函數的處設置斷點,行後按下F11這樣單步進入了中的函數。2中的libTestlibCall工程就放在了同一工作區, 上述調試方法態鏈動態鏈而言是一致的。

     動態鏈中的出接口可以使用Visual C++Depends工具看,Depends中的user32.dll,看到了吧?圈內的就是幾個版本的MessageBox了!

    當然Depends工具也可以DLL構,若用它打一個可行文件可以看出個可行文件調用了哪些DLL

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

VC++動態鏈庫編程之非MFC DLL

4.1一個簡單DLL

  第2節給出了以靜態鏈方式提供add函數接口的方法,接下來我來看看怎動態鏈庫實現一個同功能的add函數。

  在VC++new一個Win32 Dynamic-Link Library工程dllTest。注意不要選擇MFC AppWizard(dll),因MFC AppWizard(dll)建立的將是第56述的MFC 動態鏈
  在建立的工程中添加lib.hlib.cpp文件,源代如下:

/* 文件名:lib.h */

#ifndef LIB_H
#define LIB_H
extern "C" int __declspec(dllexport)add(int x, int y);
#endif

/*
文件名:lib.cpp */

#include "lib.h"
int add(int x, int y)
{
 return x + y;
}


       與第2節對態鏈調用相似,我也建立一個與DLL工程於同一工作區的用工程dllCall,它調DLL中的函數add,其源代如下:

#include <stdio.h>
#include <windows.h>

typedef int(*lpAddFun)(int, int); //
宏定函數指針類
int main(int argc, char *argv[])
{
 HINSTANCE hDll; //DLL句柄
 lpAddFun addFun; //函數指
 hDll = LoadLibrary("..//Debug//dllTest.dll");
 if (hDll != NULL)
 {
  addFun = (lpAddFun)GetProcAddress(hDll, "add");
  if (addFun != NULL)
  {
   int result = addFun(2, 3);
   printf("%d", result);
  }
  FreeLibrary(hDll);
 }
 return 0;
}


  分析上述代dllTest工程中的lib.cpp文件與第2態鏈版本完全相同,不同在於lib.h函數add的聲明前面添加了__declspec(dllexport)句。句的含是聲明函數addDLL出函數。DLL內的函數分

  (1)DLL出函數,可供用程序調用;

  (2) DLL內部函數,只能在DLL程序使用,用程序無法調用它

  而用程序DLL調用和2態鏈調用卻有大差異,下面我來逐一分析。


       首先,typedef int ( * lpAddFun)(int,int)了一個與add函數接受參數型和返回均相同的函數指針類型。隨後,在main函數中定lpAddFunaddFun

  其次,在函數main中定了一個DLL HINSTANCE句柄hDll,通Win32 Api函數LoadLibrary動態DLL並將DLL句柄賦給hDll

  再次,在函數main中通Win32 Api函數GetProcAddress得到了所加DLL中函數add的地址並賦給addFun由函數指addFun行了DLLadd函數的調用;

  最後,用工程使用完DLL後,在函數main中通Win32 Api函數FreeLibrary放了已DLL

  通過這簡單的例子,我們獲DLL調用的一般概念:

  (1)DLL中需以某特定的方式聲明出函數(或量、);

  (2)用工程需以某特定的方式調DLL出函數(或量、)。

  下面我特定的方式述。

4.2 聲明出函數

  DLL出函數的聲明有兩方式:一種爲4.1例子中出的在函數聲明中加上__declspec(dllexport)裏不再明;另外一方式是採用模(.def) 文件聲明.def文件爲鏈接器提供了有接程序的出、屬性及其他方面的信息。

  下面的代演示了怎.def文件將函數add聲明DLL出函數(需在dllTest工程中添加lib.def文件):

 

; lib.def : DLL函數

LIBRARY dllTest

EXPORTS

add @ 1


  .def文件的規則爲

  (1)LIBRARY.def文件相DLL

  (2)EXPORTS句後列出要出函數的名稱。可以在.def文件中的出函數名後加@n,表示要出函數的序號n(在行函數調個序號將發揮其作用);

  (3).def 文件中的注個注的分號 (;) 指定,且注不能與句共享一行。

  由此可以看出,例子中lib.def文件的含義爲生成名“dllTest”動態鏈出其中的add函數,並指定add函數的序號1


  4.3 DLL調用方式

  在4.1的例子中我看到了由LoadLibrary-GetProcAddress-FreeLibraryApi提供的三位一體“DLL-DLL函數地址-DLL方式,這種調用方式稱DLL動態調

  動態調用方式的特點是完全由程者用 API 函數加和卸 DLL,程序可以決定 DLL 文件何或不加接在運行決定加哪個 DLL 文件。

  與動態調用方式相對應的就是靜態調用方式,必有靜來源於物世界的一。與靜,其立與一竟無數次在技術領域裏得到驗證,譬如靜IPDHCP、靜路由與動態路由等。從前文我知道,也分態庫動態庫DLL,而想不到,深入到DLL內部,其調用方式也分動態與靜,無不在。《周易》已認識到有必有靜的靜平衡,《易.繫辭》曰:靜有常,柔斷矣。哲學意味着一普遍的真理,因此,我們經常可以在枯燥的技術領域看到哲學的影子。

  態調用方式的特點是由編譯完成DLL的加用程序 DLL 的卸。當調用某DLL用程序,若系有其它程序使用 DLLWindowsDLL記錄1,直到所有使用DLL的程序都放它。靜態調用方式簡單用,但不如動態調用方式靈活。

  下面我來看看靜態調用的例子,編譯dllTest工程所生成的.lib.dll文件拷入dllCall工程所在的路徑,dllCall行下列代

#pragma comment(lib,"dllTest.lib")

//.lib
文件中僅僅於其對應DLL文件中函數的重定位信息

extern "C" __declspec(dllimport) add(int x,int y);

int main(int argc, char* argv[])
{
 int result = add(2,3);
 printf("%d",result);
 return 0;
}


  由上述代可以看出,靜態調用方式的行需要完成兩個作:

  (1)訴編譯器與DLL對應.lib文件所在的路徑及文件名,#pragma comment(lib,"dllTest.lib")就是起個作用。

  程序在建立一個DLL文件接器會自動爲其生成一個對應.lib文件,文件包含了DLL 函數的符號名及序號(並不含有實際的代)。在用程序裏,.lib文件將作DLL的替代文件參與編譯

  (2)聲明入函數,extern "C" __declspec(dllimport) add(int x,int y)句中的__declspec(dllimport)發揮這個作用。

  靜態調用方式不再需要使用系API來加、卸DLL以及DLL出函數的地址。是因,當程序態鏈接方式編譯生成用程序用程序中調用的與.lib文件中出符號相匹配的函數符號將入到生成的EXE 文件中,.lib文件中所包含的與之對應DLL文件的文件名也被編譯器存 EXE文件內部。當用程序運行程中需要加DLL文件Windows將根據些信息發現並加DLL,然後通符號名實現對DLL 函數的動態鏈接。這樣EXE將能直接通函數名調DLL出函數,就象調用程序內部的其他函數一

4.4 DllMain函數

  Windows在加DLL候,需要一個入口函數,就如同控制檯或DOS程序需要main函數、WIN32程序需要WinMain函數一。在前面的例子中,DLL並沒有提供DllMain函數,用工程也能成功引用DLL是因Windows在找不到DllMain候,系會從其它運行中引入一個不做任何操作的缺省DllMain函數版本,並不意味着DLL可以放棄DllMain函數。

  根據範,Windows須查找並DLL裏的DllMain函數作DLL的依據,它使得DLL得以保留在內存裏。個函數並不屬於出函數,而是DLL的內部函數。意味着不能直接在用工程中引用DllMain函數,DllMain是自調用的。
      

       來看一個DllMain函數的例子

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
 switch (ul_reason_for_call)
 {
  case DLL_PROCESS_ATTACH:
   printf("/nprocess attach of dll");
   break;
  case DLL_THREAD_ATTACH:
   printf("/nthread attach of dll");
   break;
  case DLL_THREAD_DETACH:
   printf("/nthread detach of dll");
   break;
  case DLL_PROCESS_DETACH:
   printf("/nprocess detach of dll");
   break;
 }
 return TRUE;
}


  DllMain函數在DLL被加和卸載時調用,在程啓DLLMain函數也被調用,ul_reason_for_call指明瞭被調用的原因。原因共有4,即PROCESS_ATTACHPROCESS_DETACHTHREAD_ATTACHTHREAD_DETACH,以switch句列出。

  來仔一下DllMain的函數BOOL APIENTRY DllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )

  APIENTRY被定義爲__stdcall,它意味着函數以Pascal的方式調用,也就是WINAPI方式;

  程中的DLL被全局唯一的32HINSTANCE句柄標識,只有在特定的程內部有效,句柄代表了DLL程虛中的起始地址。在Win32中,HINSTANCEHMODULE是相同的,種類型可以替使用,就是函數參數hModule的來

  行下列代

hDll = LoadLibrary("..//Debug//dllTest.dll");
if (hDll != NULL)
{
 addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));
 //MAKEINTRESOURCE直接使用出文件中的序號
 if (addFun != NULL)
 {
  int result = addFun(2, 3);
  printf("/ncall add in dll:%d", result);
 }
 FreeLibrary(hDll);
}


  我看到

process attach of dll
call add in dll:5
process detach of dll


  驗證DllMain調用的機。

  代中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 ) )得留意,它直接通.def文件中add函數指定的序號訪問add函數,具體體MAKEINTRESOURCE ( 1 )MAKEINTRESOURCE是一個通序號取函數名的宏,定義爲節選winuser.h):

#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
#define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
#ifdef UNICODE
#define MAKEINTRESOURCE MAKEINTRESOURCEW
#else
#define MAKEINTRESOURCE MAKEINTRESOURCEA


  4.5 __stdcall

  如果通VC++寫的DLL欲被其他寫的程序調用,將函數的調用方式聲明__stdcall方式,WINAPI都採用這種方式,而C/C++缺省的調用方式卻__cdecl__stdcall方式與__cdecl函數名最生成符號的方式不同。若採用C編譯方式(C++中需將函數聲明extern "C")__stdcall調定在出函數名前面加下劃,後面加“@”符號和參數的字數,形如_functionname@number;而__cdecl調出函數名前面加下劃,形如_functionname

  Windows程中常的幾函數型聲明宏都是與__stdcall__cdecl的(節選windef.h):

#define CALLBACK __stdcall           //就是傳說中的回調函數
#define WINAPI __stdcall                  //
就是傳說中的WINAPI
#define WINAPIV __cdecl
#define APIENTRY WINAPI               //DllMain
的入口就在
#define APIPRIVATE __stdcall
#define PASCAL __stdcall


  在lib.h中,應這樣聲明add函數:

int __stdcall add(int x, int y);


  在用工程中函數指針類義爲

typedef int(__stdcall *lpAddFun)(int, int);


  若在lib.h中將函數聲明__stdcall調用,而用工程中仍使用typedef int (* lpAddFun)(int,int),運行錯誤(因爲類型不匹配,在用工程中仍然是缺省的__cdecl調用),出如7所示的對話框。


7 調定不匹配的運行錯誤




  8中的那段話實際上已經給出了錯誤的原因,即“This is usually a result of …”


   4.6 DLL

  DLL的全局量可以被調訪問DLL也可以訪問調程的全局數據,我來看看在用工程中引用DLL量的例子

/* 文件名:lib.h */

#ifndef LIB_H
#define LIB_H
extern int dllGlobalVar;
#endif

/*
文件名:lib.cpp */

#include "lib.h"
#include <windows.h>

int dllGlobalVar;

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
 switch (ul_reason_for_call)
 {
  case DLL_PROCESS_ATTACH:
   dllGlobalVar = 100; //dll被加載時全局100
   break;
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
   break;
 }
 return TRUE;
}


  ;文件名:lib.def

  ;DLL

LIBRARY "dllTest"

EXPORTS

dllGlobalVar CONSTANT

;
dllGlobalVar DATA

GetGlobalVar


  從lib.hlib.cpp中可以看出,全局量在DLL中的定和使用方法與一般的程序設計是一的。若要出某全局量,我需要在.def文件的EXPORTS後添加

  量名 CONSTANT   //過時的方法

  或

  量名 DATA     //VC++提示的新方法

  在主函數中引用DLL中定的全局量:

#include <stdio.h>
#pragma comment(lib,"dllTest.lib")

extern int dllGlobalVar;

int main(int argc, char *argv[])
{
 printf("%d ", *(int*)dllGlobalVar);
 *(int*)dllGlobalVar = 1;
 printf("%d ", *(int*)dllGlobalVar);
 return 0;
}


  特要注意的是extern int dllGlobalVar聲明所入的並不是DLL中全局量本身,而是其地址用程序必過強制指針轉換來使用DLL中的全局量。一點,從*(int*)dllGlobalVar可以看出。因此在採用這種方式引用DLL全局,千萬不要這樣賦值操作:

dllGlobalVar = 1;


  其果是dllGlobalVar的內容化,程序中以後再也引用不到DLL中的全局量了。

  用工程中引用DLL中全局量的一個更好方法是

#include <stdio.h>
#pragma comment(lib,"dllTest.lib")

extern int _declspec(dllimport) dllGlobalVar; //
_declspec(dllimport)
int main(int argc, char *argv[])
{
 printf("%d ", dllGlobalVar);
 dllGlobalVar = 1; //裏就可以直接使用, 須進制指針轉換
 printf("%d ", dllGlobalVar);
 return 0;
}


  通_declspec(dllimport)方式入的就是DLL中全局量本身而不再是其地址了,筆者建在一切可能的情況下都使用這種方式。

  4.7 DLL

  DLL中定可以在用工程中使用。

  下面的例子裏,我DLL中定pointcircle兩個,並在用工程中引用了它

//文件名:point.hpoint的聲明

#ifndef POINT_H
#define POINT_H
#ifdef DLL_FILE
 class _declspec(dllexport) point //point
#else
 class _declspec(dllimport) point //point
#endif
{
 public:
  float y;
  float x;
  point();
  point(float x_coordinate, float y_coordinate);
};

#endif

//
文件名:point.cpppoint實現

#ifndef DLL_FILE
 #define DLL_FILE
#endif

#include "point.h"

//
point的缺省構造函數

point::point()
{
 x = 0.0;
 y = 0.0;
}

//
point的構造函數

point::point(float x_coordinate, float y_coordinate)
{
 x = x_coordinate;
 y = y_coordinate;
}

//
文件名:circle.hcircle的聲明

#ifndef CIRCLE_H
#define CIRCLE_H
#include "point.h"
#ifdef DLL_FILE
class _declspec(dllexport)circle //
circle
#else
class _declspec(dllimport)circle //
circle
#endif
{
 public:
  void SetCentre(const point &centrePoint);
  void SetRadius(float r);
  float GetGirth();
  float GetArea();
  circle();
 private:
  float radius;
  point centre;
};

#endif

//
文件名:circle.cppcircle實現

#ifndef DLL_FILE
#define DLL_FILE
#endif
#include "circle.h"
#define PI 3.1415926

//circle
的構造函數

circle::circle()
{
 centre = point(0, 0);
 radius = 0;
}

//
得到的面

float circle::GetArea()
{
 return PI *radius * radius;
}

//
得到的周

float circle::GetGirth()
{
 return 2 *PI * radius;
}

//
心坐

void circle::SetCentre(const point &centrePoint)
{
 centre = centrePoint;
}

//
的半徑

void circle::SetRadius(float r)
{
 radius = r;
}


  的引用:

#include "../circle.h"  //包含聲明文件

#pragma comment(lib,"dllTest.lib");

int main(int argc, char *argv[])
{
 circle c;
 point p(2.0, 2.0);
 c.SetCentre(p);
 c.SetRadius(1.0);
 printf("area:%f girth:%f", c.GetArea(), c.GetGirth());
 return 0;
}


  從上述源代可以看出,由於在DLL類實現中定了宏DLL_FILE故在DLL實現所包含的實際

class _declspec(dllexport) point //point
{
 
}


  和

class _declspec(dllexport) circle //circle
{
 
}


  而在用工程中沒有定DLL_FILE,故其包含point.hcircle.h後引入的聲明

class _declspec(dllimport) point //point
{
 
}


  和

class _declspec(dllimport) circle //circle
{
 
}


  不,正是通DLL中的

class _declspec(dllexport) class_name //circle 
{
 
}


  用程序中的

class _declspec(dllimport) class_name //
{
 
}


  匹來完成出和入的!

  我往往通的聲明文件中用一個宏來決定使其編譯爲class _declspec(dllexport) class_nameclass _declspec(dllimport) class_name版本,這樣就不再需要兩個文件。本程序中使用的是:

#ifdef DLL_FILE
 class _declspec(dllexport) class_name //
#else
 class _declspec(dllimport) class_name //
#endif


  實際上,在MFC DLL解中,您將看到比便的方法,而此處僅僅_declspec(dllexport)_declspec(dllimport)問題

  由此可用工程中幾乎可以看到DLL中的一切,包括函數、量以及就是DLL所要提供的大能力。只要DLL些接口,用程序使用它就將如同使用本工程中的程序一

  本章VC++平臺解非MFC DLL,但是些普遍的概念在其它言及開發環境中也是相同的,其思方式可以直接渡。 接下來,我將要研究MFC規則DLL

 

 

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