CSDN博客

img shanhe

利用钩子函数动态汉化外文程序菜单

发表于2004/6/24 9:54:00  2876人阅读

分类: MISC

 

利用钩子函数动态汉化外文程序菜单

 杨山河

 

引言
当前,英文版的应用程序,一般在中文平台上就相应有中文版。这其中有软件商自己推出的中文语言本地化版本,也有国内或国外的第三方供应商为了市场需要而推出中文版本。后者汉化的方式又分内核汉化和外挂汉化。由于内核汉化涉及到版权等一系列问题,加之源代码设计不一定与中文兼容,因而很少有人采用。因此,在今天的中国,从中文之星到四通立方均采用外挂汉化的方式。而外挂汉化又可以采用多种途径,并且根据所处的操作系统平台的语种不同(简体中文、繁体中文、英文等),汉化的程度也不同。笔者对其中的技术略有了解,通过研究,在此提出一种在中文操作系统平台上外挂汉化的方法,供感兴趣的同仁讨论。

在此之前,我们必须明确几个相关概念。

 

二.Windows的“钩子”

同PC上的老资格操作系统DOS编程不同,Windows下编程必须得遵守微软给大家规定的“游戏规则” :建立一个窗口,为该窗口建立一个消息循环,在窗口函数中分段完成各种工 作,总之:必须听从微软的安排,否则,你的应用程序就是不符合规范的 ,难以保证兼容性。其实,微软留下很多“后门”,“钩子函数”就是其中手段之一。

所谓“钩子函数”,即是的消息处理机制中留给应用程序的一个手段,应用程序可以利用它安装一个针对特定的消息的子例程,使得我们可以在某些消息在到达目的地之前监视或修改它们。Windows提供了7种钩子函数, Windows 95有13种。鉴于操作系统的发展趋势,不作Windows3.x的讨论,以下提到Windows时指Windows 95(简称Win95)。安装和卸下这些钩子函数的方法是相同的,Win95使用以下API:

HHOOK SetWindowsHookEx(//安装

int idHook, //要安装的钩子的类型

HOOKPROC lpfn,// 钩子函数地址(回调函数)

HINSTANCE hMod,// 钩子函数所在程序的实例句柄

DWORD dwThreadId//要为之安装钩子的线程的ID

);

 

当一些应用程序安装同一类的钩子时,就会形成一个钩子函数链。安装函数返回指定钩子类型所安装的过滤函数实例句柄。。

BOOL UnhookWindowsHookEx(//卸下

HHOOK hhk //将要被卸下的钩子函数的句柄

);

Win95下的钩子类型有:

WH_CALLWNDPROC //窗口过程钩子

WH_CALLWNDPROCRET //消息已在窗口过程处理

//后,接收这些消息的钩子函数

WH_CBT //基于计算机的训练钩子

WH_DEBUG //查错钩子

WH_FOREGROUNDIDLE //前台空闲窗口钩子

WH_GETMESSAGE //接收投递消息的钩子

WH_JOURNALPLAYBACK //回放以前输入消息钩子

WH_JOURNALRECORD //记录输入消息钩子

WH_KEYBOARD //键盘钩子

WH_MOUSE //鼠标器钩子

WH_MSGFILTER //对话框等的消息钩子

WH_SYSMSGFILTER //系统消息钩子

WH_SHELL //外壳钩子

通常将钩子函数放在DLL中,使得它可供系统内的每一个进程访问,因此安装钩子API中的第三个参数应该是DLL模块的句柄。

在这些类型的钩子中,WH_GETMESSAGE类型的钩子可以为我们存取窗口的信息服务。该钩子截获所有通过标准的消息循环获取函数GetMessage从应用队列获取的消息。这些消息当中包含着很多有价值的数据,譬如:窗口句柄等。

一般情况下,钩子函数无论所关联的进程是否活动,均会被调用,会降低系统的性能,甚至破坏系统稳定。因此,钩子函数的使用应该慎重,并且使用完毕,应立即卸下。Win95是一个多线程的系统,需要使用者注意所用的钩子函数的作用范围。以下是作用范围的说明:

钩子 作用范围

WH_CALLWNDPROC 线程或系统

WH_CBT 线程或系统

WH_DEBUG 线程或系统

WH_GETMESSAGE 线程或系统

WH_JOURNALPLAYBACK 仅系统

