库调试

   

    库是实现模块化和重用的重要手段,也是共享的重要方式。

1.运行库概述

运行库是程序在运行时所需要的库文件,通常运行库足以libDLL形式提供的。运行库中一般包括编程时常用的函数,如字符串操作、文件操作、界面、逻辑模块等内容。使用运行库,可以方便的进行模块重用,大大缩小编译后的程序大小,减少程序中的重复代码。

1.1  静态链接库

静态链接库(static library)本质上是一个代码集,通常以lib的形式提供,它把一些函数做成一个函数集合放在一起,这些函数没有经过编译器链接。

如果要调用静态链接库中的函数,这些函数在工程编译时将参加编译。在使用静态链接库文件中的函数时,必须包含该函数对应的头文件,还必须引用进该lib文件,以允许编译器去查找已经编译好的二进制代码。如果可执行程序调用了静态链接库,则编译后静态链接库中的代码就要链接到可执行程序中去,成为执行程序的一部分。所以调用了静态链接的可执行文件的体积一般比较大一些。   

函数和数据被编译进一个二进制文件,编译器在链接过程中将从静态库中恢复这些函数和数据,并把这些数据和应用程序中的其他模块组合在一起生成可执行文件,这个过程称为“静态链接”,此时因为应用程序所需的全部内容都是从库中复制出来,所以静态库本身并不需要与可执行文件一起发行。

静态链接库的依赖项包括静态链接库头文件(*.h)、库文件(*.lib)。在提供静态链接库时,需要同时提供这两项才能供其他开发者使用。

1.2动态链接库

动态链接库(dynamic link library)是作为共享函数库的可执行文件,它是包含可由多个程序同时使用的代码和数据的库。

动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个DLL中,该DLL包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。

使用动态链接库有助于共享数据和资源,多个应用程序可同时访问内存中单个DLL副本的内容,如在Windows操作系统中,Comdlg32 dll执行与对话框有关的常见函数,因此每个程序都可以使用该DLL中包含的功能来实现“打开”对话框的功能,显然这种方式促进了代码重用和内存的有效使用。

使用DLL,可以将程序模块化,程序由相对独立的组件组成。动态链接库可以使程序按模块来销售,在运行时将各个模块加载到主程序中。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载,提高了效率。

使用动态链接可以更容易地将更新应用于各个模块,而不会影响该程序的其他部分。动态链接库的修改是被隔离的,不会影响到其他模块,而且DLL修改后,无需重新生成或安装整个程序,只需要更新相应的DLL就可以应用更新。如果程序中使用了动态链接库,应用程序就不是独立的,程序的运行将会依赖对应的DLL,在程序发行时,DLL需要同时发行。

动态链接库的依赖项包括头文件(*.h)、引入库文件(*.1ib)、库文件(*.dll)。在提供静态链接库时,需要同时提供这3项才能供其他开发者使用。

引入库文件包含被DLL导出的函数名称和位置、动态链接库中包含实际的函数和数据,应用程序使用引入库文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行时弄把程序代码和被调用的函数代码链接起来,从而节省了内存资源。

动态链接库和静态链接库是有区别的。静态库就是将需要的代码直接链接进可执行程序;动态库就是在需要调用其中的函数时,根据函数映射表找到该函数然后调入堆栈执行。

如果在当前工程中有多处对DLL文件中同一个函数的调用,则执行时这个函数只会留下一份副本。如果有多处对lib文件中同一个函数的调用,则执行时该函数将在当前程序的执行空间里留下多份副本,而且是一处调用就产生一份副本。

2.2  创建链接库

常用的库有静态链接库和动态链接库,本节分别介绍创建这两类库的方法。

2.2.1 创建静态链接库

