怎么想起来写这个东西?。。。。。
最近很多人都在研究怎么改造VB调用APi的方法,让VB运行的更快些,我在xx说过一次,关于这个的研究简直是在浪费时间。不以自己使用感觉说明,引述MSDN中的文章“VB调用APi只存在1-3%的效率问题。而且特别是在当第2次调用某个APi的时候,这个问题几乎可以忽略” 当然上面那段话有大部分是我钻的-.-!。。。 不过MSDN确实提到了 1-3%的效率问题。微软认为那是个可以完全忽略的问题,确实不值得研究噢。
下面我们就研究下 VB 到底是怎么样实现调用APi的。
以上代码摘录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 工程
6. mov eax,[DllFunctionCall] ;复制DllFunctionCall的函数地址到 Eax中
7. call eax ;执行DllFunctionCall函数
8. jmp eax ;DllFunctionCall 返回指定的APi函数地址,此句直接跳转执行那个(函数)地址
经过分析可以看到,当第1次调用某APi时,首先执行的是一个函数,一个转发函数。他首先取一个全局变量的值,测试是否为 0 ,当不为 0 的时候执行他。再向下看,如果这个值为0,函数执行了 DllFunctionCall ,跟进去看看,DllFunctionCall 执行了 LoadLibrary;GetProcAddress; 并且将得到的APi函数地址存放到了开始时测试的全局变量中,然后再执行这个获得的APi函数。
再简化点来看,就是声明了一个全局变量(很有可能是一个足够大的结构),保存APi地址,第1次调用时,把这个地址填充进去,第2次直接调用。
综合以上所有的条件,到现在为止,我们可以做出一个xx性的判断,VB建立了一个类似导入表的机制,但并非导入表②,只是类似。
类似点:
1. 导入表初始化前为空或为偏移地址。
2. 导入表在初始化后才具有实际意义。
3. 导入表也可以看做一个全局变量(表)。因为他是被调用的APi集合,是一个全局变量表。
对应的VB实现方式,则为:
1. 存放APi地址的变量未经过第1次调用,则为空值(0值)。
2. 存放APi地址的变量经过第1次调用后,具有指向实际APi地址的值,可直接跳转执行。
3. 存放APi地址的变量就是个全局变量,初始化一次后即可全局通用。
再回过来想想最开始的时候提出的问题,是否需要把调用的APi都做这样一个封装呢?这样的函数构造方式显然,很简单,方便,但牺牲了存储空间,所有这些代码经过编译后估计150字节也够了吧?10个APi 用1.5K,100个占用15K。很典型的空间换时间的办法,还是比较值得的。
1~3% 的效率问题。
为什么MS会说是1~3%的效率呢?以汇编的角度理解,3%的损失应该是第1次调用时可能带来的效率问题,以后每次调用只比导入表调用的方式多了 Mov reg/mem;Or reg/reg 这2个指令最多占用不超过5个xx(这词叫什么给忘了,CPU最基本的执行时间单位),而且用的是eax寄存器,效率可能更高。
想改造VB的朋友们,你们的方案是否比VB的更高效呢?
①:这个参数内容是一个结构,初步判断形式如下:
Type ModuleInfo
ModuleName as string
ProcName as string
End Type