2007年4月27日星期五

如何从DLL文件中导出LIB文件?

    由于我们经常要调用一些第三方厂商或其他编译器编写的动态链接库,但是一般都不提供源文件或.lib文件,而作为VC隐式链接到DLL (implicitly   link   to   the   DLL)调用,这些却是必需的。本文将主要讨论在没有源文件及.lib输入库文件或欲调用Windows未公开函数的情况下重建.Lib文件的方法。在建立之前,我们首先要了解一下DLL输出函数的几种方式。    
  一、从DLL中输出函数的方式(calling   conventions)    
  _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。    
  _stdcall是Pascal程序的缺省调用方式,通常用于Win32   Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。    
  _fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。    
  用VC建立一个空的动态链接库,并加入以下三个文件:    
  //noname.h     动态链接库头文件    
  extern   "C"   void   _stdcall     stdcallproc(void);    
  extern   "C"   void     _cdecl   cdeclproc(void);    
  extern   "C"   void     _fastcall   fastcallproc(void);    
  //noname.cpp       动态链接库实现文件    
  #include   <windows.h>    
  extern   "C"   void   _stdcall     stdcallproc(void)    
  {   MessageBox(0,"stdcall   function","dll   call",0);    
  }    
  extern   "C"   void     _cdecl   cdeclproc(void)    
  {   MessageBox(0,"cdecl   function   ","dll   call",0);    
  }    
  extern   "C"   void     _fastcall   fastcallproc(void)    
  {   MessageBox(0,"fastcall   function   ","dll   call",0);    
  }    
  //noname.def     动态链接库输出函数定义    
  LIBRARY             "noname"    
  EXPORTS    
  stdcallproc   @1   noname    
  cdeclproc     @2    
  fastcallproc   @3    
  编译后生成noname.lib,输出函数_cdeclproc,_stdcallproc@0,@fastcallproc@0;生成的 noname.dll在Exescope等PE格式的工具中只能看到cdeclproc和fastcallproc函数,因为stdcallproc被指定noname属性,没有名字输出,类似于Windows未公开函数。    
  二、可执行程序调用DLL的方式    
  可执行程序可以采用隐式链接(implicit   linking)或显式链接(explicit   linking)两种方式调用一个DLL。    
  使用显式链接时,使用DLL的程序在使用之前必须加载(LoadLibrary)加载DLL从而得到一个DLL模块的句柄,然后调用 GetProcAddress函数得到输出函数的指针,在退出之前必须卸载DLL(FreeLibrary),因为不是本文重点,具体例程请参考有关文档。显然,在调用大量的函数时这种方法会很不方便。    
  使用隐式链接时,可执行程序链接到一个包含DLL输出函数信息的输入库文件(.LIB文件)。操作系统在加载使用可执行程序时加载DLL。可执行程序直接通过函数名调用DLL的输出函数,调用方法和程序内部其他的函数是一样的。    
  三、重建.Lib输入库文件    
  根据微软的建议,要想隐式地链接到一个DLL,可执行程序必须从DLL的提供者那儿得到一个包含输出函数的头文件(.h文件)、一个用于链接的输入库(.lib文件)。愿望是很好的,但是一般情况下,我们都无法得到第三方动态链接库的输入库文件,或者我们需要调用Windows未公开函数。如果你是使用Delphi或Visual   Basic开发程序,那么,你只要简单的申明一下函数和输出库就可以了。但是,使用VC的朋友们只好重建.Lib文件了。    
  1.删掉第一步中生成的noname.lib(假设我们没有这个文件)。    
  2.用微软的DumpBin.exe:dumpbin   /exports   noname.dll>noname.def,留下noname.def文件的输出段:    
          ordinal   hint   RVA             name    
                      2         0   00001005   cdeclproc    
                      3         1   0000100F   fastcallproc    
                      1             0000100A   [NONAME]    
  修改为:    
  LIBRARY             "noname"    
  EXPORTS    
  cdeclproc         @2    
  fastcallproc   @3    
  nonameproc       @1   //请注意与第一步中noname.def的区别:nonameproc可以自己指定为任何名字    
  再执行lib.exe   /def:noname.def即可生成noname.lib文件(但如果这个动态链接库不仅仅包含_cdecl类型函数,那么这个noname.lib还不是最终可用的.lib文件,具体请看下文)。    
  3.建立一个名为DllCaller的Win32控制台程序,将刚才生成的noname.dll和noname.lib拷入DllCallerdebug目录。    
  //DllCaller.cpp    
  //声明函数原型    
  extern   "C"   void   _stdcall     nonameproc(void);    
  extern   "C"   void     _cdecl   cdeclproc(void);    
  extern   "C"   void     _fastcall   fastcallproc(void);    
  //链接输入库文件    
  #pragma   comment(lib,"debug\noname.lib")    
  int   main(int   argc,   char*   argv[])    
  {    
          nonameproc();    
          cdeclproc();    
          fastcallproc();    
          return   0;    
  }    
  编译器产生如下错误:    
  DllCaller.obj   :   error   LNK2001:   unresolved   external   symbol   @fastcallproc@0    
  DllCaller.obj   :   error   LNK2001:   unresolved   external   symbol   _nonameproc@0    
  根据错误提示信息将noname.def更改如下:    
  @fastcallproc@0   @3    
  nonameproc@0   @1    
  重新生成noname.lib,即可重新编译DllCaller.exe。    
  四、调用Windows未公开函数    
  根据以上分析,下面给出一个简单的调用Window98系统Shell32.DLL中序号为60的未公开函数,执行后将出现重新启动的对话框。    
  //shell32.def,据此生成Shell32.LIB    
  LIBRARY             "shell32"    
  EXPORTS    
  SHShutDownDialog@4   @60    
   
  //   DllCaller.cpp:调用未公开函数的控制台程序    
  //函数声明    
  extern   "C"   long     _stdcall   SHShutDownDialog(long   lShutdown);    
  //链接输入库文件    
  #pragma   comment(lib,"debug\shell32.lib")    
  int   main(int   argc,char*   argv[])    
  {    
        SHShutDownDialog(0);    
  return   0;    
  }     
 
 