Visual C++开发环境下可以方便地生成静态链接库文件。打开Visual C++,在菜单栏中选择File-New命令,弹出“New(新建)”对话框,切换到Project选项卡,在列表中选择Win32 Static Library选项,表示要新建一个静态链接库工程,然后在对话框右侧的Project name编辑框中输入工程的名称LibExample,在Location编辑框中输入工程的目录。单击OK按钮,弹出静态链接库设置对话框。Pre-Compiled header选项表示新建的工程将会包含预编译头文件。选择该选项后生成的工程的编译选项中将有预编译头文件的参数,而且生成的工程文件中将会看到“# include ”stdafx.h””。MFC support表示新建的工程将支持MFC,选择该选项后,可以在编写库文件时使用MFC提供的功能。这里主要是介绍如何生成静态链接库,为了简单起见,两个选项都不选择,这样将生成一个很简荜的静态链接库工程。单击Finish按钮后,提示将生成一个不包含库源文件的工程,单击OK按钮,一个空静态链接库创建完毕。打开工程文件夹,该文件夹下面只有3个文件:LibExample.dspLibExample.dswLibExample.ncb

静态链接库文件需要在上述的空工程中新建。新建LibExample.hLibExample.cpp两个文件。编辑*.h的源代码如下:

#ifndefLIBEXAMPLE_H

#define LIBEXAMPLE_H

**两个整数求和

@param  x 加数1

@param  y 加数2

@retval   两数之和

*

#endif   //#ifndef  LIBEXAMPLE_H

编辑*.cpp的源代码如下:

#include  ”lib.h”

int  Add(int x, int y)

{

    return (x+y);

}

编译该工程就得到了一个LibExample.lib文件,LibExample.lib就是一个函数库。在发布时,将头文件和.lib文件一起提交给用户,用户就可以直接使用其中的函数了。

编译静态链接库也有调试版本和发布版本两种,与编译普通应用程序一样,选择不同的编译选项生成不同的编译版本。建议发布时提供两种版本,且在调试版本的文件名后增加字母“D”。不用每次在编译完成后都去修改调试版本的文件名,在工程选项中,可以将调试版本输出文件的名字改为LibExample D.lib

在编码完成后,在菜单栏选择Build-Batch Build命令,在弹出的Batch Build对话框中,同时勾选调试版本和发布版本,单击Rebuild All按钮,可以将调试版本和发布版本一起全部重建。

生成包含预编译头文件和支持MFC静态库的过程是一样的,不同的是这两种选项的代码稍微复杂一点。

2.2.2 创建动态链接库

在创建和编写动态链接库之前,需要在开发层面上对动态链接库有所了解,下面是关于动态链接库的一些基本概念。

(l)动态链接库的编制与具体的编程语言及编译器无关

只要遵循约定的DLL接口规范和调用方式,用各种语言编写的动态链接库都可以相互调用。例如,Windows提供的系统DLL(其中包括了WindowsAPI),在任何开发环境中都能被调用,不论是在Visual BasicVisual C++,还是Delphi

 (2)  Visual C++动态链接库的分类

Visual C++支持3DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLLMFC规则DLL)和MFC Extension DLLMFC扩展DLL)。

MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFCMFC编写的应用程序所调用;MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

(3) DLL导出函数和DLL内部函数

    DLL导出函数可供应用程序调用,DLL内部函数而只能在DLL程序使用,应

用程序无法调用它们。

1.创建一个简单的动态库

打开Visual C++,在菜单栏中选择File-New命令,弹出New对话框,切换到Project选项卡,在列表中选择Win32 Dynamic-Link Library选项,表示要新建一个动态链接库工程,然后在对话框右侧的Project name编辑框中输入工程的名称LibExample,在Location编辑框中输入工程的目录,单击OK按钮,弹出类型选择对话框,包括空dll工程、简单dll工程以及包含一些导出符号的dll工程。为了介绍方便,此处选择空dll工程。建立完毕后,打开工程文件夹,该文件夹下面只有3个文件:LibExample.dsp, LibExample.dsw,LibExample.ncb

链接库文件需要在上述的空工程中新建。新建NonMfcDll.hNonMfcDll.cpp两个文件。编辑NonMfcDll.h的源代码如下:

#ifndef NONMFCDLL_H

#defineNONMFCOLL_H

**两个整数求和

@param  x 加数1

@param  y 加数2

@retval   两数之和

*

extern   “C”  int _declspecl(dllexport) Add(int x, int y)

#endif   //#ifndef NONMFCOLL_H

