delphi 全局变量 HInstance 到底是在什么时候赋值的

在学习 资源文件 和 钩子函数 时,
经常用到当前模块句柄(HInstance)这个全局变量. 今天特别想知道,
它到底是在什么时候给赋值的.

如何在Delphi里面利用Word的VBA代码进行一些总结

通过崩溃地址找错误行数之Delphi版
2009-5-11 17:42:35 来源: 转载 作者:网络 访问:360 次 被顶:2
次 字号:【大 中 小】
核心提示:什么是 MAP 文件?简单地讲, MAP
文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方、任何时候使用,不需要有额外的程序进行支持。而且,这是唯一能找出程序崩溃的地方的救星。
…DELPHI下生成MAP文件的方法:偶只知道下面两种,如果谁知道其他的方法
敬请告知 多谢 
生成详细的MAP信息的方法 

输入 HInstance; “Ctrl+鼠标” 找到它的声明之处: SysInit 单元的第 29
行(Delphi 2007) – HInstance: LongWord;
看来 Delphi 的隐含单元不只是 System.pas, 还有 SysInit.pas.

 
1、 
生成VBA代码。Word本身具有很强的可扩展性,尤其是支持用户自定义功能,其实现
的主要方式就是通过VBA代码来实现的。在“工具->宏->Visual
Basic编辑器”里面就可以看
到具体的宏代码,可以直接进行编辑。而且还可以使用录制宏的功能自动生成宏代码。方法是
选择“工具->宏->录制新宏”,然后执行自己想通过程序实现的功能,如存盘、打印等功能,
此时Word一边执行你要实现功能,一边将你的操作生成了一个宏,在实现功能后,可以选择
“工具->宏->VisualBasic编辑器”,查看生成的宏代码。

  1. project -> options -> Linker -> Map file 选择detailed. 
  2. D:FredCodeDELPHIMyPasErrLineByAddr2>dcc32 -GD
    project1.dpr

在 SysInit 单元的 658 行找到了它的赋值语句: HInstance :=
GetModuleHandle(nil,’,’,’);
是在一个 _InitExe 的过程中.

例如:我们要将文档中的“讨论”全部替换成“研讨”。
a. 
点击“工具->宏->录制新宏”,直接点击确定,默认的宏保存到了Normal.dot系统公
用模板里面。
b. 
点击“编辑->查找”,出现弹出对话框,输入查找和替换的字,点确定。进行替换。
c.  结束宏的录制,点击结束按钮。
d.  按F11或者“工具->宏->Visual Basic
编辑器”,查看宏代码。缺省查看Normal里面
的模块里面的NewMacros模块。
以下是生成的宏代码:
Sub Macro1()

‘ Macro1 Macro
‘ 宏在 2002-2-1 由 yzhshi 录制

   Selection.Find.ClearFormatting
   Selection.Find.Replacement.ClearFormatting
   With Selection.Find
       .Text = “讨论”
       .Replacement.Text = “研讨”
       .Forward = True
       .Wrap = wdFindContinue
       .Format = False
       .MatchCase = False
       .MatchWholeWord = False
       .MatchByte = True
       .MatchWildcards = False
       .MatchSoundsLike = False
       .MatchAllWordForms = False
   End With
   Selection.Find.Execute Replace:=wdReplaceAll
End Sub
2、 
精简宏代码。通常,生成的宏代码有很多语句对你要实现的功能来说都是多余的。我们要
做的就是如何找到我们需要的代码。此时我们查看具体的代码,剔除明显没有用途的代码,然后光
标停留在宏上面,按F5执行,看是否实现功能,逐步精简,得到最小代码。此步骤可参考Word的
VBA帮助来判断代码是否有用。

我们的代码为: 
unit Unit1;

那程序又是在什么时候执行的 _InitExe 方法呢?

如上例,精简下来,剩下以下代码。
Sub Macro1()
   Selection.Find.ClearFormatting
   Selection.Find.Replacement.ClearFormatting
   With Selection.Find
       .Text = “讨论”
       .Replacement.Text = “研讨”
   End With
   Selection.Find.Execute Replace:=wdReplaceAll
