奇迹智能英语记忆系统3.0真人发音,比原来的TTS可是好了N多。但其中一些单词还是只有TTS发音,而且句子全是TTS,而且用的是MS的TTS发音,真是惨不忍听。如果能采用NEOSPEECH发音,就和真人差不多了。
一开始,做了个可以用NEOSPEECH发音的ttd.exe程序,然后,修改奇迹智能原代码,将TTS发音的调用改为SHELL调用:形式为ttd.exe string,string为传入的参数(就是要读出的单词或句子),由ttd.exe再调用sapi发音。但这种SHELL方式每发音一次就要调用一次ttd.exe,速度比较慢。因此,想做成DLL调用。可以先做一个DLL,然后在奇迹智能中调用就行。DLL好做,但如何在奇迹智能中调用呢?

查了论坛上的资料,增加DLL调用的方法有两种,一种是PE工具增加improt,一种是在程序中直接调用。但是找不到VB程序中增加DLL调用的方法。
于是自己在程序中找找了,看看有没有相关的代码。
正好程序中有一个打开网站的SHELL调用,就先跟踪它吧!
用dream写的VB事件断点插件对全部事件下断,(为全部事件下断的方法:VB程序分析方法及修改例 (“万能分析断点”、启动form修改,form隐藏))很快就跟踪到SHELL调用:相关代码如下:


0044FD08   .  73 68 65 6C 6C 33 32 2E 64 6C 6C 00         ascii   "shell32.dll",0  要调用的DLL的ASCII     
0044FD18   .  53 68 65 6C 6C 45 78 65 63 75 74 65 41 00   ascii   "ShellExecuteA",0 要调用的函数的ASCII
0044FD26      00          db      00
0044FD27      00          db      00
0044FD28      08FD4400    dd      luosoft_.0044FD08     ;  ASCII "shell32.dll"    指向DLL的地址
0044FD2C      18FD4400    dd      luosoft_.0044FD18     ;  ASCII "ShellExecuteA"     指向函数的地址
0044FD30      00        db      00
0044FD31      00          db      00
0044FD32      00          db      00                         有四个00
0044FD33      00          db      00
0044FD34      D46B6100    dd      luosoft_.00616BD4                 地址指针,在00616BD4+8中存放函数的调用地址
0044FD38      00          db      00
0044FD39      00          db      00
0044FD3A      00          db      00
0044FD3B      00          db      00
0044FD3C      00          db      00
0044FD3D      00          db      00
0044FD3E      00          db      00
0044FD3F      00          db      00
0044FD40   $  A1 DC6B6100 mov     eax, [616BDC]      将00616BD4+C中的函数调用地址放EAX
0044FD45   .  0BC0        or      eax, eax
0044FD47   .  74 02       je      short 0044FD4B      函数调用地址是否为空
0044FD49   .  FFE0        jmp     eax           不为空,直接调用函数
0044FD4B   >  68 28FD4400 push    0044FD28         函数调用地址为空时,压入DLL指针
0044FD50   .  B8 D02C4000 mov     eax, <jmp.&MSVBVM60.DllFunctionCall> 
0044FD55   .  FFD0        call    eax           调用VB库得到函数调用指针,指针放入00616BD4+8=616BDC
0044FD57   .  FFE0        jmp     eax           调用函数

要注意的是,
0044FD28      08FD4400    dd      luosoft_.0044FD08     ;  ASCII "shell32.dll"    指向DLL的地址
0044FD2C      18FD4400    dd      luosoft_.0044FD18     ;  ASCII "ShellExecuteA"     指向函数的地址
044FD30      00        db      00
0044FD31      00          db      00
0044FD32      00          db      00                         有四个00
0044FD33      00          db      00
0044FD34      D46B6100    dd      luosoft_.00616BD4 
这一段数据很重要,一是指向DLL及函数的指针,还有就是四个00及其后的函数地址存放地址。开始时,我没有指定函数存放地址,结果调用就出错。

需要注意的是0044FD50   .  B8 D02C4000 mov     eax, <jmp.&MSVBVM60.DllFunctionCall> 
<jmp.&MSVBVM60.DllFunctionCall>究竟是什么呢?
我们来看看VB程序开头,可以找到如下一连串jmp,仔细看看,就能找到<jmp.&MSVBVM60.DllFunctionCall>的地址了:00402CD0

00402CB8   .- FF25 F0104000    jmp     [<&MSVBVM60._CIsin>]                      ;  MSVBVM60._CIsin
00402CBE   .- FF25 5C114000    jmp     [<&MSVBVM60._CIsqrt>]                     ;  MSVBVM60._CIsqrt
00402CC4   .- FF25 94124000    jmp     [<&MSVBVM60._CItan>]                      ;  MSVBVM60._CItan
00402CCA   .- FF25 84124000    jmp     [<&MSVBVM60._allmul>]                     ;  MSVBVM60._allmul
00402CD0   .- FF25 30114000    jmp     [<&MSVBVM60.DllFunctionCall>]             ;  MSVBVM60.DllFunctionCall
00402CD6   .- FF25 4C114000    jmp     [<&MSVBVM60.__vbaRedim>]                  ;  MSVBVM60.__vbaRedim
00402CDC   .- FF25 90104000    jmp     [<&MSVBVM60.__vbaVarXor>]                 ;  MSVBVM60.__vbaVarXor
00402CE2   .- FF25 38104000    jmp     [<&MSVBVM60.__vbaVarIdiv>]                ;  MSVBVM60.__vbaVarIdiv
00402CE8   .- FF25 E8114000    jmp     [<&MSVBVM60.__vbaVarInt>]                 ;  MSVBVM60.__vbaVarInt


现在就可以增加自己的DLL调用了。按以上代码形式,找个空地,将DLL和函数改为自己的。然后在调用DLL的程序中加上调用就行了:
push 参数     这个参数是你的DLL函数要用到的参数,我的这个程序就是string的地址,只有一个参数,所以用一个PUSH就行了。
call 0044FD40


附:回顾一下一般程序中增加DLL调用的方法: http://bbs.pediy.com/showthread.php?s=&threadid=17365&highlight=%BC%C7%CA%C2%B1%BE
0100878B      push    0100873C                         ; /pModule = "USER32.dll"
01008790      call    [<&KERNEL32.GetModuleHandleA>]   ; \GetModuleHandleA
01008796      push    01001344                         ; /ProcNameOrOrdinal = "AppendMenuA"
0100879B      push    eax                              ; |hModule
0100879C      call    [<&KERNEL32.GetProcAddress>]     ; \GetProcAddress
010087A2      call    eax

原来是没看懂,现在明白了:
这是先push要调用的DLL的地址,然后调用GetModuleHandleA,而call    [<&KERNEL32.GetModuleHandleA>]的代码也是应从程序中找到。
call    [<&KERNEL32.GetProcAddress>] 也是一样的。

后来发现,采用[<&KERNEL32.GetModuleHandleA>]调用的DLL,是在程序启动时已加载了的。
如果要调用启动时未加载的DLL,必须用call    <jmp.&kernel32.LoadLibraryA> 
即:
0100878B      push    0100873C                         ; /pModule = "USER32.dll"
01008790      call    [<&KERNEL32.LoadLibraryA>]   ; \LoadLibraryA
01008796      push    01001344                         ; /ProcNameOrOrdinal = "AppendMenuA"
0100879B      push    eax                              ; |hModule
0100879C      call    [<&KERNEL32.GetProcAddress>]     ; \GetProcAddress
010087A2      call    eax

MSVBVM60.DllFunctionCall中用的就是LoadLibraryA