Microsoft Agent技术应用

    技术2022-05-11  131

    Microsoft Agent技术应用                                       --AgentShell的实现原理介绍 [摘要]     本文介绍了如何应用Agent的以及AgentShell的实现原理和几个重要的技术处理。 [关键词]     Agent,COM,角色,语音识别,语音合成。     对Agent编程的方法主要有使用VB,VC等语言进行ActiveX调用,除此之外还有直接通过VC进行COM 编程调用。在VB中调用Agent是最简单不过了,但由于VB程序本身存在诸多缺陷,很难在实际中应用。 而在VC中,由于Agent内部完全采用了UNICODE编码,同时还要处理各种繁杂的COM接口,从而也存在一 定的问题。AgentShell是建立在Agent和应用程序之间的一个外壳程序,通过它可将Agent复杂的COM接 口封装起来,转变为简单的函数调用,很好的实现对Agent的控制。同时AgentShell也作为一个独立的程 序,可处理英文自动朗读等功能,本文将详细介绍其实现原理。     (一) 原理介绍     AgentShell和Agent Server的连接是通过COM调用来实现的,对于与应用程序的通信是通过WM_COPYDATA 消息来实现的, 下图表示出AgentShell与其它程序的关系:             [ Agent Server ]                     ¦               [ COM调用 ]                     ¦             [ AgentShell ]                     ¦                 [ 消息 ]                     ¦             [ 应用程序 ]   将一个Agent控制加载相应的动画和语音码我们称之为“角色”,一般使用COM调用创建一个Agent角色, 要经过以下几个过程:             [ 初始化COM ]                     ¦           [ 连接Agent COM Sever,创建Agent控制 ]                     ¦           [ 注册Agent控制的消息反应器(Notify Sink) ]                     ¦           [ 加载角色数据文件,创建一个角色(Character) ]                     ¦           [ 设置角色的语言、初始位置以及其它属性 ]                     ¦             [ 显示角色 ]   AgentShell中定义以下全局变量来控制角色的属性和动作:   角色的消息ID:    long g_lNotifySinkID。   角色ID: long g_lMyAgentID。   Agent控制指针: IAgentEx *g_pAgentEx。   角色指针: IAgentCharacterEx *g_pMyAgent。   角色消息反应器指针: AgentNotifySink *g_pSink。   使用以上变量可很容易的调用Agent的功能,如显示角色:     BOOL agentShow()     {         HRESULT hRes;         long lRequestID;         if( !g_pMyAgent)             return FALSE;         hRes = g_pMyAgent->Show(FALSE, &lRequestID);         if (FAILED(hRes))             return FALSE;         return TRUE;     }     (二) 角色的语言处理   目前Agent支持很多种语言,不仅是显示,还有语音合成和语音识辨(对于中文,目前仅支持显示)。 语言又分为主语言和子语言(或为副语言),如中文的主语言为中文(LANG_CHINESE),子语言则可为 简体(SUBLANG_CHINESE_SIMPLIFIED)和繁体等。AgentShell中定义两个全局变量表达角色的语种:   主语言:DWORD g_nMainLang。   子语言:DWORD g_nSubLang。   这样程序内必须根据当前语言的不同来显示不同的信息,如程序退出时的问候信:   首先定义不同的语言信息,可以为宏定义或资源数据:   #define MES_GOODBYEL"Goodbye!"   #define MES_GOODBYE_CH L"再见!"   #define MES_GOODNIGHTL"Good night!"   #define MES_GOODNIGHT_CH L"祝您晚安!"   以下为实现退出提示代码:   void Goodbye()   {         if( g_bAgentOK)         {           SYSTEMTIME time;           agentStop();           agentShow();           agentPlay(L"Wave");           GetLocalTime(&time);           // 根据时间不同提示不同信息           if( g_nMainLang == LANG_ENGLISH)           {                 // 提示英文信息                 if( time.wHour < 19)                   agentSpeak(MES_GOODBYE);                 else                   agentSpeak(MES_GOODNIGHT);           }           else           {                 // 提示中文信息                 if( time.wHour < 19)                   agentSpeak(MES_GOODBYE_CH);                 else                   agentSpeak(MES_GOODNIGHT_CH);           }           agentHide();           // 等待若干时间           Sleep(MAX_QUIT_TIME);         }   }   当然以上介绍的只是一种较为简单的方法,仅在于描述这种原理。     (三) 实现自动朗读英文   实现自动朗读实际上是响应剪贴板消息的过程,当复制选种的文本信息时,系统自动发送WM_DRAWCLIPBOARD 消息给所有剪贴板监视队列中的窗口,相应的窗口只要读取当前剪贴板内的信息进行朗读即可,具体实现如下:   安装剪贴板监视:   void InstallClipSpy()   {         g_hNextWnd = SetClipboardViewer(g_hMainWnd);   }     主窗口的回调函数中相应剪贴板消息:   LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)   {     // 剪贴板窗口队列发生变化     case WM_CHANGECBCHAIN:     hwndRemove = (HWND)wParam; // handle of window being removed     hwndNext = (HWND) lParam;     if( hwndRemove == g_hNextWnd)     {         g_hNextWnd = hwndNext;     }     if( g_hNextWnd)     {         SendMessage(hwndNext, WM_CHANGECBCHAIN, wParam, lParam);     }     // 剪贴数据发生变化     case WM_DRAWCLIPBOARD:   // 是否自动阅读   if( g_bEnableRead)   {         // 阅读剪贴板信息         ReadClipText();   }   if( g_hNextWnd)   {         SendMessage(g_hNextWnd,WM_DRAWCLIPBOARD,wParam, lParam);   }   获取剪贴板信息并且朗读:   void ReadClipText()   {         if( g_bAgentOK)         {           // 只有文本文件才朗读           if( IsClipboardFormatAvailable(CF_TEXT))           {                 if (OpenClipboard(g_hMainWnd))                 {                   LPWSTR pwsz;                   UINT cch;                   HGLOBAL hglb;                   LPSTR lpstr;                   hglb = GetClipboardData(CF_TEXT);                   lpstr = (LPSTR)GlobalLock(hglb);                   cch = lstrlen(lpstr);                   if( cch > 0)                   {                     pwsz = new WCHAR[cch + 1];                     MultiByteToWideChar(CP_ACP, 0, lpstr, -1, pwsz, cch);                     pwsz[cch] = '/0';                     agentSaveState();                     agentPlay(L"Read");                     agentSpeak(pwsz);                     agentPlay(L"ReadReturn");                     agentRestoreState();                     delete pwsz;                   }                   GlobalUnlock(hglb);                   CloseClipboard();                 }           }         }   }   最后还须在程序退出时将当前窗口句柄从剪贴板监视队列移走:   void RemoveClipSpy()   {         ChangeClipboardChain(g_hMainWnd, g_hNextWnd);   }     (四) 与外部程序的接口     应用程序和AgentShell之间传递数据主要通过WM_COPYDATA消息实现,由于传递的数据类型各 有不同,所以需要定义一个数据结构来描述:     struct AgentActionSTRUCT     {         WORD nAction;         DWORD nD1;         DWORD nD2;         WCHAR sData[MAX_DATA_LEN];     };     nAction用来表示Agent应该执行的操作,如显示、表演等。nD1,nD2,sData用来记录传递的数据。 传递消息必须获取AgentShell主窗口的句柄,实现如下:     HWND GetAgentMainWnd()     {         return FindWindow(AGENT_CLASS_NAME, NULL);     }     由于Agent采用了UNICODE, 必须将ANSI字符转化为UNICODE字符:     BOOL SendMesToAgent(WORD nAction, DWORD nD1, DWORD nD2, LPCSTR sData)     {         UINT nSize;         HWND hWnd = GetAgentMainWnd();         if( hWnd)         {         action.nAction = nAction;         action.nD1 = (DWORD)nD1;         action.nD2 = (DWORD)nD2;         // 将ANSI符转换为UNICODE的字符         nSize = MultiByteToWideChar(CP_ACP, 0, sData, lstrlen(sData) + 1,         action.sData, MAX_DATA_LEN);         action.sData[nSize] = '/0';         //         COPYDATASTRUCT cds;         cds.dwData = (DWORD)0;         cds.cbData = (DWORD)sizeof(action);         cds.lpData = (VOID *)&action;         // 通过WM_COPYDATA消息与AgentShell交换数据         SendMessage(hWnd, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);         return TRUE;         }         return FALSE;     }     目前AgentShell提供的函数主要有:     // 启动角色外壳程序(AgentShell) bRun是否执行     BOOL agentAPIRun(BOOL bRun = TRUE);     // 退出角色外壳程序(AgentShell)     BOOL agentAPIExit();     // 创建一个新角色(sPath角色数据文件路径, nLang主语言, nSubLang子语言)     BOOL agentAPICreate(LPCSTR sPath, UINT nLang, UINT nSubLang);     // 设置角色名字(sName角色名字)     BOOL agentAPISetName(LPCSTR sName);     // 将角色卸载     BOOL agentAPIUnload();     // 显示角色     BOOL agentAPIShow();     // 隐藏角色     BOOL agentAPIHide();     // 显示或隐藏角色     BOOL agentAPIShowORHide();     // 停止角色表演     BOOL agentAPIStop();     // 角色表演(sAction动作名称)     BOOL agentAPIPlay(LPCSTR sAction);     // 角色讲话(sText句子)     BOOL agentAPISpeak(LPCSTR sText);     // 角色鞠躬(x,y 指方向)     BOOL agentAPIGesAt(WORD x, WORD y);     // 移动角色到指定的位置(x,y移动的坐标)     BOOL agentAPIMoveTo(WORD x, WORD y);     // 保存当前角色显示状态     BOOL agentAPISaveState();     // 恢复角色的状态     BOOL agentAPIRestoreState();     // 允许自动阅读     BOOL agentAPIEnableAutoRead();     // 禁止自动阅读     BOOL agentAPIDisableAutoRead();     注意传递给AgentShell的数据长度不要超过1K(实际上一般不会大于1K)。     2) 使用接口     有了以上介绍的接口函数,对Agent的控制变的很简单,以下是一个简单的问候示例:     // 启动AgentShell     if( agentAPIRun(TRUE))     {       // 保存当前Agent的状态       agentAPISaveState();       // 开始表演       agentAPIPlay(_T("Greet"));       // 讲话       agentAPISpeak(_T("hello, my friend."));       // 表演结束       agentAPIPlay(_T("GreetReturn"));       // 恢复原来状态       agentAPIRestoreState();       // 退出       agentAPIExit();     }     AgentShell在笔者的免费软件"我的助手"中得到很好的利用,当然目前其仅涉及了Agent的一小部分内容,还有如语音识辨等,未做处理,还有待一步改进。以上程序在Visual C++ 6.0编译通过,源代码可到助手之家(http://www.helperHome.com)下载。

    最新回复(0)