3

我在 C++ Builder 中将以下代码用于盲人的 Text-To-Speech 应用程序控件(很可能在 Delphi 中可以使用类似的示例)。主窗体已KeyPreview检查属性以启用键 F11 预览以开始说话活动(聚焦)控件。代码原样有效,但存在一些问题。此示例在 C++ Builder 代码中,但根据我的发现,Delphi 遇到了同样的问题,我找到的解决方案是相同的。如果您有 Delphi 解决方案,请随时发布,反正它是相似的。

#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);
}
//---------------------------------------------------------------------------

问题:

  1. 如果我不使用该函数覆盖异常, pVoice->Speak 会导致浮点除以零。Set8087CW这只发生在 Windows 7(也可能是 Vista 和 Windows 8)上,但不会在同一程序(编译的 exe)中的 Windows XP 上发生。有没有不使用的解决方案Set8087CW?删除这些行将导致问题和异常。我有BCB2010。

  2. 函数是同步的,在完成读取文本之前不会关闭或将控制权返回给程序。这是较长文本的问题。它还阻止程序事件。有没有办法让它异步或引入一个事件来定期检查 F11 键状态,如果再次按下 F11,它会停止读取并取消初始化对象?例如,每 300 毫秒(或每个单词后等)轮询一次按键 F11,如果按下,停止说话?还是线程运行?

  3. SAPI 是否有一些在各个站点上写入的内存泄漏?

  4. 上面的代码可以用andOleCheck代替吗?CoCreateInstanceCoUninitialize

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);

还可以在 CodeRage 4 会话中找到详细示例:http: //cc.embarcadero.com/item/27264

4

1 回答 1

4
  1. Vista中也确实发生了该错误。屏蔽浮点异常是唯一的解决方案。

  2. 要使Speak()异步运行,您需要SPF_ASYNC在调用它时包含该标志。如果您需要检测异步说话何时结束,您可以使用ISpVoice::WaitUntilDone(), 或调用ISpVoice::SpeakCompleteEvent()并将返回的函数传递HANDLEWaitFor...()函数族之一,例如WaitForSingleObject().

  3. 其他网站谈论什么样的泄漏?

  4. 不代替,不。 OleCheck()只检查一个值的HRESULT值,如果它是错误值则抛出异常。您仍然必须首先调用返回实际HRESULT值的 COM 函数。如果有的话,OleCheck()将成为替代品SUCCEEDED()

对于您正在尝试的内容,我建议采用以下方法:

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));
}
于 2013-03-06T23:04:02.853 回答