Pregunta

I use the following code for Text-To-Speech application controls for blind persons in C++ Builder (most likely similar example can be used in Delphi). Main form has KeyPreview property checked to enable key F11 preview to start speaking active (focused) control. The code as it is works but there are some problems. This example is in C++ Builder code but from what I've found, Delphi suffers from same problem and the solution I found is the same. If you have Delphi solution, feel free to post it, it is similar anyway.

#include <sapi.h>
#include <WTypes.h>

//---------------------------------------------------------------------------
// Speak text string (synchronous function)
//---------------------------------------------------------------------------

bool SpeakText(UnicodeString Text)
{
ISpVoice* pVoice = NULL;

if (FAILED(::CoInitialize(NULL))) return false;

Word Saved8087CW = Default8087CW;                                               // Disable floating point division by zero exception caused by Speak
Set8087CW(0x133f);

HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if (SUCCEEDED(hr))
    {
    //pVoice->SpeakCompleteEvent()
    //pVoice->SetSyncSpeakTimeout(1000);
    hr = pVoice->Speak(WideString(Text).c_bstr(), SPF_DEFAULT, NULL);
    pVoice->Release();
    pVoice = NULL;
    }

Set8087CW(Saved8087CW);

::CoUninitialize();
return true;
}

//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
{
UnicodeString Speaker;

if (Key == VK_F11)
    {
    if      (Screen->ActiveControl->InheritsFrom(__classid(TButton)))   { Speaker += "Button, "   + static_cast<TButton*>(Screen->ActiveControl)->Caption + "."; }
    else if (Screen->ActiveControl->InheritsFrom(__classid(TEdit)))     { Speaker += "Edit box, " + static_cast<TEdit*>(Screen->ActiveControl)->Text      + "."; }
    }

if (Speaker != "") SpeakText(Speaker);
}
//---------------------------------------------------------------------------

Problems:

  1. pVoice->Speak causes Floating point division by zero if I don't override the exception using the Set8087CW function. This happens only on Windows 7 (possibly Vista and Windows 8 too) but not on Windows XP in the same program (compiled exe). Is there a solution without using Set8087CW? Removing these lines will cause the problem and exception. I have BCB2010.

  2. Function is synchronous and won't shut up or return control to program until it finishes reading text. This is a problem for longer text. It also blocks program events. Is there a way to make it asynchronous or introduce an event to periodically check for F11 key status and if F11 is pressed again it stops reading and uninitializes object? For example poll every 300 ms (or after each word etc.) for key-press F11 and if pressed, stop speaking? Or run it threaded?

  3. Does SAPI has memory leaks as some write on various sites?

  4. Can above code use OleCheck instead of CoCreateInstance and CoUninitialize?

UPDATE for those looking for solution as suggested by Remy Lebeau:

SavedCW = Get8087CW();
Set8087CW(SavedCW | 0x4);
hr = pVoice->Speak(WideString(Text).c_bstr(), SPF_DEFAULT | SPF_ASYNC, NULL);
pVoice->WaitUntilDone(-1); // Waits until text is done... if F11 is pressed simply go out of scope and speech will stop
Set8087CW(SavedCW);

Also found detailed example in CodeRage 4 session: http://cc.embarcadero.com/item/27264

¿Fue útil?

Solución

  1. The error does occur in Vista as well. Masking floating point exceptions is the only solution.

  2. To make Speak() run asynchronously, you need to include the SPF_ASYNC flag when calling it. If you need to detect when asynchronous speaking is finished, you can use ISpVoice::WaitUntilDone(), or call ISpVoice::SpeakCompleteEvent() and pass the returned HANDLE to one of the WaitFor...() family of functions, like WaitForSingleObject().

  3. What kind of leaks do other sites talk about?

  4. Not instead of, no. OleCheck() merely checks the value of an HRESULT value and throws an exception if it is an error value. You still have to call COM functions that return the actual HRESULT values in the first place. If anything, OleCheck() would be a replacement for SUCCEEDED() instead.

For what you are attempting, I would suggest the following approach instead:

struct s8087CW
{
    Word Saved8087CW;

    s8087CW(Word NewCW)
    {
        Saved8087CW = Default8087CW;
        Set8087CW(NewCW);
        // alternatively, the VCL documentation says to use SetExceptionMask() instead of Set8087CW() directly...
    }

    ~s8087CW()
    {
        Set8087CW(Saved8087CW);
    }
};

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent *Owner)
    : TForm(Owner)
{
    ::CoInitialize(NULL);
}

//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
    if (pVoice) pVoice->Release();
    ::CoUninitialize();
}

//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
{
    if (Key == VK_F11)
    {
        TWinControl *Ctrl = Screen->ActiveControl;
        if (Ctrl)
        {
            TButton *btn;
            TEdit *edit;

            if ((btn = dynamic_cast<TButton*>(Ctrl)) != NULL)
                SpeakText("Button, " + btn->Caption);

            else if ((edit = dynamic_cast<TEdit*>(Ctrl)) != NULL)
                SpeakText("Edit box, " + edit->Text);
        }
    }
}

//---------------------------------------------------------------------------
ISpVoice* pVoice = NULL;

bool __fastcall TForm1::SpeakText(const String &Text)
{
    s8087CW cw(0x133f);

    if (!pVoice)
    {
        if (FAILED(CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice)))
            return false;
    }

    SPVOICESTATUS stat;
    pVoice->GetStatus(&stat, NULL);
    while (stat.dwRunningState == SPRS_IS_SPEAKING)
    {
        ULONG skipped;
        pVoice->Skip(L"SENTENCE", 1000, &skipped);
        pVoice->GetStatus(&stat, NULL);
    }

    return SUCCEEDED(pVoice->Speak(WideString(Text).c_bstr(), SPF_ASYNC, NULL));
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top