张志强
2007-04-27

2007年4月20日星期五

美国枪击案追踪报道:专家准备破解赵承熙电脑

美国刑事侦破电脑专家将对赵承熙生前使用的电脑进行"解剖",使人们可以更多地了解赵承熙在虚拟世界的状态.

专家卡鲁索说,通过调查,他们可以掌握赵承熙上过哪些网站,都干了些什么,是否浏览图片,还可以查看他生前收发过的电子邮件.他们还能恢复那些被删到垃圾箱中的文档.

调查人员将利用这些文件、邮件和聊天记录来研究赵承熙的行为和作案动机.

卡鲁索表示,这是一个较长的过程,他们将彻底检查赵承熙电脑上的一切信息,确保毫无遗漏.

虽然弗吉尼亚理工大学枪击案中遇难者家人无法看到凶手被绳之以法,但从赵承熙电脑上恢复的数据可以帮助解开他们心头的谜团:这一切都是为什么?

 
 

张志强
2007-04-20

2007年4月8日星期日

得到类数据成员的位置

    类的数据成员的布局是按某种顺序的,有一个相对与类的头位子的偏移量,这个偏移量的计算方法如下:
     
        (size_t)&(((classname*)0)->members)

        classname 是一个类名,members是类中任何一个数据成员的名字。0在这里是一个地址,这是一个保留地址,它不能作为左值,但可以作为右值,去提取其中的对象。
        在0地址位,构造了一个classname的结构布局。

        在使用中去每次写这个表达式还是很麻烦的。可以用个宏将其包起来,如下

        #difine offsetof( ClassName, MemberName ) (size_t)&(((classname*)0)->members)

        其实这个宏在windows和linux平台下都以提供了。
 

张志强
2007-04-08

2007年4月6日星期五