编辑NonMfcDll.cpp的源代码如下:

#include  ”*.h”

int  Add(int x, int y)

{

    return (x+y);

}

NonMfcDll.h文件代码中的dllexport声明函数Adddll导出函数,是动态链接库提供给用户的接口。

编译该工程就得到了一个NonMfcDll.libNonMfcDll.dll文件,NonMfcDll.dll就是需要的动态链接库,NonMfcDll.libNonMfcDll.dll的引入库文件,它提供了两数求和的功能,即Add函数所提供的功能。在发布时,将头文件和.lib.dll文件一起提交给用户,用户就可以直接使用其中的Add函数了。

2DLL导出函数

DLL中导出函数的声明有两种方式:一种是函数声明中加上_declspecB(dllexport);另一种是采用模块定义(.def)文件声明,.def文件为有关被链接程序的导出、属性及其他方面的信息。

使用_declspec(dllexport)声明函数导出已经给出,这里介绍使用.def文件声明函数导出。在工程中添加.def文件,按照上面的例子,应该在工程中添加*.def文件。.def文件的主要编写规则如:

LIBRARY语句说明.def文件相应的DLL

EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用)。

.def文件中的注释由每个注释行开始处的分号“:”指定,且注释不能与语句共享一行。

要将示例工程中的Add函数声明为导出函数,NonMfcDll.def文件的源码如下:

;lib.def  :导出 DLL函数

LIBRARY  NonMfcDll

EXPORTS

Add @ 1

根据.def文件规则,上面代码的含义为生成名为NonMfcDll的动态链接库,导出其中的Add函数,并指定Add函数的序号为1

3DLL导出变量

DLL定义的全局变量可以被调用进程访问,同时DLL也可以访问调用进程的全局数据。下面描述在应用工程中如何引用DLL中变量的例子。

在文件NonMfcDll.h中,编写以下代码:

       extern int  g_DllExternVar;

 g_DIIExternVar是应用程序中的全局变量,在DLL工程的头文件中声明外部变量后,就可以在.c.cpp文件中使用了。在文件NonMfcDll.c中,编写以下代码:

  int AddGlobalVal(int  x)

  {

    return (x + g_DIIExtemVar);

}

   def文件中将g_DIIExtemVar声明为导出变量,在文件NonMfcDll.def中,编写代码如下:

;DLL中导出变量

LIBRARY  “NonMfcDll”

  EXPORTS

  g_DIIExtemVar DATA;

GetGlobalVar

NonMfcDll.hNonMfcDll.cpp中可以看出,全局变量在DLL中的定义和使用方法与一般的程序设计是一样的。若要导出某全局变量,需要在.def文件的EXPORTS后添加:

变量名 DATA

在应用工程中引用DLL中全局变量方法如下:

 #pragma comment(lib,” NonMfcDll”)

//_declspecl(dllexport)导入变量

extern int _declspecl(dllexport)  g_DllExternVar;

int main(intargc,char *argv[])

{

         g_DllExternVar=100;

int myVal= g_DllExternVar+200;

return 0;

}

建议读者通过declspec(dllimport)方式导入DLL中的全局变量,不要图省事而使用extern int g_DIIExtemVar。用extern int g_DIIExtemVar声明所导入的并不是DLL中的全局变量本身,而是其地址,应用程序必须通过强制指针转换来使用DLL中的全局变量。

使用declspec(dllimport)方式导入的是全局变量本身而不是其地址,建议在一切可能的情况下都使用这种方式。

4DLL导出类

  通过DLL导出类,DLL中定义的类可以在应用程序的工程中使用。

  下面以学生类(CStudent)为例,介绍在DLL中导出类的方法。DLL工程依然使用NonMfcDII工程,在添加CStudent类的声明文件(student.h)和实现文件( student.c)student.h文件如下所示:

#ifndef STUDENT_H

#define STUDENT _H

#ifdef DLLSTUDENT

class   _declspec (dllexport)  CStudent   //导出类CStudent

#else

class   _declspec (dllimport)  CStudent   //导入类CStudent

#endif

