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

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值