如何获得物理内存大小

The GlobalMemoryStatus function obtains information about the system's current usage of both physical and virtual memory.

 

张志强
2007-04-06

2007年4月5日星期四

建行用户网上银行16万巨款被盗案告破

 
网上银行账户内16万余元莫名"流失",原来是被害人网上购物浏览图片时"顺道"将病毒也引进了电脑。日前,本市警方在云南警方的大力协助下,一举侦破"3・10"特大网上盗窃案,犯罪嫌疑人白某和葛某在昆明落入法网。据了解,这是迄今为止本市警方破获的最大一起网上盗窃案。昨天,警方向外界进行了情况通报。

3月10日中午,市民蔡先生像往常一样,上网查看自己银证通账户情况。意想不到的事情发生了,蔡先生发现账户下显示资金余额只有36.62元,而不是本应该有的16万余元。蔡先生马上联系建行,客服小姐说,钱都通过网上银行转账转走了。

卢湾警方接报后,迅速成立专案组。在分析案情和银行反馈信息并向被害人了解了上网情况后,侦查员进行了综合判断,认为被害人的电脑极有可能被黑客侵入并安装木马程序,从而导致银行账号、密码和认证证书被盗,导致账号内存款"流失"。侦查员通过查询银行转账记录,查明被害人的两张信用卡被犯罪嫌疑人通过网上银行分11次转出人民币共计163014元(不包括转账手续费),被盗资金全部转入一个开户在云南昆明的建设银行活期账户内并已被人取走。

据此,警方迅速派员赴云南昆明开展侦查工作。在当地警方的大力协助下,侦查员很快查明犯罪嫌疑人实施网上盗划的行为地在昆明市金沙小区金春苑某室内,并进一步查明该室实际居住人是青年男子白某和青年女子葛某。经与银行监控录像比对,确定白某即为取款人。

2007年3月28日晚,侦查员通过守候伏击顺利抓获了白某和葛某,并查获了作案用的电脑和部分赃物。

经查,犯罪嫌疑人白某今年31岁,葛某今年27岁。其中白某是某公司的软件开发人员。两人到案后,如实供述了经事先预谋,在网上利用发送照片之际,将携带木马程序的病毒植入被害人的电脑,进而获取了被害人的银行账号、密码和认证证书,同时修改了被害人的密码,盗取被害人银行账户内人民币的犯罪事实。

目前,两人已被依法刑事拘留,案件在进一步侦查中。
 
 
 
呵呵,我关心使用了什么取证方法,调查结果在法庭上的认可程度!!
 
 

张志强
2007-04-05

2007年4月3日星期二

上网偶遇

歹徒闯入民宅强 J妇女遭到誓死反抗,丈夫下地回来见老婆被歹徒压住,抡起铁铲怒拍,就听老婆骂道:「该死的,反抗了半天,被你一铲子给拍进去了.
 
 

张志强
2007-04-03

new的三中使用方法

new有三种使用方式:plain new,nothrow new和placement new。
(1)plain new顾名思义就是普通的new,就是我们惯常使用的new。在C++中是这样定义的:
    void* operator new(std::size_t) throw(std::bad_alloc);
    void operator delete(void *) throw();
提示:plain new在分配失败的情况下,抛出异常std::bad_alloc而不是返回NULL,因此通过判断返回值是否为NULL是徒劳的。
(2)nothrow new是不抛出异常的运算符new的形式。nothrow new在失败时,返回NULL。定义如下:
    void * operator new(std::size_t,const std::nothrow_t&) throw();
    void operator delete(void*) throw();
(3)placement new意即"放置",这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。定义如下:
    void* operator new(size_t,void*);
    void operator delete(void*,void*);
提示1:palcement new的主要用途就是反复使用一块较大的动态分配的内存来构造不同类型的对象或者他们的数组。
提示2:placement new构造起来的对象或其数组,要显示的调用他们的析构函数来销毁,千万不要使用delete。
char* p = new(nothrow) char[100];
long *q1 = new(p) long(100);
int *q2 = new(p) int[100/sizeof(int)];
 