{

public :

     CStudent (const char* pName) ;

     virtual   ~CStudent ( ) ;

public :

void SetID(unsigned int newlD)//修改学生的ID

void SetScore (unsigned  int  newScore);//修改分数

private :

       charm_Name[20];//姓名

unsigned int m_ID;//学生ID

unsigned intm_Score;//分数

};

#endif

 

student.c文件如下所示:

#ifndef  DLL_STUDENT

#define  DLL_STUDENT

#endif

#include “student.h”

CStudent : : CStudent (const char* pName)

{

    memcpy( m_Name, pName,sizeof(pName);

     m ID = 11223344;

     m Score= 100;

}

CStudent : : ~CStudent ()

{

}

void CStudent : : SetID(unsigned int newlD)

{

         m_ID=newID;

}

void CStudent : : Set Score (unsigned int newScore)

{

         m_Score=newScore;

}

在应用程序中使用类,代码如下:

#include “student.h”

#pragma comment(lib,”NonMfcDll.lib”);

int main(int argc,char * argv[])

{

         CStudent  aStudent(”sly”);

   aStudent.SetID(1234567);

   aStudent.SetScore(100);

return 0;

}

在实现CStudent类时,使用了宏DLL STUDENT,这个宏很巧妙。在NonMfcDII工程中,在student.c中定义了宏DLL STUDENT,所以在DLL工程进行编译时,会启用导出类的那段代码,即:

class   _declspec (dllexport)  CStudent    //导出类CStudent

{

         //…

};

在应用程序的工程中,由于没有定义宏DLL STUDENT,所以在编译时会启

用导入类的那段代码,即:

class   _declspec(dllimport)  CStudent                  //导入类 CStudent

{

         //…

};

 

这样,在编写DLL工程的CStudent类以及在应用程序工程中使用CStudent类时,编译器会有不同的表现,同时省去了在应用程序中添加导入CStudent类的代码。在student.h文件中,_ declspec(dllexport)_declspec(dllimport)是匹对的。

通过导出动态链接库中的函数、变量以及类中在应用工程中几乎可以“看到”DLL中的一切,只要DLL释放这些接口,应用程序使用它就将如同使用本工程中的程序一样方便。

5_stdcall约定

动态链接库的编制与具体的编程语言及编译器无关。如果通过Visual C++编写的DLL要被其他语言编写的程序调用,应该将函数的调用方式声明为_stdcall方式。

Windows操作系统中的WINAPI都采用这种方式,而C/C++语言默认的调用方式却为  _cdecl_stdcall方式与_cdecl对函数名最终生成符号的方式不同。

若来用C编译方式(在C++中需将函数声明为extemC”),_stdcall调用约定在输出函数名前面加下画线,后面加“@”符号和参数的字节数,如_functionname@number;而_cdecl调用约定仅在输出函数名前面加下画线,如_functionname

Windows编程中常见的几种函数类型声明宏都与_stdcall_cdecl有关,具体如下:

#define CALLBACK  _stdcall   //回调函数

#define WINAPI   _stdcall    // WINAPI

#define WINAPIV   _cdecl

 

#define APIENTRY  WINAPI       //DlIMain的入口

#define APIPRIVATE   _stdcall

#define  PASCAL    _stdcall

NonMfcDll.h中,声明Add函数为:

    int_stdcall  add (int  x, int  y);

    在应用工程中函数指针类型应定义为:

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

若在NonMfcDll.h中将函数声明为_stdcall调用,而应用工程中仍使用typedefint(* lpAddFun)(intint),运行时将发生错误,因为这两种编译方式不匹配,在应用工程中仍然是默认的_cdecl调用。

调试静态链接库

本节介绍如何使用静态链接库以及静态链接库的调试方法,并对与静态链接库相关的常见问题做了总结。

3.1  静态连接库的使用

静态库包括.lib.h文件,要在应用程序工程中使用静态库,首先要把静态库对应的.lib.h文件复制到工程目录中,在程序工程目录中添加静态链接库的头文件,然后在程序工程中载入静态库。

要在自己的应用程序工程中载入静态库,可以通过在项目设置中引用和在应用程序工程代码中显示加载两种方法。下面以静态库LibExample的加载为例来说明两种方法的具体操作。

(l)在项目设置中引用

选择菜单栏中的Project-Settings命令,弹出ProjectSettings对话框,切换到Link选项卡,在object/library modules编辑框中添加.lib,此例中应该添加LibExample.lib。如果要添加多个静态库,可以将库文件名都添加进去,文件名之间用空格隔开。

(2)显示加载

在应用程序代码中编写以下代码进行加载。

#pragma comment (lib,” LibExample.lib”);

#pragma comment (lib,其他静态链接库文件名);

完成上面的操作后,就可以像使用本工程的函数一样使用静态库提供的功能了。

3.2  静态链接库的调试

静态链接库的调试有两种方法:直接法和间接法。直接法是在静工程中直接进行程序调试,调试的方法写调试可执行差不多;间接法库的应用程序工程去调试静态链接库。

1.直接法

打开静态链接库工程,按【F5】键或选择菜单栏中的Build-StartDebug-Go命令,开始执行Debug模式。由于库文件不能单独执行,在执行调试时,会弹出静态链接库调试对话框。

    这个对话框要求用户输入可执行文件的路径来启动库函数的执行。在Executable file name编辑框中输入调用该静态库的可执行文件目录和文件名,或通过Executable file name编辑框右边的箭头选择可执行文件,然后单击OK按钮,就可以对库进行调试了,其调试技巧与一般应用工程的调试一样。

2.间接法

    程序员可以将库工程和调用库的应用程序工程放置在同一Visual C++工作区,只对应用工程进行调试,在应用工程调用库中函数的语句处设置断点,调试过程中如果程序停在断点处,可以使用Step Into(按【FII]键)进入库函数进行调试,这样就可以单步调试库中的函数。