End Sub

//{$D+,L+}

用 F7 逐句调试…找不到!

3、 
转换成Delphi代码。这一步其实很简单,对于VBA代码,只需要在前面添加Word的句柄或者
文档的句柄或者文档的句柄.Application就可以直接操作了。
例:逐句翻译:(Word_Handle是Word的句柄)
(VB)     Selection.Find.ClearFormatting
(Delphi)  Word_Handle.Selection.Find.ClearFormatting;

interface

勾选 Project -> Options -> Compiler -> Use debug DCUs 后,
重新用 F7 逐句调试, 找到了:

(VB)     Selection.Find.Replacement.ClearFormatting
(Delphi)  Word_Handle.Selection.Find.Replacement.ClearFormatting;
以上两句简单添加上Word的句柄就可以了。

uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, 
  Dialogs, StdCtrls;

原来程序在执行 Application.Initialize; 之前就来到 SysInit 单元执行了
_InitExe 方法!

(VB)     With Selection.Find
             Text = “讨论”
             Replacement.Text = “研讨”
End With
(Delphi)  Word_Handle.Selection.Find.Text := ‘讨论’;
 Word_Handle.Selection.Find.Replacement.Text := ‘研讨’;
以上几句因为Delphi不支持Variant的With结构,所以分开写。同时转换成Delphi语法。

type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    procedure Button1Click(Sender: TObject); 
  private 
    { Private declarations } 
  public 
    { Public declarations } 
  end;

(VB)    Selection.Find.Execute Replace:=wdReplaceAll
(Delphi) Word_Handle.Selection.Find.Execute(Replace:=2);
上面一句存在一个小技巧,如何找到常量wdReplaceAll的数值为2?
这里有几个办法,
一:直接use Word2000或者word97单元,那

var 
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject); 
var 
  I, J: Integer; 
  p: PChar; 
begin 
  I := 10; 
  J := 0; 
  //I := I div J;  // 32 
  //ShowMessage(IntToStr(I)); 
  p := nil; 
  p^ := ‘A’;  // 38 
end;

end. 
// 想必大家看到了
会有返回0地址错误….我们这里就是要让它崩溃,让我让你崩溃 ^_^ 
然后执行 点击 然后出错 我的机器上 崩溃地址为0044d946 

如果要查找代码行号,需要使用下面的公式做一些十六进制的减法运算: 
崩溃行偏移 = 崩溃地址(Crash Address) – 基地址(ImageBase Address) –
0x1000  
减去后得到 0004c946 然后查找 0004c946  
0044d946 – 00400000 = 0004d946 – 00001000 = 0004c946 <=
后面列出的  
0004C946 就是它了 我们用ultraedit32之类的工具打开 .map文件 搜索
0004C94,找到了,然后就找 
<= 0004c946的那个地址 然后看到了 
Line numbers for Unit1(Unit1.pas) segment .text

    37 0001:0004C944    38 0001:0004C946    39 0001:0004C949    41
0001:0004C97C 
    41 0001:0004C983

38 0001:0004C946    就是它了。。。unit1.pas的第38行!!去代码里看一下
果然就是38行

 

 

在CSDN晃悠的时候无意发现MAP文件使用,记录下来供大家学习。
      
   仅通过崩溃地址找出源代码的出错行
   相信很多人都曾经遇到自己的程序在执行代码时会跳出“Access
xxxxx”地址错误。在通常情况下,我们根据此错误信息追踪不到错误代码行,只能一个个去看代码。特别是自己的软件已经发布,刚开始运行OK,但是时不时会跳出错误—真的是一件很崩溃的事情。可是你知道吗?我们已经拥有去追踪错误的方法,只是你一直不知道而已。
   
   MAP文件

   什么是
MAP文件?简单地讲,MAP文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方、任何时候使用,不需要有额外的程序进行支持。
   DELPHI下生成MAP文件的方法:偶只知道下面两种,如果谁知道其他的方法  
敬请告知   多谢   
   生成详细的MAP信息的方法   
   1.   project   ->   options   ->   Linker   ->   Map  