张志强
2007-04-03

关于C结构体的bit field

C语言的STRUCT提供了一种叫bit field的语法,可以根据需要决定成员占用某字节的从X位到Y位,例如,下面一个结构:
struct tagtest
{
   char a:4;
   char b:2;
   char c:2;
};

这个定义的含义是整个结构是一个字节长度,成员a占4位,b占2位,c占2位。这样定义以后,我们可以方便的通过设置成员的值来设置结构,而不需要进行位操作了。例如:
tagtest myTest;
myTest.a = 10;
myTest.b = 2;
myTest.c = 1;
 

张志强
2007-04-03

Dump 调用堆栈的原理以及异常信息的反馈

动机:

在游戏开发过程中,我们利用 QA 部门来做产品的质量保证,尽可能将绝大部分错误消化在内部,保证游戏的版本质量,但是 QA 部门毕竟有他的局限性,尽管经过严格的测试也很难保证将所有的问题一网打尽.

 

通过在 Log 中转储的错误信息,我们可以进一步找出问题,但是 Log 文件产生在终端,我们拿到的也仅仅是公司内部测试部门产生的 Log 文件,显然公司内部得到的信息是很有限的,如果能从玩家那里拿到异常信息,我们才能最快的去解决问题,尽可能在错误产生重大影响之前将其解决,所以我们有必要从被动的获取异常信息,转为主动去获取.

 

可行性 :

       在错误发生时 Dump 调用堆栈,可以让我们知道错误发生的位置,这比已往普通的 LOG 更加有效的多.我们可以将出错的堆栈地址反馈回来.这一切在终端出现异常的时候自动进行. Windows 操作系统提供的 SEH 结构化异常机制可能让我们在程序崩溃的瞬间处理这些事情.

 

效率问题 :

       SHE windows 的异常机制,除非在编译时候特别指定不使用,否则总有默认的 SEH 处理机制, kernel32.dll 中有默认的 SEH 处理接口,当我们需要自己处理异常的时候,我们的处理点会挂接在异常处理链的最前端,这种链类似 Hook 的链.链的头部放在 fs[0] 的位置.也就是说效率的问题是可以不必考虑,

 

 

具体实现 :

       通过阅读反汇编代码可以了解函数调用过程中堆栈的结构 :

      

       1 函数调用时 CALL 将下一行指令地址压入堆栈

       2 函数运行第一行会将 EBP 压入堆栈

       3 保存当前堆栈地址到 EBP (mov ebp,esp)

      

       再遇到 call 时从第一步执行,所以每次第二步压入堆栈的都是上一层函数调用的 ESP 地址,而这个地址 +4 字节偏移则是当前调用函数返回后的下一条指令,也就是上一层函数的地址,所以我们只要知道当前函数的 EBP ( 也就是当前函数的栈顶 ) 就能够遍历得到所有调用堆栈层次.

       dumpebp.jpg

我们将windows SEH 结构化异常引入后,可以在异常发生的时候得到当前的EBP值,从而通过这个值得到整个调用堆栈的地址.

 

在发布工程的时候,我们只需要生成map文件,就可以通过这个地址得到崩溃位置.使用HTTP GET 或POST方式可以将我们所需要的崩溃信息提交到我们指定的网站.这种方式只是通过URL参数来提交数据,只需要使用API InternetOpenUrl就可以很方便的将信息提交.此外如果不使用HTTP方式,我们也可以在这个时候创建新的socket 对指定的服务器进行连接来传输数据.

    
    static TCHAR hdrs[] 
= _T("Content-Type: application/x-www-form-urlencoded"
); 
    static 