在应用程序工程中的工作区右击,在弹出的菜单中选择Add New Project to Workspace命令。在弹出的工程文件选择对话框中选择库文件的工程文件。

操作完成后,在原来的工作区中可以看到添加的静态库工程。在应用程序工程中调用静态库中的函数,并在函数调用的代码行添加断点,代码如下:

#include  “LibExample.h”

void  CBookCodeDlg ::ShowBreakPoint()

{

  int a =Add(3,5)  //Add是库函数,在该行设置断点后,可进入函数进行单步调试

}

执行调试运行,当程序在断点处停下时,可进入库函数对其进行单步调试。

上述调试方法对静态链接库和动态链接库而言是一致的。

3.设置工程依赖

    将库工程和应用程序放在同一个工程下方便对库函数的调试。如果在调试过程中,修改了库函数的实现,则需要对库进行重新编译生成新的lib;如果没有修改完库函数的代码后都编译一遍库工程,然后再进行应用程序工程编译,是很麻烦的。

    设置工程的依赖之后,可以在编译工程之前,编译该工程所依赖的工程后再编译本工程。

要设置工程依赖,首先将依赖的工程添加到本工程中,添加完成后,选择菜单栏中的Project-Dependencies命令,在弹出的ProjectDependencies对话框中的列表框中选择依赖的工程即可。

3.3  常见问题及处理方法

    初学者在使用静态链接库时,经常容易忘记加载.lib,在编译时Visual C++会提示LNK2001错误。如果既没有在编译选项中设置,也没有显示加载LibExample.libVisual C++会警告,提示找不到函数Add。例如:

BookCodeDlg.obj : error LNK2001: unresolvedexternal symbol  Add Debug/BookCode.exe :fatal error LNK1120: I unresol\red externals

这时需要加载静态链接库。

4调试动态链接库

    本节介绍如何使用动态链接库以及与动态链接库的调试相关的问题。

4.1  动态链接库的使用

    静态库包括.lib.dll.h文件,要在应用程序工程中使用动态链接库,首先要把动态链接库对应的.lib.dll,.h文件复制到工程目录中,在程序工程目录中添加静态链接库的头文件,然后在程序工程中载入动态链接库。

    这里要说明的是,在编码中加载动态链接库只需要将动态链接库的引入库文件加人到工程中就可以了。引入库的加载方式与静态链接库的加载方式相同。

