Delphi应用调用新版SAPI语音(如Microsoft Mark)的问题求助
解决方案:Delphi枚举OneCore语音失败问题修复
问题根源分析
你的代码存在三个核心问题:
- 硬编码注册表路径:直接使用注册表路径而非SAPI预定义的类别GUID,导致兼容性问题
- 未处理32/64位注册表重定向:32位程序在64位Windows下默认访问
Wow6432Node下的注册表项,而OneCore语音的配置不在该路径中 - 未正确释放内存:
Token.GetId返回的PWideChar未调用CoTaskMemFree释放,存在内存泄漏风险
修正后的枚举代码
procedure TMainForm.EnumerateVoices; const // SAPI预定义的语音类别GUID(OneCore语音也归属于此类别) SPCAT_VOICES = '{4EEC79F4-7423-48EB-9889-079F74F64754}'; // 强制访问64位注册表(适配32位程序在64位系统运行) SPOBJTOKENCAT_FLAG_64BIT = $00000002; var Category: ISpObjectTokenCategory; EnumTokens: IEnumSpObjectTokens; Token: ISpObjectToken; TokenCount: Cardinal; TokenID: PWideChar; EnumResult: HResult; DisplayName: WideString; begin try ListBox.Clear; Category := CoSpObjectTokenCategory.Create; // 设置语音类别,指定访问64位注册表解决重定向问题 if Succeeded(Category.SetId(SPCAT_VOICES, SPOBJTOKENCAT_FLAG_64BIT)) then begin if Succeeded(Category.EnumTokens(nil, nil, EnumTokens)) then begin EnumResult := EnumTokens.Next(1, Token, TokenCount); while EnumResult = S_OK do begin if Succeeded(Token.GetId(TokenID)) then begin // 获取语音友好名称(如"Microsoft Mark") if Succeeded(Token.GetStringValue('SpokenName', DisplayName)) then ListBox.Items.Add(Format('%s (%s)', [DisplayName, TokenID])) else ListBox.Items.Add(TokenID); // 释放GetId返回的内存 CoTaskMemFree(TokenID); end; Token := nil; EnumResult := EnumTokens.Next(1, Token, TokenCount); end; end else ShowMessage('Category.EnumTokens failed'); end else ShowMessage('Category.SetId failed'); ShowMessage(Format('EnumerateVoices found %d voices', [ListBox.Count])); except on E: Exception do ShowMessage(Format('EnumerateVoices failed. Error: %s', [E.Message])); end; end;
关键修复点说明
- 使用官方类别GUID:
SPCAT_VOICES是SAPI标准的语音类别标识符,OneCore语音也注册在此类别下,避免硬编码路径带来的版本兼容性问题 - 注册表重定向处理:
SPOBJTOKENCAT_FLAG_64BIT强制组件访问64位注册表路径,解决32位程序无法读取OneCore语音配置的问题;如果是64位程序,可以移除该标志 - 内存管理:调用
CoTaskMemFree释放Token.GetId返回的字符串指针,符合COM组件的内存管理规则 - 友好名称显示:通过
Token.GetStringValue('SpokenName')获取用户可见的语音名称,提升体验
额外注意事项
- 确保生成的
SpeechLib_TLB.pas是从C:\Windows\System32\Speech_OneCore\common\sapi_onecore.dll正确生成,且适配程序位数(32位程序需用32位版本的TLibImp.exe) - 程序运行时需具备读取HKLM注册表项的权限(普通用户权限即可满足)
- 若仍无法枚举,可尝试在
EnumTokens的第二个参数传入语言筛选(如'Language=409'表示英语),缩小枚举范围
内容的提问来源于stack exchange,提问作者Brad Kilmer
相关产品推荐
相关产品推荐