WH_JOURNALRECORD 仅系统

WH_KEYBOARD 线程或系统

WH_MOUSE 线程或系统

WH_MSGFILTER 线程或系统

WH_SHELL 线程或系统

WH_SYSMSGFILTER 仅系统

 

 

利用钩子函数实现汉化
如果我们知道一个窗口的句柄,我们就可以GetMenu函数获得

该窗口的菜单句柄。之后,我们可以通过以下菜单函数进行每一菜单项的外文字符串获取,通过查字典可以获取相应的中文字符串,最后将中文字符串对外文字符串进行替换。注意,中文的显示本程序并不能解决,所以需要在中文操作系统上执行。这里的以上过程可以表示为:

 

hWnd(窗口句柄)

 

GetMenu( hWnd)

 

hMenu(菜单句柄)

GetSubMenu(hMenu,flag,position)

 

hSubMenu(子菜单句柄)

GetMenuItemString(hSubMenu,position,lpString,len,flag)

lpszText(菜单项外文字符串)

 

FindChinese(lpString,lpChinese,len)

 

lpszChinese(中文字符串)

 

ModifyMenu(hSubMenu,position,flag,id,lpChinese)

菜单项汉化

 

那么,如何从一个程序获取另外一个程序的窗口句柄呢?这必须知道窗口的窗口类。因为窗口类名是一个全局原子,不可能和另外的类名相同,可以作为唯一标识 。通过类名,FindWindowEx可以获取它的当前正在运行的应用程序实例的窗口句柄。

通过以上分析,我们可以提出这样一个方案:

运行一个特定的应用程序,该程序完成两项任务:

 
搜索我们的外文版程序的类名,获取该程序当前实例的窗口句柄、主线程的ID,为其安装一个WH_GETMESSAGE类型的钩子函数。利用窗口句柄获取主菜单句柄,在菜单后面加上一个自定义的菜单项。该菜单项主要用来向窗口过程发送消息,以便WH_GETMESSAGE类型的钩子函数能够激活,并且获得外文

程序的主菜单的句柄,进行上述分析的汉化过程。

2.

钩子函数主要进行汉化工作。钩子函数应该存在与一个DLL中。主要用来截获自定义的菜单消息,在此菜单消息处理中,获取主菜单句柄,查找DLL中的字典,替换外文菜单项。

 

程序说明
由于Win95是多线程环境,因而给程序挂接钩子函数时必须指定线程ID。在Windows 3.x环境下,该参数通常置为 NULL。
钩子安装程序在寻找窗口类时,若找不到,应该根据设定的有关外文程序的路径,利用Winexec函数启动之,再来寻找,以便可以顺利安装钩子函数。
钩子安装程序通过获得窗口句柄,此时也可以进行菜单汉化。但考虑到许多应用程序在运行当中往往动态修改菜单,因而将汉化的主要过程放在钩子函数中。因为在卸下前钩子总有效,所以通过自定义菜单项的选取,就可以方便的进行动态汉化。
钩子安装程序和我们要汉化的外文程序是两个不同的进程,在Win95下分别享有两个不同的应用程序空间。要实现动态连接库的字典为外文程序所用,因为钩子函数挂接后,同所关联的线程属同一进程空间,而DLL中的字典数据属于钩子安装程序,所以必须将字典和一些钩子函数用到的变量设置成存储器共享,需用到#pragma date_seg指令。具体见程序代码。这一点很重要,否则会出现令人讨厌的保护错。
为简单起见,动态连接库中的字典词条数量有限。实际上,Win95下,可以大于64K。另,示例中采用的模式匹配极为简单,大家可以在次基础上进行改进。
菜单中往往包含有许多的键盘操作简捷键,所以中文字符串必须要继承外文字符串中的相关内容,原程序的所有操作必须不能被改变。。
替换菜单项后,必须马上进行菜单条的刷新,否则会使菜单的显示极为混乱。具体应在替换后立即调用DrawMenuBar函数。
要定义好.def文件,对函数的导出、引入必须准确,否则会导致编译失败。
程序在Pwin95、BC5.0,PMMX166环境下通过。可能需要BC的一些动态连接公用库,调试时注意。
10为起到演示目的、缩减篇幅,在此将程序的部分代码进行注释。

钩子函数安装程序:

#define STRICT

#include <windows.h>

#include <windowsx.h>

#include "mate.h"

