创建符合 Microsoft SAPI 标准的应用程序






4.53/5 (12投票s)
2004年2月25日
5分钟阅读

77287

1616
本文将介绍如何创建符合 Microsoft SAPI 标准的应用程序或将 TTS 集成到 SAPI 中。
引言和动机
本文旨在解释和演示什么是 SAPI 兼容应用程序。本示例还说明了将 TTS 最少地集成到 SAPI 的方法。
背景
如果您想将 TTS 插件连接到 Microsoft 的 SAPI,那么您可以通过创建基于 ActiveX/COM 的 SAPI 兼容应用程序框架轻松实现。任何符合 SAPI 的应用程序都意味着任何使用 SAPI 的应用程序都可以通过与 SAPI 相同的代码调用您的应用程序,只需更改某些函数的参数即可。
在这里,我向您展示一个最小化的 SAPI 兼容应用程序,可用于将您的应用程序与 SAPI 集成。这是通过在 SAPI 接口的成员函数(您的自定义接口是从中派生的)中进行重写(用 C++ 的术语来说)来实现的。
使用代码
在这里,我将指导您创建 SAPI 兼容应用程序。首先,您需要使用 MSVC++ 6.0 的 ATL COM AppWizard 创建一个 ActiveX 组件(您可以自由使用 .NET 版本,但我尚未在该版本上进行测试)。
要在 VC++ 6.0 下创建 ActiveX 组件,请按照以下步骤操作:
1. 使用 ATL COM AppWizard 创建一个 ActiveX DLL。
2. 在组件中添加一个 ATL 对象,并将其命名为 SAPIObj。
3. 向导将生成多个文件,包括 .idl、.c、.rc 和 .cpp 文件。
更改生成的 IDL 文件
ATL COM AppWizard 生成的 IDL 代码如下所示。当然,UUID 在您的情况下会有所不同。
import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(29B9CEB1-5C49-11D8-8333-5254AB2226C5), dual, helpstring("ISAPIObj Interface"), pointer_default(unique) ] interface ISAPIObj : IDispatch { }; [ uuid(779D02D1-5AC3-11D8-832D-5254AB2226C5), version(1.0), helpstring("SAPIComp 1.0 Type Library") ] library SAPICOMPLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(779D02E0-5AC3-11D8-832D-5254AB2226C5), helpstring("SAPIObj Class") ] coclass SAPIObj { [default] interface ISAPIObj; }; };
由于我们需要实现 SAPI 接口,因此我们不需要在 IDL 文件中定义自己的接口。所以我们可以做的是删除我们的自定义接口和类工厂(coclass),并将 SAPI 接口作为默认接口而不是自定义接口。我们可以通过导入 sapi 附带的 "sapiddk.idl" 文件来获取 SAPI 接口定义。对于可以重载 speak 函数的最小 SAPI 兼容应用程序,我们需要两个 SAPI 接口:ISpTTSEngine
和 ISpObjectWithToken
。新代码将如下所示:
import "oaidl.idl"; import "ocidl.idl"; import "sapiddk.idl"; [ uuid(779D02D1-5AC3-11D8-832D-5254AB2226C5), version(1.0), helpstring("SAPIComp 1.0 Type Library") ] library SAPICOMPLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(779D02E0-5AC3-11D8-832D-5254AB2226C5), helpstring("SAPIObj Class") ] coclass SAPIObj { [default] interface ISpTTSEngine; interface ISpObjectWithToken; }; };
更改生成的类头文件
现在是时候编写我们自己的实现了。由于我们已经删除了 ATL COM AppWizard 生成的自定义接口,因此我们也必须从类公共派生以及 `BEGIN_COM_MAP` 和 `END_COM_MAP` 中删除对此特定接口的任何引用。
生成的 *SAPIObj.h* 头文件将如下所示:
class ATL_NO_VTABLE CSAPIObj : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CSAPIObj, &CLSID_SAPIObj>, public IDispatchImpl<ISAPIObj, &IID_ISAPIObj, &LIBID_ABCLib> { public: CSAPIObj() { } DECLARE_REGISTRY_RESOURCEID(IDR_SAPIOBJ) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CSAPIObj) COM_INTERFACE_ENTRY(ISAPIObj) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() // ISAPIObj public: };
由于我们没有自定义接口,因此我们必须删除 `ISpTTSEngine` 和 `ISpObjectWithToken` 的公共派生列表中的行 `public IDispatchImpl<ISAPIObj, &IID_ISAPIObj, &LIBID_ABCLib>` 以及 `ISAPIObj` 和 `IDispatch` 的 `COM_INTERFACE_ENTRY`,并在派生列表中添加 `public ISpTTSEngine, public ISpObjectWithToken`,并在 `COM_INTERFACE_ENTRY` 中添加 `ISpTTSEngine` 和 `ISpObjectWithToken`。
更改后的代码将如下所示:
class ATL_NO_VTABLE CSAPIObj : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CSAPIObj, &CLSID_SAPIObj>, public ISpTTSEngine, public ISpObjectWithToken { public: CSAPIObj() { } DECLARE_REGISTRY_RESOURCEID(IDR_SAPIOBJ) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CSAPIObj) COM_INTERFACE_ENTRY(ISpTTSEngine) COM_INTERFACE_ENTRY(ISpObjectWithToken) END_COM_MAP() public: };
现在,我们真正需要实现的是当 SAPI 选择我们的应用程序或 TTS 时必须调用的方法。因此,我们必须编写 `ISpTTSEngine` 和 `ISpObjectWithToken` 接口函数的定义。头文件和函数定义将如下所示:
class ATL_NO_VTABLE CSAPIObj : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CSAPIObj, &CLSID_SAPIObj>, public ISpTTSEngine, public ISpObjectWithToken { public: CSAPIObj() { } DECLARE_REGISTRY_RESOURCEID(IDR_SAPIOBJ) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CSAPIObj) COM_INTERFACE_ENTRY(ISpTTSEngine) COM_INTERFACE_ENTRY(ISpObjectWithToken) END_COM_MAP() STDMETHODIMP SetObjectToken( ISpObjectToken * pToken ) { return S_OK; } STDMETHODIMP GetObjectToken( ISpObjectToken ** ppToken ) { return S_OK; } // ISpTTSEngine STDMETHOD(Speak)( DWORD dwSpeakFlags, REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx, const SPVTEXTFRAG* pTextFragList, ISpTTSEngineSite* pOutputSite ) { MessageBox ( 0 , "This Is me......." , "Msg" , 0 ); return S_OK; } STDMETHOD(GetOutputFormat)( const GUID * pTargetFormatId, const WAVEFORMATEX * pTargetWaveFormatEx, GUID * pDesiredFormatId, WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx ) { return S_OK; } public: };
现在您已完成。您刚刚创建了一个重载 SAPI speak 函数的应用程序,当使用 TTSApp 等 SAPI 示例应用程序时,它将调用此 speak 函数,其中只包含一个消息框。
好吧,我刚才说一切都完成了,是这样吗?在某种程度上是,在某种程度上不是。是的,因为您创建了一个兼容的应用程序,不是,因为 SAPI 框架不知道您的应用程序,所以它将如何调用此应用程序?
生成 SAPI 注册表项
“不是”的答案存在于注册表中。SAPI 在注册表中有一个特定的路径,其中包含语音和相应的应用程序 CLSID。注册表的路径是 `HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Speech\\Voices\\Tokens`。如果您的系统上安装了 SAPI,并且您查看了此特定注册表项,您将能够看到系统中存在的各种语音。为了让 SAPI 了解您的应用程序,您必须在 `voices` 中有一个条目,其中包含我们刚刚创建的应用程序的 CLSID。这使得 SAPI 能够列出应用程序特定的语音,并在 SAPI 框架选择您的语音时调用应用程序特定的重写函数。
向注册表中添加条目很简单,因为每个 ActiveX/COM 对象都会自行注册到注册表中,并在取消注册 ActiveX/COM 组件时调用 `DLLRegisterServer` 和 `DLLUnregisterServer`。这些函数也是由 ATL COM AppWizard 生成的,存在于向导生成的 .cpp 文件中。原始生成的函数将如下所示:
////////////////////////////////////////////////////////////////// // DLLRegisterServer - Adds entries to the system registry STDAPI DLLRegisterServer(void) { #ifdef _MERGE_PROXYSTUB HRESULT hRes = PrxDLLRegisterServer(); if (FAILED(hRes)) return hRes; #endif // registers object, typelib and all interfaces in typelib return _Module.RegisterServer(TRUE); } ////////////////////////////////////////////////////////////////// // DLLUnregisterServer - Removes entries from the system registry STDAPI DLLUnregisterServer(void) { #ifdef _MERGE_PROXYSTUB PrxDLLUnregisterServer(); #endif return _Module.UnregisterServer(TRUE); }
我们将利用这两个函数,正如我们将要做的那样。一旦组件注册自己,我们将在注册表中添加一个关于自定义语音的条目,并在组件取消注册自己后从注册表中删除该条目。更改后的代码将如下所示:
//////////////////////////////////////////////////////////////// // DLLRegisterServer - Adds entries to the system registry STDAPI DLLRegisterServer(void) { #ifdef _MERGE_PROXYSTUB HRESULT hRes = PrxDLLRegisterServer(); if (FAILED(hRes)) return hRes; #endif // Make Entry in the Registry for The Voice HKEY lKey; DWORD LocalDisp = 0; // Try to Open long lResult = RegCreateKeyEx ( HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Speech\\Voices\\Tokens\\MyTTSOption", 0, "MyTTSOption" , REG_OPTION_NON_VOLATILE , KEY_ALL_ACCESS ,0 ,&lKey , &LocalDisp ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing Custom SAPI" , "Error Message.." , 0 ); return E_FAIL; } char LocalData [ 256 ]; memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "MySampleTTS" ); lResult = RegSetValueEx ( lKey , "409", NULL ,REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing SAPI Application. " "Either SAPI is not installed on your system" " or you do not have" " suffecient rights to do so. Please contact your " "system Administrator " , "Error Message.." , 0 ); return E_FAIL; } memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "{779D02E0-5AC3-11D8-832D-5254AB2226C5}" ); lResult = RegSetValueEx ( lKey , "CLSID", NULL , REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing Custom SAPI" , "Error Message.." , 0 ); return E_FAIL; } memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "MySample_Voice_Data" ); lResult = RegSetValueEx ( lKey , "VoiceData", NULL ,REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing SAPI Application." " Either SAPI is not installed on your system or you" " do not have suffecient rights to do so. Please contact" " your system Administrator " , "Error Message.." , 0 ); return E_FAIL; } // Close the Key RegCloseKey ( lKey ); LocalDisp = 0; // Now Open Attributes lResult = RegCreateKeyEx ( HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Speech\\Voices\\Tokens\\MyTTSOption\\Attributes", 0, "Attributes" , REG_OPTION_NON_VOLATILE ,KEY_ALL_ACCESS , 0 ,&lKey , &LocalDisp ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing SAPI Application. " "Either SAPI is not installed on your" " system or you do not have " "suffecient rights to do so. Please contact your system" " Administrator " , "Error Message.." , 0 ); return E_FAIL; } memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "Adult" ); lResult = RegSetValueEx ( lKey , "Age", NULL ,REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing SAPI Application. " "Either SAPI is not installed on your system or you do not" " have suffecient rights to do so. Please contact your" " system Administrator " , "Error Message.." , 0 ); return E_FAIL; } memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "Male" ); lResult = RegSetValueEx ( lKey , "Gender", NULL ,REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing SAPI Application." " Either SAPI is not installed on your system or you do not " "have suffecient rights to do so. Please contact your " "system Administrator " , "Error Message.." , 0 ); return E_FAIL; } memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "409;9" ); lResult = RegSetValueEx ( lKey , "Language", NULL ,REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing SAPI Application. " "Either SAPI is not installed on your system or you do not" " have suffecient rights to do so. Please contact your" " system Administrator " , "Error Message.." , 0 ); return E_FAIL; } memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "MyTTSOption" ); lResult = RegSetValueEx ( lKey , "Name", NULL ,REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0, " Error Installing SAPI Application. Either SAPI is" " not installed on your system or" " you do not have suffecient rights" " to do so. Please contact your system Administrator " , "Error Message.." , 0 ); return E_FAIL; } memset ( LocalData , 0 , 256 ); strcpy ( LocalData , "General...." ); lResult = RegSetValueEx ( lKey , "Vendor", NULL ,REG_SZ, ( LPBYTE )LocalData , strlen ( LocalData ) ); if ( lResult != ERROR_SUCCESS ) { MessageBox ( 0," Error Installing SAPI Application." " Either SAPI is not installed on your system or you do " "not have suffecient rights to do so. Please contact your " "system Administrator " , "Error Message.." , 0 ); return E_FAIL; } // Close the Key RegCloseKey ( lKey ); // registers object, typelib and all interfaces in typelib return _Module.RegisterServer(TRUE); } //////////////////////////////////////////////////////////////////// // DLLUnregisterServer - Removes entries from the system registry STDAPI DLLUnregisterServer(void) { HKEY lKey; long lResult = RegOpenKeyEx ( HKEY_LOCAL_MACHINE , "Software\\Microsoft\\Speech\\Voices\\Tokens\\MyTTSOption", 0 , KEY_ALL_ACCESS , &lKey ); if ( lResult != ERROR_SUCCESS ) MessageBox ( 0 ,"Unable to DE Install Application properly, " "you may have to remove some entries manually ", "Error DeInstall" , 0 ); lResult = RegDeleteKey ( lKey , "Attributes" ); if ( lResult != ERROR_SUCCESS ) MessageBox ( 0 ,"Unable to DE Install Application properly," " you may have to remove some entries manually ", "Error DeInstall" , 0 ); lResult = RegCloseKey ( lKey ); lResult = RegOpenKey ( HKEY_LOCAL_MACHINE , "Software\\Microsoft\\Speech\\Voices\\Tokens", &lKey ); if ( lResult != ERROR_SUCCESS ) MessageBox ( 0 ,"Unable to DE Install Application " "properly, you may have to remove some entries manually ", "Error DeInstall" , 0 ); lResult = RegDeleteKey ( lKey , "MyTTSOption" ); if ( lResult != ERROR_SUCCESS ) MessageBox ( 0 ,"Unable to DE Install Application properly," " you may have to remove some entries manually ", "Error DeInstall" , 0 ); lResult = RegCloseKey ( lKey ); #ifdef _MERGE_PROXYSTUB PrxDLLUnregisterServer(); #endif return _Module.UnregisterServer(TRUE); }
现在您已全部完成。您的 SAPI 兼容应用程序已准备就绪。编译并注册此组件,然后测试应用程序。您可以使用 SAPI 提供的 TTSApp 示例应用程序来测试此应用程序。
关注点
最初,我非常努力地尝试创建 SAPI 兼容应用程序,甚至通过使用基于 MFC 的应用程序成功做到了这一点,但这真的很简单,并且将帮助人们不仅理解 SAPI,还理解它的一些框架。
历史
- 这是最新版本。