const TCHAR* accept= _T("Accept: */*"
); 
        static TCHAR action[]=_T("datecomit.aspx"
);//预提交的页面
        static TCHAR server[]=_T("192.168.9.119");//提交的server地址

    static TCHAR frmdata[
1024={0}; 
    _tcscpy(frmdata,_T("message=this is a test message");  //提交数据, message为提交名字   
    
    
// for clarity, error-
checking has been removed 
    HINTERNET hSession 
= InternetOpen("MyAgent"

    INTERNET_OPEN_TYPE_PRECONFIG, 
NULLNULL0
); 
    HINTERNET hConnect 
= InternetConnect(hSession, server

    INTERNET_DEFAULT_HTTP_PORT, 
NULLNULL, INTERNET_SERVICE_HTTP, 01
); 
    HINTERNET hRequest 
= HttpOpenRequest(hConnect, "POST", actionNULLNULL&accept, 01
); 
    HttpSendRequest(hRequest, hdrs, strlen(hdrs), frmdata, strlen(frmdata)); 

 

此后我们只需要定期观察所提交的内容,便可以立即得知是否有异常出现.根据同一异常出现的几率可以得知是否是致命的错误,是否需要紧急更新.


张志强
2007-04-03

2007年4月2日星期一

C++的编程模型

网上看见一篇文章,觉得很有道理!

原文:http://www.cppblog.com/netnchen/archive/2007/03/28/20764.html

 

 

一种语言代表了一种思维,而思维决定了问题的解决方式。

 

从程序设计语言出现到现在已经经历了四代。大多数高级语言都对应到第三代或四代程序设计语言。按其主要支持的编程模型(思维模式)分类,大致可以分为如下类型

1 过程型:这类语言把应用抽象为序列化的操作步骤,其典型代表如PASCALC等。

2 函数型:这类语言把应用抽象为函数(按定义,函数是从一个域到另一个域的映射);它们试图把问题分解为集合和集合间的函数关系。典型代表是LISP

3 逻辑型:这类语言把问题抽象为事实与规则的结合,试图通过逻辑演算解决实际问题。典型代表是PROLOG

4 面向对象型:这类语言从上世纪80年代后迅速发展,这类语言试图将实际问题抽象为独立的对象以及对象间的交互,典型代表是早期的SmallTalkEiffel

5 面向数据结构型:这类语言将问题抽象为对结构化数据的操作,例如现在常用的SQL

 

作为一个诞生与上世纪80年代初期的编程语言,C++被广泛的认为是一种支持面向对象的语言;但是,我认为C++的伟大之处却更多的在于(除了所提供完备的面向对象支持)它同样提供了C的面向过程的编程模型以及只有少数高级语言能支持的范型编程(我更喜欢叫它面向算法的编程)。在实际的应用过程中,我们往往需要不同层次的抽象,C++所支持的广泛的编程模型为我们提供了强大和灵活的工具,使得我们在设计和实现时能自如的选择不同的模型,以最合理的(组合)方式解决问题。

 

不可否认,在当今的程序设计中,面向对象已经基本上是一统江湖,但它往往并不是最合理的选择;例如在针对协议栈的开发中,使用面向过程的模型往往优于使用面向对象的模型(或许这也是为什么主流的电信设备供应商坚持使用C的原因?);而在针对算法进行抽象时,使用面向对象的思维模式几乎完全是不可行的(算法和对象间的不同之处是显而易见的);如果机械的运用面向对象思维,只能使最终产品的质量、可维护性、可读性下降。

 

或许有人会问,那么应该如何选择抽象方式呢?我觉得这个应该是因人和问题的不同而不同的,关键是在使用C++的过程中,多从这些方面进行思考、总结,以体会采用不同模式解决问题的优缺点,努力使用最合理的方式(或组合)对问题进行抽象并加以解决。我也常常为这些种种选择而迷惑,因此在这儿也希望那些已经到了"不惑"层次的哥们多多指教。

 

当然,从另一方面来说,越多的选择往往意味着越难的选择,这或许也是为什么现在这么多人趋骛与更单纯、更简单的JAVAC#的原因吧;但是当我们真正理解并掌握C++提供的这些编程模型时,我想我们会更加坚定当初选择C++的信念;

 

张志强
2007-04-02