PctGL SERIES  
http://pctgl.cnblogs.com
2008-11-05 13:01



怎么想起来写这个东西?。。。。。

最近很多人都在研究怎么改造VB调用APi的方法,让VB运行的更快些,我在xx说过一次,关于这个的研究简直是在浪费时间。不以自己使用感觉说明,引述MSDN中的文章“VB调用APi只存在1-3%的效率问题。而且特别是在当第2次调用某个APi的时候,这个问题几乎可以忽略” 当然上面那段话有大部分是我钻的-.-!。。。 不过MSDN确实提到了 1-3%的效率问题。微软认为那是个可以完全忽略的问题,确实不值得研究噢。

 

下面我们就研究下 VB 到底是怎么样实现调用APi的。

 

 

00401A08   $  A1 E8324000    MOV EAX, DWORD PTR DS:[4032E8]

00401A0D   .  0BC0           OR EAX, EAX

00401A0F   .  74 02           JE SHORT 工程1.00401A13

00401A11   .  FFE0            JMP NEAR EAX

00401A13   >  68 F0194000     PUSH 工程1.004019F0

00401A18   .  B8 30114000     MOV EAX, <JMP.&MSVBVM60.DllFunctionCall>

00401A1D   .  FFD0           CALL NEAR EAX

00401A1F   .  FFE0           JMP NEAR EAX

 

 

以上代码摘录VB编译后的Exe中,经常反编译VB生成的程序的朋友们,一定看到过此类代码,而且很常见。以上代码经常出现在程序中,而且很多。到底有多少有没有个统计呢?有的,你写的VB代码中调用了多少个APi就会有多少个这个(代码)函数。要这么多吗?

 

先分析下这段代码到底是怎么执行的。

代码分析:

       我们一行一行的解读上面的代码。

1:               mov  eax,[4032e8]               ;从一个全局变量中复制一个Long型的数值

2:               or    eax,eax                      ;测试 eax 的值

3:               je    5                       ;如果eax的值为 0,跳转到第5

4:               jmp   eax                           ;如果eax的值不为0,跳转执行eax所指向的函数地址

5.                      push  工程1.004019F0        ;参数压栈

6.                      mov  eax,[DllFunctionCall] ;复制DllFunctionCall的函数地址到 Eax

7.                      call   eax                            ;执行DllFunctionCall函数

8.                       jmp   eax                           DllFunctionCall 返回指定的APi函数地址,此句直接跳转执行那个(函数)地址

 

经过分析可以看到,当第1次调用某APi时,首先执行的是一个函数,一个转发函数。他首先取一个全局变量的值,测试是否为 0 ,当不为 0 的时候执行他。再向下看,如果这个值为0,函数执行了 DllFunctionCall ,跟进去看看,DllFunctionCall 执行了 LoadLibraryGetProcAddress 并且将得到的APi函数地址存放到了开始时测试的全局变量中,然后再执行这个获得的APi函数。

 

再简化点来看,就是声明了一个全局变量(很有可能是一个足够大的结构),保存APi地址,第1次调用时,把这个地址填充进去,第2次直接调用。

 

综合以上所有的条件,到现在为止,我们可以做出一个xx性的判断,VB建立了一个类似导入表的机制,但并非导入,只是类似。

 

类似点:
1.
导入表初始化前为空或为偏移地址。

2. 导入表在初始化后才具有实际意义。

3. 导入表也可以看做一个全局变量(表)。因为他是被调用的APi集合,是一个全局变量表。

 

对应的VB实现方式,则为:

1. 存放APi地址的变量未经过第1次调用,则为空值(0值)。

2. 存放APi地址的变量经过第1次调用后,具有指向实际APi地址的值,可直接跳转执行。

3. 存放APi地址的变量就是个全局变量,初始化一次后即可全局通用。

 

再回过来想想最开始的时候提出的问题,是否需要把调用的APi都做这样一个封装呢?这样的函数构造方式显然,很简单,方便,但牺牲了存储空间,所有这些代码经过编译后估计150字节也够了吧?10APi 1.5K100个占用15K。很典型的空间换时间的办法,还是比较值得的。

 

1~3% 的效率问题。

为什么MS会说是1~3%的效率呢?以汇编的角度理解,3%的损失应该是第1次调用时可能带来的效率问题,以后每次调用只比导入表调用的方式多了 Mov reg/memOr reg/reg 2个指令最多占用不超过5xx(这词叫什么给忘了,CPU最基本的执行时间单位),而且用的是eax寄存器,效率可能更高。

 

想改造VB的朋友们,你们的方案是否比VB的更高效呢?

 

 

 

①:这个参数内容是一个结构,初步判断形式如下:

Type ModuleInfo

ModuleName  as    string

ProcName      as    string

End Type

 

②:在显式的跨模块函数调用中,如Exe调用Dll中的函数,Exe文件在编译过程中会创建一个导入表(IAT),这个表可以看做一个全局变量的集合。 Exe文件未被加载前导入表中可能只存在被调用的函数的在某个模块中的偏移地址或导出函数名字,Exe初始化加载工作之一就是重定位这些APi地址。
posted on 2009-08-06 17:01  PctGL  阅读(699)  评论(0编辑  收藏  举报