BOOL WINAPI _import InstallMsgFilterHook(HWND hWnd);

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPre, LPSTR lpCmdLine, int nCmdShow)

{

char szProgramClass[30]="shi";//外文程序的类名,此处为本演示//程序的类名

char szProgramPath[30]="e://ysh//shi//shi.exe";//外文程序路径,此

//处可以设置成读者自定义的要被汉化的程序路径

MSG msg;

HWND _hWnd; //要被挂接钩子的外文程序句柄

HMENU hMenu; //主菜单句柄

int nTopLevel; //顶级弹出菜单的项数

_hWnd=FindWindow( szProgramClass,NULL);

if(_hWnd= =NULL) //未找到

{

if(WinExec(szProgramPath,SW_SHOW)==ERROR_FILE_NOT_FOUND) //未找到指定的程序

{

MessageBox(NULL,"File not found!","中文伴侣",MB_OK);

return(FALSE);

}

_hWnd=FindWindow(szProgramClass,NULL);//启动之后再次寻找

if(_hWnd==NULL)

{

MessageBox(NULL,"Find Window Faild!","中文伴侣",MB_OK);

return(FALSE);

}

}

LoadLibrary("mate.dll"); //装入库

hMenu=GetMenu(_hWnd); //获取菜单

AppendMenu(hMenu,MF_BITMAP|MF_ENABLED,CM_MYMENUITEM,"中文&&英文"); //增加自己的菜单项

 

DrawMenuBar(_hWnd); //重画菜单条,否则菜单显示混乱

if(!InstallMsgFilterHook(_hWnd))//安装钩子

{

MessageBox(NULL,"钩子函数安装失败!","中文伴侣",MB_OK);

return(FALSE);

}

}

return(TRUE);

}

 

动态连接库的主要代码:

#define COMMONNUM 100

#include <windows.h>

#include <windowsx.h>

#include <string.h>

#include "mate.h"

#pragma data_seg("Tweny") / /须在.def中说明成共享段

static HHOOK MsgFilterHook=NULL;//接收投递消息钩子句柄

static HINSTANCE hInstance=NULL;

static HWND shihWnd=NULL; //外文程序窗口句柄

static BOOL flag=TRUE;

static HMENU hWndMenu=NULL; //菜单句柄

COMMONDICT word[COMMONNUM]= //字典,结构定义在头

//文件中