在实际开发中,可能需要知道某个动态链接库导出的函数有哪些,动态链接库中的导出接口可以使用Visual C++Depends工具进行查看。

打开Depends工具,工具启动后,选择菜单栏中的File-Open命令,选择上面例子中编写的NonMFC.dll,从Depends工具中的列表可以看到NonMFC.dll导出函数有AddProductMinus

4.2 DLL冲突

    当程序使用DLL时,一个称为依赖性的问题可能导致该程序无法运行。当程序使用DLL时,就会创建一个依赖项。如果其他程序改写和损坏了该依赖项,原来的程序就可能无法成功运行。如果发生下列操作之一,则该程序可能无法运行:

    ·依赖DLL升级到新版本;

    · 修复了依赖DLL;

    · 依赖DLL被其早期版本覆盖:

    · 从计算机中删除了依赖DLL

    这些操作通常称为DLL冲突。如果没有强制实现向后兼容性,则该程序可能无法成功运行。为了最大限度地减少依赖性问题,在Windows 2000和校高版本的Windows操作系统中引入了文件保护和专用DLL

(l) Windows文件保护

    Windows文件保护中,操作系统禁止未经授权的代理更新或删除系统DLL。因此,当程序安装操作尝试删除或更新被定义为系统DLLDLL时,Windows文件保护将寻找有效的数字签名。

(2)专用DLL

    通过专用DLL可以使程序避免遭受对共享DLL进行的更改。专用DLL使用版本特定信息或空local文件来强制要求程序所使用的DLL的版本。如果要使用专用DLL,可以在程序根文件夹中查找DLL。对于新程序,要向该DLL中添加版本特定信息。对于旧程序,要使用空Iocal文件。每个方法都告诉操作系统使用位于程序根文件夹中的专用DLL

    在编码开发的层面上讲,导致DLL冲突的主要原因是libdll版本不符。

    发布的动态链接库包括头文件(*.h)、引入库文件(*.1ib)、库文件(*.dll)。而引入库文件包含被DLL导出的函数的名称和位置、动态链接库中包含实际的函数和数据,在发布时引入库文件和库文件是一一对应的。如果在移动动态链接库文件时,将版本搞错,则会出现DLL冲突的问题。

    在进行程序开发时,如果使用了多个动态链接库,建议将动态链接库工程的配置进行修改,将工程输出目录设置为应用程序的工作目录,即将编译后最新的libdll同时输出到应用程序工程目录下,这样可以保证libdll的版本一致性。

4.3  获取DLL的相关信息

    获取指定DLL的相关信息是很重要的,如DLL的舨本信息。动态链接库为模块重用提供了方便,但是要使应用程序顺利的与DLL对接是一件很不容易的事情。这时,DLL的版本信息可以为程序开发和调试提供很多有价值的信息,而且很多时候需要程序中判断DLL的版本来决定下一步的代码执行方案。

   DLL相关的信息包括产品版本信息、产品名称、文件描述、文件版本信息内部名称等。

1VerQueryValua函数

   VerQueONalue函数用于从版本资源中获取信息。调用这个函数前,必须GetFileVersionlnfo函数获取版本资源信息。这个函数会检查资源信息,并将需的数据复制到一个缓冲区里。函数VerQueryValue的函数原型为:

BOOL VerQueryValue (

  const LPVOID pBlock,  // addressof buffer for version resource

  LPTSTR lpSubBlock,    // address of value to retrieve

  LPVOID *lplpBuffer,    // addressof buffer for version value pointer

  PUINT puLen         // address oflength buffer

);

参数说明:

·pBlock:指定一个内存块第一个字节的地址。这个内存块包含了由GetFileVersionlnfo函数取回的版本数据信息。

· lpSubBlock String:可取下面的参数为”\”获取文件的VS_FIXEDFILE­_INFO结构:”WarFilelnfo\Translation”获取文件的翻译表;”\StringFilelnfo\...”获取文件的字串信息。

· lplpBuffer Long:指定一个Long变量的地址,该变量用于装载一个缓冲区的地址。请求的版本信息最终会装载到那个缓冲区里。

