Microsoft Speech SDK提供关于语音(Speech)处理的一套应用程序编程接口SAPI(Speech Application Programming Interface)。SAPI提供了实现文字-语音转换(Text-to-Speech)和语音识别(Speech Recognition)程序的基本函数,大大简化了语音编程的难度,降低了语音编程的工作量。Speech SDK可以免费从如下网址下载:www.microsoft/speech。
由于Speech SDK是以COM接口的方式提供服务的,所以首先介绍COM的有关基础知识。
11.1.1 COM基础
Speech SDK提供了完善的COM接口,所以具备一定的COM编程基础对进行Speech SDK编程来说是非常必要的。笔者将简要介绍COM编程的基础知识。虽然这些知识对阅读本书来说是足够了,但是如果你没有进行过任何的COM编程实践,笔者还是建议你先阅读一本COM的教科书。
1.什么是COM
组件对象模型(Component Object Model,COM)对象是符合COM规范的可重用的软件组
件。符合COM规范的COM对象相互之间可以很好地工作,并且可以很容易地集成到应用程序中。从应用的观点来看,一个COM对象就是一个黑箱,应用程序可以使用它来创建一项或多项任务。
COM对象常常用动态链接库(Dynamic Link Libraries,DLLs)的形式来实现。与传统的DLL一样,COM对象暴露其方法,应用程序能调用这些方法来实现对象所支持的功能。应用程序与COM对象的关系就像应用程序与C++对象的关系,但其中也存在一些区别。
1)COM对象执行严格的封装。你不能简单地创建一个对象就调用其中的公用方法。COM对象的公用方法聚合为一个或多个接口。为了使用一个方法,必须先创建一个对象,并从对象中获得相应的接口。一个接口一般包含一组方法,通过它们能使用对象的特定功能。不能通过接口来调用不属于该接口的方法。
2) 创建COM对象的方法与创建C++对象的方法不同。有多种方法可以创建COM对象,但所有的方法都需要使用COM的细节技术。Speech SDK应用程序编程接口(API)包括许多的帮助函数和方法,它们简化了创建大部分Speech对象的工作。
3) 必须使用COM的细节技术来控制对象的生命期。
4) COM对象不需要明确地装载。COM对象一般包含在DLL中。然而,与使用普通DLL中的方法不同,使用COM对象时,不需要明确地装载DLL或链接静态库。每一个COM对象都具有一个惟一的注册标识。用该标识来创建对象时,COM将自动地装载正确的DLL。
5)COM是一种二进制规范。COM对象可用许多种编程语言来编写和调用。对于使用者来说,并不需要了解对象的源程序的任何信息。比如,Microsoft Visual Basic编写的应用程序可以很好地调用C++编写的COM对象。
下面先介绍COM的几个基本概念,包括对象与接口、GUID、HRESULT类型和指针地址等。
(1)对象与接口
理解对象与接口的区别是非常重要的。通常用其主要接口的名称来称呼对象。但是,严格地说,这两个术语是不能互换使用的。
一个对象能暴露任何数量的接口。例如,所有的对象都必须暴露IUnknown接口,它们一般还暴露至少一个其他的接口,它们也可能暴露更多的接口。为了使用一个特定的方法,首
先必须获得正确的接口指针。
多个不同的接口可以暴露同一个接口。一个接口就是一组执行特定操作的方法。接口定义只是指定了方法的调用语法和它们的一般功能。任何需要支持一组特定操作的COM对象都可通过暴露一个合适的接口来实现这些特定的操作。有些接口非常专业,仅仅由单一的对象来暴露。有些接口在许多场合下都非常有用,它们可由许多的对象来暴露。最极端的例子是IUnknown接口,所有的COM对象都需要暴露它。
如果一个对象暴露一个接口,它必须支持接口定义的每一个方法。但是,不同的对象实现一个特定方法的方式是不同的。不同的对象可能使用不同的算法来实现最后的结果。有时一个对象暴露一组通用的方法,但往往只需要支持其中的一部分方法。可是其他的未被支持的方法也需要能被成功调用,只是它们都只返回E_NOTIMPL。
COM标准要求接口一旦发布,其定义就不能再改变。例如,不能在一个已有的接口中增加一个新的方法,而必须创建一个新的接口。如果没有限制接口中必须有什么样的方法,通常的做法是在其下一代接口中包括原有接口的所有方法和其他的新方法。
但是一个接口有几代版本的情况并不常见。通常所有的版本实现完全相同的功能,只是在具体实现细节上不同。一个对象经常暴露所有版本的接口。这样做使较老版本的应用程序能继续使用对象的较老版本的接口,而较新版本的应用程序能使用较新版本接口的强大功能。一般来说,同一家族的接口具有相同的名称,在名称后加一个整数来表示不同的版本。比如,原来的接口叫做IMyInterface,接下来的两代版本就分别叫做IMyInterface2和IMyInterface3。
(2)GUID
全球惟一标识符(Globally Unique Identifiers,GUIDs)是COM编程模型的关键部分。从本质上来说, GUID是一个128位的结构。然而,GUID在创建时必须保证不能出现两个相同的GUID。COM在如下的两个方面广泛地使用GUID:
1)惟一地标识一个特定的COM对象。赋予一个COM对象的GUID叫做一个类标识(class ID,CLSID)。当需要创建一个相关的COM对象的实例时,需要使用CLSID。
2)惟一地标识一个特定的COM接口。与一个COM接口相关的GUID 叫做接口标识(interf
ace ID,IID)。当从一个对象中请求一个特定接口时,需要使用IID。不管哪个对象暴露接口,该接口的IID都是相同的。
为了叙述方便,在文档中经常使用对象和接口的描述名称来引用对象和接口。虽然这样做并不会在文档中引起混乱,但是,严格地说,并不能保证不会有两个或多个不同的对象或接口具有相同的描述名。惟一的不能引起歧义的方法是用GUID 来引用对象或接口。
虽然GUID是一种结构,但却经常表示为对应的字符串。最常用的GUID字符串形式是“{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}”, 其中x 为一个十六进制整数。
由于实际的GUID很长,并且很容易写错,所以一般还提供一个等价的名称。在调用CoCreateInstance之类的函数时,可以使用这个名称而不使用实际的GUID结构。通常的命名惯例是分别在对象或接口的描述名称前加上CLSID_或IID_作为前缀。例如,ISpVoice接口的CLSID的名称是CLSID_ISpVoice。
(3)HRESULT类型值
所有的COM方法都返回一个32位的HRESULT类型的值。对大部分方法而言,HRESULT本
质上是一个包含独立的两部分信息的结构:
1)方法调用成功了还是失败了;
2)关于方法所支持操作的结果的更详细信息。
有些方法仅仅返回在Winerror.h中定义的标准的HRESULT类型值。然而,方法可以返回自己定义的HRESULT类型值,以提供更专用的信息。当然这样的自定义值一般会在方法的参考文档中说明。需要注意的是,参考文档可能不会列出标准的HRESULT类型值,但方法却可能返回标准值。
虽然HRESULT类型值经常用来返回错误信息,但不要将它们看成错误码。由于说明成功或失败的位在包含详细信息的数据中是分别存储的,所以HRESULT类型值可以包含任何数量的成功和失败代码。作为一种惯例,成功码的名称具有S_前缀,错误码的名称具有E_前缀。比如,最常用的成功码和错误码分别是S_OK和E_FAIL。
由于COM方法可能返回许多不同的成功码或错误码,因此必须很小心地测试HRESULT类型的值。例如,假设一个方法在文档中说明成功,返回S_OK,不成功则返回E_FAIL。这
时,请注意,该方法可能还会返回其他的成功码或错误码。下面的代码段说明了使用简单测试的危险。
// hr 是该方法返回的HRESULT类型值
if(hr == E_FAIL)
{
// 处理错误
}
else
{
// 处理成功
}
只有在该方法只返回E_FAIL 来指示失败时,上面的测试才能正常工作。然而,该方法还可能返回一个E_NOTIMPL或 E_INVALIDARG之类的错误值。上面的代码将会将它们解释为成功,这将可能导致程序的失败。
如果需要关于方法运行结果的更详细的信息,必须测试每一个相关的HRESULT值。但经常只关心方法是成功的还是失败的。一种可靠的测试HRESULT类型值说明成功还是失败的方法是利用如下的宏来判断,这些宏定义在Winerror.h中。
1)宏SUCCEEDED返回TRUE作为成功码,返回FALSE作为失败码;
2)宏FAILED返回TRUE作为失败码,返回FALSE作为成功码;
可以使用宏FAILED来修改上面的代码段:
// hr 是该方法返回的HRESULT类型值
if(FAILED(hr))
{
// 处理错误
}
sdkelse
{
// 处理成功
}
这段代码能合理地处理E_NOTIMPL和E_INVALIDARG之类的失败码。
大多数的COM方法返回结构化的HRESULT类型值,只有很少数量的方法使用HRESULT来返回简单的整数值,这类方法经常是成功的。如果将这类整数值传给宏SUCCESS,该宏将总是返回TRUE。常用的例子是IUnknown::Release方法,它减少一次对象的引用计数并返回当前的引用计数。将在管理对象的生命期一节中讨论引用计数的问
(4)指针地址
阅读一些COM方法的参考文档时,经常看到如下的说明:
HRESULT CreateDevice(
IDirect3DDevice8 **ppReturnedDeviceInterface
);
C或C++开发人员熟悉普通的指针,但是COM经常使用另外的间接层。这种间接的第二层用两个星号(**)跟着类型声明来表示。变量名一般使用“pp”作为前缀。在上面的例子中,参数ppReturnedDeviceInterface表示指向IDirect3DDevice8 接口的指针的地址。
与C++不同,不需要直接访问COM对象的方法,而必须获取指向方法的接口的指针。然后像调用指向C++方法的指针一样来调用方法。例如,使用如下的语法来调用方法IMyInterface::DoSomething method:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论