65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (12投票s)

2004年2月25日

5分钟阅读

viewsIcon

77287

downloadIcon

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 接口:ISpTTSEngineISpObjectWithToken。新代码将如下所示:

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,还理解它的一些框架。

历史

  • 这是最新版本。
© . All rights reserved.