Microsoft Agent技术应用

  • 来源: 互联网 作者: 若水   2008-03-17/17:33
  • 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)#p#分页标题#e#
              {
                    // 提示英文信息
                    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;
                      }#p#分页标题#e#
                      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"));#p#分页标题#e#
          // 恢复原来状态
          agentAPIRestoreState();
          // 退出
          agentAPIExit();
        }

        AgentShell在笔者的免费软件"我的助手"中得到很好的利用,当然目前其仅涉及了Agent的一小部分内容,还有如语音识辨等,未做处理,还有待一步改进。以上程序在Visual C++ 6.0编译通过,源代码可到助手之家(http://www.helperHome.com)下载。



    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多