Microsoft Agent具有相當廣泛的用途,我們既可以把它加入到普通應用程式中供本地系統使用,也可以把它嵌入到HTML文檔中供Internet/Intranet使用。Microsoft Agent支援C/C++、Visual Basic、Java、JScript和VBScript等多種程式設計語言,並為程式員提供了OLEAutomation 伺服程式和ActiveX控制項兩種編程方法,從本質上講,這兩種編程方法都屬於OLE技術的範疇,都建立在COM (Component Object Model,元件物件模型)的基礎之上。利用VC++的MFC類庫或VB等支援ActiveX的編程工具可以很容易地調用ActiveX控制項,但是ActiveX控制項把許多OLE技術的細節都隱藏起來了,如果我們想加深對一個COM對象的瞭解的話,則應直接使用它的COM介面來編程,從這一點出發,本文將介紹Microsoft Agent的COM介面的基本編程方法,希望能夠起到拋磚引玉的作用。
OLE編程的基礎知識
早期的OLE(現稱為OLE 1)首次出現在Windows 3.1中,其主要用途是產生複合型文檔,使得一個應用程式的文檔可以通過鏈連或嵌入的方式包含其它應用程式的資料(對象)。隨著軟體組件技術變得日益重要起來,Microsoft在OLE 1的基礎上設計了OLE 2,利用它可以實現二進位級上可重用的軟體組件,並且控制這些組件的版本和擴充其功能變得相當容易。由於OLE 2的體繫結構被設計成為開放式的、可擴充的,所以以後不會再出現OLE 3或4。經過多年的發展,如今的OLE已經包括了OLE自動化、COM、COM+、DCOM和ActiveX等多項技術,它們是ActiveDirectory(將用於NT 5.0的一項關鍵技術)、OLE Messaging、DirectX 、Active Controls、ActiveX Scripting和Task Scheduler等等多種新技術的基礎,OLE已不再是Object Linking and Embedding的縮寫,它變成了一個獨立的單詞,專門用來表示Microsoft的軟體組件整合技術。
COM是OLE技術的基礎,它規定了對象之間如何相互連訊,符合COM規範的對象也叫做COM對象。按照COM的規定,對象內部可以使用任何語言來編寫,它們通過介面(Interface)來與外界通訊。所謂介面是指對象提供的一組特定的功能調用(方法),每個對象可以有多個介面,不同的對象可以實現同一種介面,客戶程式通過對象的介面指標來調用對象的功能。由於OLE規定了組件在二進位級上可重用,客戶程式不能夠直接存取對象內部的資料,讀取或設定對象的屬性也都要通過介面來進行。每一種介面都是從一個叫IUnknown介面繼承而來,都必須重新實現IUnknown的三個方法:QueryInterface、AddRef和Release,客戶程式調用QueryInterface可以獲得對象的其它介面指標,AddRef和Release分別將對象的引用計數加一和減一,當引用計數為零時,對象就會被釋放。客戶程式調用COM對象的一般步驟是首先建立一個對象,然後擷取需要的介面指標,調用相應的功能,最後釋放介面指標和對象。
C++程式調用Microsoft Agent的基本方法
根據前面介紹的基礎知識,下面我們來看看C++程式中如何調用Microsoft Agent。
1.設定與選項
本文使用的編程工具為Visual C++ 5.0,程式為一般的Win32應用程式,為了使程式能夠正確地編譯串連和運行,您首先需要擁有AgtSvr.h和AgtSvr—i.c兩個定義Microsoft Agent的COM介面的檔案,它們可以在Microsoft的MS Agent網站(http://www.microsoft.com/workshop/
prog/agent/)上找到,或者請下載Microsoft最新的Internet Client SDK或Platform SDK,其次,請在Project/Settings/Link菜單中加入以下的庫:ole32.lib、oleaut32.lib、uuid.lib、odbc32.lib odbccp32.lib,最後要確保系統中安裝有Microsoft Agent及動畫人物資料。
2.建立Microsoft Agent對象
建立OLE對象之前需要初始化OLE,這由OleInitialize()函數來完成,如果OLE初始化不成功,那麼就無法繼續執行後面的代碼,建立對象由CoCreateInstance()函數來完成:
if (FAILED(OleInitialize(NULL))) return -1;//初始化OLE
hRes = CoCreateInstance(CLSID—AgentServer,NULL,CLSCTX—SERVER,IID—IAgent,(LPVOID *)&pAgent);//建立Microsoft Agent Server的執行個體
if (FAILED(hRes)) return -1;
CoCreateInstance()的第一個參數是對象的CLSID(類代碼),Microsoft Agent Server的CLSID為定義在AgtSvr—i.c檔案中的CLSID—AgentServer,這個128位的編碼唯一地標識了Agent伺服器,伺服器所在路徑和運行參數等資訊都放在系統註冊表中;第二個參數一般情況下設為NULL;第三個參數用來指明對象的運行環境,如遠程或本地,此處設為CLSCTX—SERVER;第四個參數指明用來與對象通訊的介面的ID,這也是一個128位的編碼,Agent的介面ID為IID—IAgent;第五個參數是用來接收IAgent的介面指標。
如果Microsoft Agent Server還沒有在記憶體中運行,那麼CoCreateInstance()會啟動它並建立一個Agent對象,如果伺服器已經運行了,則CoCreateInstance()會與之串連並建立一個Agent對象。當所有的Agent對象都被釋放了後,伺服器自動結束。
3.裝入動畫人物資料
下面的代碼調用IAgent::Load()方法來裝入一個動畫人物的資料,由於Agent伺服器在自己的記憶體空間中運行,所以傳送的字串變數需要用SysAllocString()來分配記憶體:
VariantInit(&vPath); //初始化OLE變數
vPath.vt = VT—BSTR; //指明變數類型為Unicode的字串
vPath.bstrVal=SysAllocString(kpwszCharacter);
//kpwszCharacter為動畫人物資料的存放路徑
hRes = pAgent->Load(vPath,&lCharID,&lRequestID);
//裝入資料,人物ID在lCharID中返回
hRes = pAgent->GetCharacter(lCharID,&pdCharacter);
//擷取lCharID的IDispatch介面指標調用IDispatch::QueryInterface()方法可以得到 //IAgentCharacter的介面指標:
hRes = pdCharacter->QueryInterface(IID—IAgentCharacter, (LPVOID *)&pCharacter);
pdCharacter->Release(); //釋放IDispath
通過IAgentCharacter介面就可以調用動畫人物支援的各種方法了:
hRes = pCharacter->Show(FALSE, &lRequestID);//顯示動畫人物
hRes = pCharacter->MoveTo(320,240,100,&lRequestID); //移動動畫人物到螢幕中央
bszSpeak = SysAllocString(L"Hello World!"); //分配字串
hRes = pCharacter->Speak(bszSpeak, NULL, &lRequestID); //讓動畫人物說話
SysFreeString(bszSpeak); //釋放字串所佔記憶體
4.釋放對象
程式在退出之前需要把建立的Agent對象釋放:
if (pCharacter) {
pCharacter->Release(); //釋放IAgentCharacter介面
pAgent->Unload(lCharID); //卸載動畫人物資料
}
pAgent->Release(); //釋放Agent對象
VariantClear(&vPath); //清除OLE變數
進一步的編程要點
前面介紹的是調用Microsoft Agent伺服器最基本的一些步驟,為了完成比較實際的任務,客戶程式還應根據自己的情況考慮下面的一些編程要點。
1.檢查Agent Server的版本
OLE要求組件或對象具有向後相容性,高版本對象支援低版本對象的所有介面和屬性,這樣可以很方便地進行組件升級。客戶程式通常應檢查對象的版本,只有系統中安裝的對象的版本號碼高於或等於所期望的版本號碼時才能調用對象。下面的IsValidAgentVersion()函數檢查Microsoft Agent的版本號碼,並將它與定義在AgtSvr.h檔案中的版本號碼相比較:
BOOL IsValidAgentVersion(IAgent *pAgent) {
IDispatch *pdAgent = NULL;
ITypeInfo *pTypeInfo = NULL;
ITypeLib *pTypeLib = NULL;
TLIBATTR *pTypeLibAttr = NULL;
BOOL bValid = FALSE;
UINT uiIndex;
pAgent->QueryInterface(IID—IDispatch, (LPVOID *)&pdAgent);
pdAgent->GetTypeInfo(0, 0, &pTypeInfo); //取得類型資訊
pTypeInfo->GetContainingTypeLib(&pTypeLib, &uiIndex);//取得類型庫
pTypeLib->GetLibAttr(&pTypeLibAttr); //取得類型庫中的屬性
if ((pTypeLibAttr->wMajorVerNum > AGENT—VERSION—MAJOR) ¦¦((pTypeLibAttr->wMajorVerNum == AGENT—VERSION—MAJOR) &&(pTypeLibAttr->wMinorVerNum >= AGENT—VERSION—MINOR)))
bValid = TRUE; //期望的版本號碼定義在AgtSvr.h檔案中
if (pTypeLib) {
if (pTypeLibAttr) pTypeLib->ReleaseTLibAttr(pTypeLibAttr);
pTypeLib->Release(); }
if (pTypeInfo) pTypeInfo->Release();
if (pdAgent) pdAgent->Release();
return bValid;}
2.實現IAgentNotifySink介面
為了能夠處理使用者的輸入,瞭解Agent對象的狀態,客戶程式應實現IAgentNotifySink介面來接收Agent對象的事件。IAgentNotifySink的聲明和預設實現可以在Platform SDK或Internet Clinet SDK中的Notify.h和Notify.cpp中找到,客戶程式應根據需要修改某些事件的處理函數
。下面的代碼向Agent對象註冊IAgentNotifySink介面,其中AgentNotifySink是從IAgentNotifySink繼承而來:
pSink = new AgentNotifySink;
pSink->AddRef(); //增加引用計數
hRes = pAgent->Register((IUnknown *)pSink, &lNotifySinkID);//進行註冊
...
if (pSink) {
pAgent->Unregister(lNotifySinkID); //登出IAgentNotifySink介面
pSink->Release(); }
客戶程式最感興趣的兩個事件是RequestComplete和Command。Agent伺服器採用非同步方式來處理客戶程式的各種請求,這樣客戶程式可以在請求服務的同時進行自己的工作,當伺服器完成一項請求時就會激發RequestComplete事件,客戶程式可以判斷是哪一項請求已經結束,並做相應的處理。Command事件是當使用者使用滑鼠或麥克風向動畫人物發出命令時激發的,客戶程式可以通過IAgentUserInput介面來瞭解命令的具體資訊。
3.自訂命令
Agent伺服器為每個動畫人物都提供了一些預設的命令,這些命令出現在關聯菜單或命令視窗中,客戶程式可以通過IAgentCommands介面添加自訂命令。
為了得到IAgentCommands的介面指標,應使用參數IID—IAgentCommands來調用IAgentCharacter::QueryInterface(),IAgentCommands的Add()或Insert()方法可以加入自訂命令,同時設定Caption、Visible和Voice屬性,指明該命令是否顯示出來,顯示在關聯菜單中還是在命令視窗中。
Agent伺服器為每個命令賦予一個ID值,客戶程式可以使用這個ID值調用IAgentCommands::GetCommand()方法,得到每個命令的IAgentCommand介面指標,從而對單個命令的各種屬性進行調整。
4. WAV檔案代替語音合成
Microsoft Agent目前只支援英語的語音合成功能,要輸出中文時只能用WAV檔案來代替。如果給IAgentCharacter::Speak()方法的第二個參數傳遞一個WAV檔案的路徑,那麼Agent伺服器自動播放這個WAV檔案,並在文字氣球中顯示出第一個參數中包含的文字,如果給第二個參數傳遞一個帶音節資訊的LWV檔案的路徑,則不需要在第一個參數中提供文字,因為LWV檔案中包含有文字資訊。當使用LWV檔案時,動畫人物的嘴部動作能與輸出的語音保持一致,所以在可能的情況應該盡量使用LWV檔案,這種格式的檔案可用Microsoft Agent Linguistic Information Sound Editing Tool編輯WAV檔案來產生。
5.其它一些COM介面
除了前面提到的介面外,Agent伺服器還有其它一些COM介面。IAgentCommandWindow允許客戶程式訪問或設定命令視窗的屬性,包括位置、大小和是否可見。IAgentSpeechInputProperties允許客戶程式訪問語音輸入功能的屬性,其中大部分屬性都是唯讀。IAgentAudioOutputProperties允許客戶程式讀取語音輸出功能的部分屬性。IAg
entPropertySheet允許客戶程式訪問或設定Agent伺服器的屬性工作表。IAgentBalloon允許客戶程式訪問文字氣球的屬性,可以設定少數屬性,如是否可見和字型名稱。關於這些介面的具體定義和用途請參考Microsoft Agent的協助文檔。
總 結
Microsoft Agent是一項較新的技術,它屬於OLE的範疇,涉及較深的編程理論,本文所介紹的只是從OLE自動化服務角度出發的最基本的使用方法,感興趣的讀者可以參考Microsoft Press出版的《Inside OLE》(第二版)一書來進一步瞭解OLE編程的知識,並參加Microsoft的Agent新聞討論群組(新聞伺服器為msnews.microsoft.com,討論群組為microsoft.public.msagent)。