0

I have example for SpeechAPI 5.4 in Delphi XE2. Here's part of it:

try
  SpVoice:= TSpVoice.Create(nil);
  SOTokens := SpVoice1.GetVoices('', '');
  for i := 0 to SOTokens.Count - 1 do begin
    SOToken := SOTokens.Item(I);
    SOToken._AddRef;
    s:=SOToken.GetDescription(0); // raise exception
    Log(IntToStr(i)+': '+s);
  end;
except
  on E : Exception do begin
    Log(E.ClassName+' error raised, with message : '+E.Message);
  end;
end;

This code works correct on all Windows from XP till 8.1. Unfortunately, Windows 10 raise exception. In log file I can read:

0: Microsoft David Desktop - English (United States)
EOleException error raised, with message : OLE error 8004503A

Standard Windows Text-to-speech tool shows me 2 available voices:

Microsoft David Desktop - English (United States)
Microsoft Zira Desktop - English (United States)

It's important create TSpVoice dynamically.

How can I work with all voices without exceptions?

0xFF
  • 585
  • 1
  • 6
  • 22
  • I have worked with SAPI in Windows 7. Haven't worked with it in Windows 10 tho. `0x8004503a` means `SPERR_NOT_FOUND`: `The requested data item (data key, value, etc.) was not found.` Can you try this: http://nextup.com/forum/viewtopic.php?t=6641 - Does this work on Windows 10? – Mario Werner Apr 07 '16 at 08:30
  • what updates did you install to Delphi ? can you work this with Update pack 3 ? That is not the same but in Delphi XE2 Update 4 they broken some COM parts and made MS Office interaction broken until a patch is applied. Maybe (just maybe) that could affect you too. https://github.com/the-Arioch/XE2fixes – Arioch 'The Apr 07 '16 at 10:53
  • 1
    Updated, line where ``SOToken.GetDescription(0)`` called – 0xFF Apr 07 '16 at 10:58
  • @Arioch'The I use ``Embarcadero RAD Studio XE2 with Update 4 Hotfix 1 x32`` compiler. Unfortunately I can't use Update 3 because we have big developer group and it's not to easy to change compilers for all. Tanks for link, I'll try to use it. – 0xFF Apr 07 '16 at 11:15
  • what interface is `SOToken` ? Does it have something like `.Count` or `.Length` or `.IsEmpty` ? What if there really is not a single element within some containers, not even the zeroth one ? Can you put a guard before `GetDescription` - something like `if SOToken is not empty then SOToken.GetDescription(0)` ? – Arioch 'The Apr 07 '16 at 11:20
  • @Arioch'The Yes, it have. And it's not empty, not nil because I have 2 voices in system and I can get info about first of them, you can check it in log. – 0xFF Apr 07 '16 at 12:24
  • @0xFF Did you try the link in my comment? – Mario Werner Apr 07 '16 at 12:30
  • @MarioWerner Still no. Unfortunately I have no direct access now at those PC, so I can try it today's evening or tomorrow – 0xFF Apr 07 '16 at 13:09
  • @0xFF `And it's not empty, not nil because I have 2 voices in system ` - that proves nothing. System can give you one SOToken with two voices inside, or 10 SOToken's of which 8 would be empty and two would contain one voice each. At least that is so until proven otherwise. So I reinstate my suggestion to check each and every obtained SOToken how many items it has inside. You should realise - all your assumptions like `And it's not empty, nor nil` are already failed. If your assumptions were true - you would not have exceptions. You do have exceptions. That means your assumptions are not valid. – Arioch 'The Apr 07 '16 at 13:10
  • `SOToken._AddRef;` - why do you do it? Where do you call `SOToken._Release;` in the loop ? – Arioch 'The Apr 07 '16 at 13:11
  • @Arioch'The It's only part of code. I call ``SOToken._Release;`` when I stop to use voice components. – 0xFF Apr 07 '16 at 13:58
  • Delphi automatically calls both AddRef and Release. When you manually call that it is a code smell, it usually means that either the code or Delphi was broken and that error could not be fixed so the ugly hack-around with explicit AddRef calls was introduced. That is why i asked. – Arioch 'The Apr 07 '16 at 14:06
  • When calling explicitly for example you have to ensure the variable never changes its value nor goes out of lifetime scope between calls to AddRef and Release. That is not only tedious but error-prone as well.... – Arioch 'The Apr 07 '16 at 14:08
  • @Arioch'The I tried without ``SOToken._Release;`` and I had the same result. – 0xFF Apr 07 '16 at 14:20
  • By removing Release you introduce memory leak and what not. In good code there would be neither explicit AddRef nor explicit Release. There should be an exotic and compelling reason to ever call them manually. Normally Delphi calls them automagically and you do not touch those methods ever. – Arioch 'The Apr 07 '16 at 15:03
  • @Arioch'The Thank you for advice, I'll remove both of them. – 0xFF Apr 07 '16 at 15:12
  • Do a simple test - in the end of the loop add SOToken := nil;, then put a breakpoint there. When it would stop here go View, Debug Windows, CPU View - if the type of SOToken is declared correctly you would see Delphi compiler inserted the call to Release itself. You would also easily spot difference between compiled interface types of variable and interpreted Variant/OLEVariant types, the latter would call a complex intermediate RTL procedure before at last calling Release – Arioch 'The Apr 07 '16 at 16:20
  • In your quoted code it would be a good idea to nil the SOToken variable right after the loop, so it would let the object go as soon as you exited the loop and no more need it. I once was personally hit by the code that did not cleared interface variable inside a long function. The function later tried to modify the resource being modified by the object held by that interface variable until the end of the function (and the local variable lifetime). With some versions of interface providing libraries it worked, but with others function was corrupting data due to race conditions. – Arioch 'The Apr 07 '16 at 16:26
  • @Arioch'The Now I use SpVoice only in one method so it's a local variable. But thank you for advice. Unfortunately my customer has some problems with his laptop and I can't test proposed variants yet. Can you give me some instruction how to use your bugfix for XE2? – 0xFF Apr 08 '16 at 09:00
  • @0xff I do not quite remember, just look into it - either both units have two public functions to patch RTL / undo patching, or they do it automatically from initialization/finalization as soon as you use those units somewhere – Arioch 'The Apr 08 '16 at 10:34
  • @0xff just be warned. In my case it also was a single function, it just was long enough to do more than trivial things and make conflict with forgotten variable's object... praemonitus praemunitus – Arioch 'The Apr 08 '16 at 10:36
  • a bit of nit-picking - there is no such thing as x32, there is x86 and x64 :-) – Arioch 'The Apr 08 '16 at 15:02
  • @Arioch'The I looked at ``SOToken``, it's an interface ``ISpeechObjectToken``: http://www.daisy.org/projects/pipeline/doc/api/transformers/se_tpb_speechgen2/external/win/sapi5/ISpeechObjectToken.html So, it has no any ``count`` and ``size`` methods. It's only one Token per one voice. Unfortunately I didn't find documentation for it at MSDN, only this: https://msdn.microsoft.com/en-us/library/ms722610(v=vs.85).aspx – 0xFF Apr 10 '16 at 18:23
  • What is Delphi declaration of SpObjectToken interface? In the example the GetDescription method takes no parameters. https://msdn.microsoft.com/en-us/library/ms722596.aspx - in MS Office VBA vs Delphi that usually means there is `LCID` requested locale param implicit in VBA and explicit in compiled languages. And there was a bug in Excel that cells (iRange type) only executed some method in one specific locale.... And i do not think 0 is correct value, maybe SYSTEM_LOCALE or DEFAULT_LOCALE to be tried... Find and read Delphi declaration of that datatype! – Arioch 'The Apr 10 '16 at 18:51
  • Your Java page also claims there goes locale id - so read MSDN about Windows locales and about how to construct valid LCID – Arioch 'The Apr 10 '16 at 18:58
  • Your MSDN link claims locale should be optional though.... But something obviously is going differently than estimated and planned.... https://msdn.microsoft.com/en-us/library/ms722602.aspx or maybe there indeed is registry corruption. Win10 installer sucks... – Arioch 'The Apr 10 '16 at 19:01
  • @MarioWerner Unfortunately your proposition didn't help. I think I should try new compiler, maybe it's really Delphi bug for new Windows versions. – 0xFF Apr 11 '16 at 11:10
  • @Arioch'The The ``0`` is default value, so it's correct code. – 0xFF Apr 11 '16 at 11:13
  • You can not try xe2u3 but u can try post-xe2? Like i said, all the assumptions failed, if there is an exception. Personally i see no documentation on MSDN about how the call would react to unexpected LCID. So i suggest you to try two defaults above and an American English locale too. There ma be all kinds of problems, in Delphi, Windows, voice module, Windows installation instance, whatever.... – Arioch 'The Apr 11 '16 at 12:49
  • @Arioch'The it's only part of truth, but I think it's dirty solution try to guess locale for each voice. I'll try XE2u3, but if XE6 or higher solve my problem I'll have more benefits from XE6. On the other hand I'll have some problems with migration to higher version of compiler. think I can get a conclusion only after testing both of the variants. – 0xFF Apr 11 '16 at 13:14
  • You might also make a DLL in xe6, FPC or even C++ to query the list of voices, and call that DLL from your main app. Or you can try to utilize Windows Scripting Host and query voices via VBScript or JScript – Arioch 'The Apr 11 '16 at 13:23
  • @Arioch'The I've got results of small test app from test computer. Unfortunately not XE2u3 nor Delphi 10 Seattle didn't fix this error. I had the same error message: ``EOleException error raised, with message : OLE error 8004503A`` – 0xFF Apr 13 '16 at 21:44
  • I say it again - from my experience with Excel COM - try the three LCIDs I mentioned above. BTW, today Windows 10 foregone massive platform update – Arioch 'The Apr 14 '16 at 09:48

0 Answers0