·  puLen Long:指定由lplpBuffer参数引用的数据值的长度,以字节为单位。

    函数返回TRUE(非0)表示执行成功。例如,请求的信息不存在,或pBlock不属于有效版本信息,则返回FALSE

    在调用函数VerQueryValue时,如lplpBuffer参数为”\StringFilelnfo\...”,缓冲区里就会载入一个整数数组,每一对整数都代表一种语言和代码页,它们描绘了可用的字串信息。

    通过用下面这3部分指定一个字串,可以获得StringFilelnfo字串数据:”\StringFilelnfo\langua8ecodepage\stringname”,其中languagecodepage(语言代码页)是采用字串形式的一个8字符十六进制数字,如翻译表中的语言代码页条目是&H04090000,这个字串就应该是04090000stringname(字串名)指定的是一个字串名。

2.获取信息

定义:

LPVOID m_lpData;

UINT  m_uiDataSize;

用于返回欲获取的具体信息内容,在适当的位置将这两个参数初始化为:

m_uiDataSize=80;

m_lpBuffer=malloc(1024);

下面是获取各种信息的具体办法:

//获取产品版本

::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\ProductVersion”),

&m_lpData,&m_uiDataSize);

//获取产品名称

::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\ProductName”),

&m_lpData,&m_uiDataSize);

 

//获取产品描述

::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\FileDescription”),

&m_lpData,&m_uiDataSize);

//获取文件版本

::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\FileVersion”),

&m_lpData,&m_uiDataSize);

//获取内部名称

::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\InternalName”),

&m_lpData,&m_uiDataSize);

4.4  列举程序加载的模块

    在开发过程中,有时候需要获取某个程序加载的所有模块,这些模块包括静态链接库和动态链接库。这里介绍枚举进程加载的所有模块的方法。操作步骤为:

提升当前进程的权限、获取目标进程的进程ID (PID)、枚举目标进程中的所有模块。

提升当前进程的权限使其有权限对其他进程进行操作,操作方法如下:

    int ModifyPrivilege()

    {

       HANDLE  hToken;

       LUID  sedebugnameValue;

                   TOKEN_PRIVILEGES  tkp;

                   //获取当前进程的令牌句柄

                   If(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,

&hToken)){

return 1;

}

}

//查询当前进程权限

If(!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&sedebugnameValue)){

CloseHandle(hToken);

return 1;

}

tkp.PrivilegeCount=1;

tkp.Privilege[0].Luid=sedebugnameValue;

tkp.Privilege[0].Attributes=SE_PRIVILEGE_ENABLED;

//修改当前进程权限

If(!AdjustTokenPrivileges(hToken,FALSE,&tkp,sizeof  tkp,NULL,NULL))

{

                   CloseHandle(hToken);

                   return 1;

}

}

 

获取目标进程的进程ID的方法如下:

DWORD  GetCurProcessId(){

    //获取目标进程的PID

DWORD  pid;

HANDLE  snapshot;

snapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);

struct  tagPROCESSENTRY32  processsnap;

processsnap.dwsize=sizeof(tagPROCESSENTRY32);

for(Process32First(snapshot,&processsnap);Process32Next(snapshot,&processsnap);){

if(!stricmp(processsnap.szExeFile,exename))

{

           pid=processsnap.th32ProcessID;

break;

}

}

}

CloseHandle(snapshot);

return 1;

}

枚举目标进程中的所有模块:

int  EnumProcessModule(DWORD pid){

MODULEENTRY32  pe32;

//设置结构的大小

pe32.dwSize=sizeof(pe32);

     //给进程内所有模块拍一个快照

                   HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,pid);

      If(hProcessSnap==INVALID_HANDLE_VALUE){

                            //建立快照失败

                            return-1;

}

//遍历进程快照,轮流显示每个进程的信息

BOOL  bMore=Module32First(hProcessSnap,&pe32);

while(bMore){

printf(“\n[DLL NAME]\t%s\n”,pe32.szModule);

printf(“[DLL PATH]\t%s\n”,pe32.szExePath);

bMore=Module32Next(hProcessSnap,&pe32);

}

//清除snapshot对象

CloseHandle(hProcessSnap);

return 1;

}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值