{

{"&File", "文件&F"},

{"&New", "新建&N"},

{"&Open", "打开&O"},

{"&Save", "保存&S"},

{"Save &As", "另存为&A"},

{"P&rinter Setup", "打印机设置&R"},

{"Page Set&up", "页面设置&U"},

{"&Print", "打印&P"},

{"E&xit", "退出&X"},

{"&Edit", "编辑&E"},

{"&Undo", "撤消&U"},

{"&Redo", "重做&R"},

{"Cu&t", "剪切&T"},

{"&Copy", "复制&C"},

{"&Paste", "粘贴&P"},

{"&Window", "窗口&W"},

{"&Windows", "窗口&W"},

{"&Tile", "级联&T"},

{"&Cascade", "层叠&C"},

{"&Hide", "隐藏&H"},

{"&Hidden", "隐藏&H"},

{"&View", "展示&V"},

{"&All", "所有的&A"},

{"&Hidden Document List", "隐藏所有的文本窗口&H"},

{“&Help","帮助&H"},

{"Help Topics","帮助主题&W"},

{"&What's This?","这是什么&W"},

{"&About", "关于&A"},

};

#pragma data_seg( ) //共享段声明结束

 

BOOL WINAPI _export InstallMsgFilterHook(HWND hWnd);

LRESULT CALLBACK _export MsgFilter(int nCode ,WPARAM wParam,LPARAM lParam); //钩子类型声明

BOOL WINAPI searchDict1(char * english,char * chinese);//查字典

BOOL WINAPI DllEntryPoint(HINSTANCE hInThis,DWORD fDwReason,LPVOID lpvResvered)

{

hInstance=hInThis;

switch( fDwReason)

{……}//参见相关文档

return (TRUE);

}

BOOL WINAPI _export InstallMsgFilterHook(HWND hWnd)

{//安装钩子函数

DWORD pid;//线程ID

shihWnd=hWnd; //被挂接的程序窗口

hWndMenu=GetMenu(shihWnd);//获取菜单句柄

if(!hWndMenu)MessageBox(NULL,"Get Menu Faild",

"Install",MB_OK);

return(MsgFilterHook=SetWindowsHookEx(WH_GETMESSAGE ,(HOOKPROC)MsgFilter,hInstance ,GetWindowThreadProcessId(hWnd,&pid) )!=NULL);//安装指定线程的钩子函数

}

LRESULT CALLBACK _export MsgFilter(int nCode ,WPARAM wParam,LPARAM lParam)

{//消息过滤函数

HMENU hPopup; //主菜单句柄

char lpMenustr[30];//菜单项的字符串

int i,Count;

if(nCode>=0) //小于零应留给系统处理

{

switch(nCode)

{

MSG * msg;

HMENU hSubMenu,hSub2Menu;//两层菜单

HWND hWndThis;

char buf[100],chinese[100];//字符缓冲区

int topnum,subnum,sub2num;//分别为顶级菜单项数、

//弹出菜单项数、二级子菜单项数

int i=0,j=0,k=0;

UINT ItemID; //菜单项ID,修改菜单项时不可更改菜单的命令ID

MENUITEMINFO MenuItem;

case HC_ACTION :

msg=(MSG *)lParam;

switch (msg->message)

{

case WM_COMMAND:

if(msg->wParam==CM_MYMENUITEM)//是自定义菜单项吗

{

hWndThis=msg->hwnd;//获取的窗口句柄

hWndMenu=GetMenu(hWndThis);//获取主菜单句柄

topnum=GetMenuItemCount(hWndMenu);//获取顶级菜单项数

for(i=0;i<topnum-1;i++)

{

if(GetMenuString(hWndMenu,i,buf,100,MF_BYPOSITION))

//获取指定位置菜单项的字符串

{

if(searchDict1(buf,chinese))//若从字典中查到

{

ItemID=GetMenuItemID(hWndMenu,i);//先获取菜单ID

ModifyMenu(hWndMenu,i,MF_STRING|MF_BYPOSITION,ItemID,chinese);//修改菜单

DrawMenuBar(hWndThis);//立即更新

}

else

{

}

}

}

if (hWndMenu)

{

for(i=0;i<topnum-1;i++)

{//汉化弹出菜单项

hSubMenu=GetSubMenu(hWndMenu,i);

subnum=GetMenuItemCount(hSubMenu);

for(j=0;j<=subnum;j++)

{

{

if(GetMenuString(hSubMenu,j,buf,100,MF_BYPOSITION))

//弹出菜单项内容是字符吗

{

if(searchDict1(buf,chinese))//查字典

{

ItemID=GetMenuItemID(hSubMenu,j);

if(ItemID==0xFFFFFFFF)//还有下层子菜单吗

{//汉化

hSub2Menu=GetSubMenu(hSubMenu,j);

sub2num=GetMenuItemCount(hSub2Menu);

for(k=0;k<sub2num;k++)

{

if(GetMenuString(hSub2Menu,k,buf,100,MF_BYPOSITION))

{

if(searchDict1(buf,chinese))

{

ItemID=GetMenuItemID(hSub2Menu,k);

ModifyMenu(hSub2Menu,k,MF_STRING|MF_BYPOSITION,ItemID,chinese);

}

}

}

}

ModifyMenu(hSubMenu,j,MF_STRING|MF_BYPOSITION,ItemID,chinese);

DrawMenuBar(hWndThis);//立即更新

}

}

}

}

}

}

else

MessageBox(NULL,"hWndMenu is NULL","My hook",MB_OK);

}//未获得主菜单句柄

break;

default:

break;

}

break;

}

}

return( CallNextHookEx(MsgFilterHook,nCode,wParam,lParam));

}

 

BOOL WINAPI searchDict1(char * english,char * chinese)

{

char in[100],out[100];

int i;

BOOL flag;//查找标志

lstrcpy(in,english);

for(i=0;i<60/*NUMBER*/;i++) //此处限定字典的长度

{

lstrcpy(out,word[i].English);

flag=lstrcmp(in,out);

if(!flag)//查到

{

lstrcpy(chinese,word[i].Chinese);//复制中文字符

break;

}

else

{//此处可添加查第二字典的代码

}

}

return(!flag); //返回查找结果

}

 
==

1998年写的   

0 0

相关博文

我的热门文章

img
取 消
img