file   选择detailed.   
   2.   D:/Fred/Code/DELPHI/MyPas/ErrLineByAddr2>dcc32   -GD  
project1.dpr (哈,这个我没看懂,先用第一种吧)

   下面根据例子看看MAP文件的功能。
   我们的程序:
   unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms,
      Dialogs, StdCtrls;
    
    type
      TForm1 = class(TForm)
        btn1: TButton;
        btn2: TButton;
        edt1: TEdit;
        procedure btn1Click(Sender: TObject);
        procedure btn2Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    {本例子参照CSND相关文章给出的例子}
    procedure TForm1.btn1Click(Sender: TObject);
    var
      p: PChar;
    begin
      p:= nil;
      p^:= ‘a’;//此句将引发地址错误
    end;
    
   
//因为MAP文件在查找错误行时涉及到16进制计算问题,所以为了测试我特地加了一个Edit框用来计算最后得出的值
    procedure TForm1.btn2Click(Sender: TObject);
    begin
      edt1.text:= IntToHex(StrToInt(‘$’+ edt1.text) –
StrToInt(‘$00400000’) – StrToInt(‘$00001000’), 8) ;
    end;
    
    end.
    运行你的程序,点击Btn1时报地址类错误,在我的机器上描述如下:
    Access violation at address 0044F8D1 in module ‘Project1.exe’. Write
of address 00000000.
    
    错误地址为 0044F8D1。
    
    下面我们将根据这个地址找到Delphi 源码文件中对应代码行。
   
如果你设置的没有问题,程序运行后在同目录下会产生一个‘Project.MAP’文件,打开你的MAP文件。
    文件格式分为几个段落,你可以看到 :
    
     0002:00002970       _UninitializeFlatSB
     0002:00002B64       _WINNLSEnableIME
    
    
      Address         Publics by VALUE
//在这个段落中,你可以根据地址找到出错函数名
    
     0002:FFBB0010       TlsLast
     0001:000001F4       GetStdHandle
     0001:000001FC       RaiseException
     0001:00000204       RtlUnwind
     0001:0000020C       UnhandledExceptionFilter

     —-省略—-
     
     Line numbers for Unit1(Unit1.pas) segment .text
     
         32 0001:0004E8C8    33 0001:0004E8C9    34 0001:0004E8CE    35
0001:0004E8D4
         38 0001:0004E8D8    39 0001:0004E8F4    40 0001:0004E978    42
0001:0004E9E4
         42 0001:0004E9EB

      …….
     注释: Line numbers行下格式
     
       32 0001:0004E8C8 
       
    
第一个数字代表在源代码中的代码行号,第二个数是该代码行在所属的代码段中的偏移量。
     如果要查找代码行号,需要使用下面的公式做一些十六进制的减法运算:

     崩溃行偏移 = 崩溃地址(Crash Address) – 基地址(ImageBase
Address) – 0x1000 
     
     为什么要这样做呢?我们得到的崩溃地址都是由 偏移地址+ 基地址
得来的,所以在计算行号的时候要把基地址减去,一般情况下,基地址的值是
0x00400000 。另外,由于一般的 PE 文件的代码段都是从 0x1000
偏移开始的,所以也必须减去 0x1000 。
     (基地址可以通过 Project->Options, 在页面中有一个Image
Base值,就是它了)
     
    
如果你不知道十六进制如何计算,那么就按照我上面Btn2下的代码,将得到的
0044F8D1 崩溃行地址输入=> 
     得到 0004E8D1
     再看:
         Line numbers for Unit1(Unit1.pas) segment .text
     
         32 0001:0004E8C8    33 0001:0004E8C9    34 0001:0004E8CE    35
0001:0004E8D4
         38 0001:0004E8D8    39 0001:0004E8F4    40 0001:0004E978    42
0001:0004E9E4
         42 0001:0004E9EB
     找出 小于等于 0004E8D1 的数(最接近的数,且不能大于) –>34
0001:0004E8CE.
     源码中的错误行即为 34!

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注