Windows の音声認識を C++ で(コマンド認識編)
2013年07月29日(月)19時11分
コマンド認識
前回に引き続き「Windows の音声認識を C++ で使う」というお話です(「音声認識」タグで当ブログの音声認識関連の記事が一覧できますので、目次代わりにご利用ください)。
今回は、あらかじめ単語や文章を登録しておき、そのどれに一番近いか?を識別する「コマンド認識(または「コマンド・アンド・コントロール」あるいは「指定文法」)」モードを使ってみました。
Microsoft Speech Platform のために
今回この「コマンド認識」モードを使うためにいろいろと調べた理由は、Microsoft Speech Platform(以下 MSSP)ではこれしか使えないからです。
つまり、MSSP には自由に話した内容を文字列化する「口述筆記」モードがない、ということです。
ですので、前回までのサンプルを MSSP で使おうとしても、エラーで動きません。
ということで、MSSP を試すのであれば、この「コマンド認識」モードを避けては通れないのです。
MSSP を使う場合
SAPI5 も MSSP も、使い方はほぼ、というより全く同じです。
違うのは「認識エンジン」「プロファイル」「入力デバイス」等の情報の格納場所(レジストリ)で、これを変更することで使い分けることができます。
具体的には、例えば「認識エンジン」であれば、SAPI5 の場合、その一覧は「HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Recognizers」を指定して取得しますが、ここを「HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech Server\\v11.0\\Recognizers」に変更すれば、MSSP の認識エンジン一覧を取得するようになります。
今回のサンプルでは、「認識エンジン」と「入力デバイス」の二か所に切り替え可能な個所があります(目印に「■」をつけてあります)。
MSSP 使用時の注意
ただし、上記の二か所を MSSP のものに変更しただけでは、実行時にアクセス違反で落ちます。
これはどうも、普通のビルドすると、システム標準の音声機能用である「sapi.dll」が呼び出されるようになることが原因のようです。
そして MSSP の認識エンジンはどうも、この標準の「sapi.dll」内には「存在しない何か」を参照するか使用するかしているために、エラーが出ているものと思われます。
ですので、MSSP 使用時には、MSSP 用の DLL である「mssps.dll」が呼び出されるようにする必要があります。
そのためには、MSSP の SDK に含まれる「sapi.lib」をプロジェクトに追加し、リンクさせます。
サンプル
その他細かいことは、ソースコード内にコメントとして直接書き込んであります。
コマンド(認識する文字列)を登録する方法として、あらかじめ XML ファイルとして書き出しておき、LoadCmdFromFile 関数で一気に読み込む方法と、ソースコード内で AddWordTransition 関数を使用して一文ずつ登録していく方法がありますが、今回のサンプルでは後者の方法を使っています。
そのため、サンプル実行のために XML ファイル等の別ファイルは必要ありません。
以下に SyntaxHighlighter によるソースコードを掲載します。
ソースコード部分でダブルクリックすると、全選択されますのでコピーしやすくなりますが、それとは別に TEXT ファイルとしてもアップロードしておきます(→「DictCPP02.txt」)。
//WindowsのSAPI5またはMicrosoft Speech PlatformとC++で音声認識を使用するサンプル。 // //■サンプルの動作内容は以下の通り。 //1.日本語用の認識エンジンを設定。 //2.音声入力に標準入力デバイスを設定。 //3.認識文を表示するコールバック関数を設定。 //4.文法としていくつかのルール(コマンド文字列)を登録する。 //5.音声入力をメッセージ・ループで待ち受け、コールバックで認識文を表示し続ける。 //6.「コマンド終了」という文字を含む文字列が来ればループを打ち切り終了。 // //■その他雑記。 //・開発環境は「Win7x64」と「VC++2010」。 //・「Win32 コンソール アプリケーション」。 //・Unicode文字セット(SAPI5の関数群はUnicodeを使用するためマルチバイト文字セットを使う場合は変換が必要になる)。 //・「sapi.h」をプロジェクトに含める必要がある。 //・サンプルであるためSAPI5関数群の戻り値hResultのチェックとエラー処理は省略している。 //・Microsoft Speech Platformを使用する場合はSDKに含まれるsapi.libをリンクする必要がある。 // //http://denspe.blog84.fc2.com/blog-entry-192.html //古い形式の関数の使用警告を表示しないプラグマ。 #pragma warning(disable:4996) //普通にビルドするとシステム標準の「sapi.dll」が呼び出されるようになるが、 //この「sapi.dll」でMicrosoft Speech Platform(以下MSSP)を使用すると、音声入力時にアクセス違反で落ちる。 //そのためMSSPを使用する場合は、MSSPのSDKに含まれる「sapi.lib」をリンクし、 //MSSP用のDLLである「mssps.dll」を呼び出すようにする必要がある。 //「sapi.lib」は、MSSPのSDKのインストール時にインストール先を変更していなければ //「C:\Program Files\Microsoft SDKs\Speech\v11.0\Lib\sapi.lib」または「C:\Program Files (x86)\~」にある。 //生成するバイナリが64ビット化32ビットかに応じて上記どちらかの「sapi.lib」を選択し、 //「プロジェクト→既存項目の追加」や、プロジェクトのプロパティの「追加の依存ファイル」、 //あるいは以下のようなpragmaを使用する等してプロジェクトに「sapi.lib」を追加する。 //#pragma comment(lib,"C:\\Program Files (x86)\\Microsoft SDKs\\Speech\\v11.0\\Lib\\sapi.lib") #include <stdio.h> #include <windows.h> #include <tchar.h> #include <locale.h> #include "sapi.h" //開始時にCoInitialize、終了時にCoUninitializeするだけのクラス。 //COMコンポーネントを使用するアプリケーションの最初で実体を確保しておくことで、終了時にCoUninitializeを不要にする。 //CComPtr等の自動解放系システムの使用時には、システムが各COMコンポーネントを自動解放した後に //CoUninitializeが実行されるようにしなければならないため、スコープを分ける必要がある。 class CAutoCoInitialize { public: HRESULT hResult; //コンストラクタ。 CAutoCoInitialize():hResult(S_FALSE){hResult=CoInitialize(NULL);} //デストラクタ。 ~CAutoCoInitialize(){CoUninitialize();hResult=S_FALSE;} } AutoCoInitialize; //COMオブジェクトの自動解放をカプセル化したもの。 //atlbase.hにあるCComQIPtrの劣化版。atlbase.hを使用しないため似たものを自作。 template <class T> class AutoCom { public: //実装していない機能は実体をそのまま使う。 T *pCom; //標準のコンストラクタ。 AutoCom(void):pCom(NULL){} //初期化用コンストラクタ(コピー・コンストラクタ呼び出し)。 AutoCom(T *pSrcCom):pCom(NULL){operator=(pSrcCom);} //デストラクタ。 ~AutoCom(void){if(pCom){pCom->Release();pCom=NULL;}} ////////////////////// //有効・無効の確認。// ////////////////////// bool IsAvailable(void){return pCom?true:false;} bool IsNull(void){return pCom?false:true;} /////////////////////////////////////////////////// //COMポインタと同等に扱えるようにするための定義。// /////////////////////////////////////////////////// operator T*(void){return pCom;} operator T**(void){return &pCom;} operator T&(void){return *pCom;} T& operator*(void){return *pCom;} T** operator&(void){if(pCom){pCom->Release();pCom=NULL;}return &pCom;} T* operator->(void){return pCom;} T* operator=(T *pSrcCom){if(pSrcCom==pCom)return pCom;if(pCom)pCom->Release();pCom=pSrcCom;return pCom;} //////////////////////////// //その他作業用関数の定義。// //////////////////////////// //COMオブジェクト生成。 HRESULT Create(GUID ClsID,GUID IID=__uuidof(T)) { if(pCom){pCom->Release();pCom=NULL;} HRESULT hResult=CoCreateInstance(ClsID,NULL,CLSCTX_ALL,IID,(LPVOID *)&pCom); if(FAILED(hResult))pCom=NULL; return hResult; } }; //認識イベント発生時に呼び出されるコールバック関数。 //コールバック設定時に第一引数wParamとして音声認識管理用オブジェクトISpRecoContextのアドレスが渡されるようにしておく。 void __stdcall NotifyCallback(WPARAM wParam,LPARAM lParam) { //音声認識管理用オブジェクトISpRecoContextの実体はメイン関数にあるため使用後に解放する必要はない。 ISpRecoContext *pRecoContext=(ISpRecoContext *)wParam; //メッセージループ終了の目印。 bool IsQuit=false; //イベントキューに蓄積されたイベントの処理。 SPEVENT SpEvent;memset(&SpEvent,0,sizeof(SPEVENT)); ULONG Fetched=0; //イベント発生毎に必ずコールバックされるのであれば、ここでは一つのイベントのみ処理すれば間に合うはずだが、 //確認が面倒だったため複数のイベントが存在する可能性があることを前提に取得不能になるまでループで処理している。 while(SUCCEEDED(pRecoContext->GetEvents(1,&SpEvent,&Fetched))&&Fetched) { switch(SpEvent.eEventId) { case SPEI_HYPOTHESIS://仮定イベント。 case SPEI_RECOGNITION://確定イベント。 //「SpEvent.elParamType==SPET_LPARAM_IS_OBJECT」のとき「SpEvent.lParam」に結果(ISpRecoResult)が入っている。 if(SpEvent.elParamType==SPET_LPARAM_IS_OBJECT) { //「SpEvent.lParam」を結果オブジェクトISpRecoResultとして扱う。 ISpRecoResult* pResult=(ISpRecoResult *)SpEvent.lParam; //ISpRecoResultのGetText関数で認識結果を文字列で取得できる。 WCHAR *pResultText=NULL; if(SUCCEEDED(pResult->GetText(SP_GETWHOLEPHRASE,SP_GETWHOLEPHRASE,TRUE,&pResultText,NULL))) { //ルール名やルールIDを知るためにフレーズ情報を取得。 SPPHRASE* pSpPhrase=NULL; if(SUCCEEDED(pResult->GetPhrase(&pSpPhrase))) { //ルール名・ルールID・認識文を表示。 //仮定段階では無印、確定した結果には「*」印を先頭に付与。 if(SpEvent.eEventId==SPEI_HYPOTHESIS) wprintf(L" [名:%s/ID:%lu]%s\n",pSpPhrase->Rule.pszName,pSpPhrase->Rule.ulId,pResultText); else wprintf(L"*[名:%s/ID:%lu]%s\n",pSpPhrase->Rule.pszName,pSpPhrase->Rule.ulId,pResultText); //取得したフレーズ情報領域は解放する必要があるが、領域内の各要素まで個別に解放する必要はない。 CoTaskMemFree(pSpPhrase);pSpPhrase=NULL; } //「終了」の文字が含まれればイベント処理ループ終了後に認識ループも終了。 if(wcsstr(pResultText,L"終了"))IsQuit=true; //取得した文字列は解放する必要がある。 CoTaskMemFree(pResultText);pResultText=NULL; } } break; default: break; } //イベント通知のためにSAPI5側が内部で動的確保した各種情報は受け取り側の責任で解放する必要がある。 //これは「sphelper.h」内で定義されているヘルパー関数SpClearEventで行えるが //「sphelper.h」はATLがない環境では使用できないため、必要な処理を手作業で移植する。 //以下「sphelper.h」のSpClearEvent関数の内容を移植。 if(SpEvent.elParamType!=SPEI_UNDEFINED) { if(SpEvent.elParamType==SPET_LPARAM_IS_POINTER||SpEvent.elParamType==SPET_LPARAM_IS_STRING) CoTaskMemFree((void *)SpEvent.lParam); else if(SpEvent.elParamType==SPET_LPARAM_IS_TOKEN||SpEvent.elParamType==SPET_LPARAM_IS_OBJECT) ((IUnknown*)SpEvent.lParam)->Release(); } memset(&SpEvent,0,sizeof(SPEVENT)); //以上「sphelper.h」のSpClearEvent関数の内容を移植。 } //認識結果に「終了」の文字が含まれていれば認識ループ終了。 //スレッドの隠しウインドウにWM_QUITを送ることでメイン関数にあるメッセージ・ループを打ち切る。 if(IsQuit)PostMessage(NULL,WM_QUIT,0,0); } //メイン関数。 int _tmain(int argc,TCHAR* argv[]) { //日本語の表示に必要な処理。 _tsetlocale(LC_ALL,_T("")); //SAPI5関連の戻り値を受ける。 //今回のサンプルではただ受けるだけで内容は無視しているが本来はエラー処理等を行う必要がある。 HRESULT hResult=S_OK; ///////////////////////// //ISpRecognizerの生成。// ///////////////////////// //ISpRecognizerは認識エンジンを管理するオブジェクト。 AutoCom<ISpRecognizer> acRecognizer; acRecognizer.Create(CLSID_SpInprocRecognizer); //////////////////////// //認識エンジンの設定。// //////////////////////// //認識エンジンの変更は、CreateRecoContext後にはできなくなるため、ISpRecognizer生成直後に行う必要がある。 //認識エンジン一覧を管理するオブジェクトの生成。 AutoCom<ISpObjectTokenCategory> acRecognizerCategory; acRecognizerCategory.Create(CLSID_SpObjectTokenCategory); //■認識エンジン一覧記録位置の設定(上はMSSP11で下がSAPI5)。 //hResult=acRecognizerCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech Server\\v11.0\\Recognizers",TRUE); hResult=acRecognizerCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Recognizers",TRUE); //認識エンジン一覧を格納するオブジェクト。 AutoCom<IEnumSpObjectTokens> acRecognizerTokens; //認識エンジン一覧の取得。 hResult=acRecognizerCategory->EnumTokens(NULL,NULL,&acRecognizerTokens); //認識エンジンの一覧を走査し日本語認識エンジンを探す。 while(true) { //特定の認識エンジンを格納するオブジェクト。 AutoCom<ISpObjectToken> acRecognizerToken; //以下で先頭から末尾までを走査できる。 if(FAILED(acRecognizerTokens->Next(1,&acRecognizerToken,NULL))||acRecognizerToken.IsNull())break; //言語IDの確認。 BOOL IsMatches=FALSE; //言語IDが411(日本語)の場合はその認識エンジンを日本語用として設定。 hResult=acRecognizerToken->MatchesAttributes(L"language=411",&IsMatches); if(IsMatches==TRUE)hResult=acRecognizer->SetRecognizer(acRecognizerToken); } //////////////////// //音声入力の設定。// //////////////////// //InProc(非共有)タイプの音声認識システムは標準では入力デバイスが未設定であるため、必ず何かを設定する必要がある。 //入力デバイス一覧を管理するオブジェクトの生成。 AutoCom<ISpObjectTokenCategory> acAudioInputCategory; acAudioInputCategory.Create(CLSID_SpObjectTokenCategory); //入力デバイス一覧の生成に成功すればそこから標準入力デバイスIDが取得できる。 if(acAudioInputCategory.IsAvailable()) { //■入力デバイス一覧記録位置の設定(上はMSSP11で下がSAPI5)。 //hResult=acAudioInputCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech Server\\v11.0\\AudioInput",TRUE); hResult=acAudioInputCategory->SetId(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\AudioInput",TRUE); //標準入力デバイスIDの取得。 WCHAR *pDefaultAudioInputId=NULL; if(FAILED(acAudioInputCategory->GetDefaultTokenId(&pDefaultAudioInputId)))pDefaultAudioInputId=NULL; //標準入力デバイスIDから標準入力デバイスを生成し設定。 if(pDefaultAudioInputId) { //標準入力デバイスを生成。 AutoCom<ISpObjectToken> acAudioInputToken; acAudioInputToken.Create(CLSID_SpObjectToken); acAudioInputToken->SetId(NULL,pDefaultAudioInputId,FALSE); //生成した標準入力デバイスを入力デバイスとして設定。 hResult=acRecognizer->SetInput(acAudioInputToken,TRUE); //取得した文字列は解放する必要がある。 CoTaskMemFree(pDefaultAudioInputId);pDefaultAudioInputId=NULL; } } //////////////////////////////// //ISpRecoContextの生成と設定。// //////////////////////////////// //ISpRecoContextは音声認識管理用オブジェクト。 //認識エンジン管理オブジェクトISpRecognizeや後述の文法管理オブジェクトISpRecoGrammarを統括し //音声認識利用者はこのISpRecoContextオブジェクトを通して認識結果を取得する。 //認識エンジン管理オブジェクトISpRecognizeと密接に結びついており、このオブジェクトが単体で存在することはない。 //このオブジェクトを直接生成した場合には内部で標準の認識エンジンを管理するISpRecognizeが生成される。 //また、一度このオブジェクトが生成されると、 //それが持つISpRecognizeが管理する認識エンジンやISpRecognize自体を差し替えることはできなくなるため、 //認識言語(エンジン)を変更する際にはこのオブジェクトも作り直す必要がある。 AutoCom<ISpRecoContext> acRecoContext; acRecognizer->CreateRecoContext(&acRecoContext); //コールバック関数の設定。 //呼び出された先で使えるようWPARAMとして自分自身のアドレスを渡すように設定。 hResult=acRecoContext->SetNotifyCallbackFunction(NotifyCallback,(WPARAM)acRecoContext.pCom,0); //どのイベントが発生した場合に通知させるかを設定。「SPEI_RECOGNITION」は認識完了時。 //「SPEI_HYPOTHESIS」は途中経過(認識中に何度かその時点で一番確率の高い仮定の結果が通知される)。 hResult=acRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION)|SPFEI(SPEI_HYPOTHESIS),SPFEI(SPEI_RECOGNITION)|SPFEI(SPEI_HYPOTHESIS)); //////////////////////////////// //ISpRecoGrammarの生成と設定。// //////////////////////////////// //ISpRecoGrammarは音声認識文法管理用オブジェクト。 //あらかじめ登録した単語(文章)のどれが一番近いか?を認識する「コマンド認識」モードの場合は //ここでこのオブジェクトに対してコマンドの登録などを行う。 //自由に話した内容を文字列化する「口述筆記」モード使用時には特にやることはない。 AutoCom<ISpRecoGrammar> acRecoGrammar; acRecoContext->CreateGrammar(NULL,&acRecoGrammar); //以下、認識ルールの構築(コマンド文の設定)。 SPSTATEHANDLE SpStateHandle; //GetRule関数でルールの取得または新設。 //第一引数(ルール名)と第二引数(ルールID)は不要であれば省略可能だがどちらか片方は必ず指定する必要がある。 //第四引数をTRUEにしておくと、既存のルール(名前・IDが一致するもの)がない場合に新設される。 acRecoGrammar->GetRule(NULL,1,SPRAF_TopLevel|SPRAF_Active,TRUE,&SpStateHandle); //上記で取得または新設したルールにAddWordTransition関数でコマンド文字列を追加。 //第四引数は単語(または文節)の区切り文字を指定する。 //英語などでは単語ごとに半角スペースで区切って書くため、一般的なサンプルなどではここで半角スペースが指定されている。 //しかし日本語では通常分かち書きを行わないため、当然全文が一つのフレーズとして扱われる。 //そのため「SPEI_HYPOTHESIS(仮定)」イベントにおいて初期段階から常に全文状態で通知される。 acRecoGrammar->AddWordTransition(SpStateHandle,NULL,L"全文が一つのフレーズとなる。",L" ",SPWT_LEXICAL,1,NULL); //日本語をあえて文節ごとに分かち書きすることで個別のフレーズとして認識させる。 //「SPEI_HYPOTHESIS(仮定)」イベントでは現時点で可能性の高いフレーズまでが通知される。 acRecoGrammar->GetRule(NULL,2,SPRAF_TopLevel|SPRAF_Active,TRUE,&SpStateHandle); acRecoGrammar->AddWordTransition(SpStateHandle,NULL,L"半角 スペースで フレーズを 分割 する。",L" ",SPWT_LEXICAL,1,NULL); acRecoGrammar->AddWordTransition(SpStateHandle,NULL,L"句読点。あるいは「カッコ・中黒」など、複数文字を、まとめて「区切り文字」として、指定することも、できる。",L"、。・「」",SPWT_LEXICAL,1,NULL); //ルール名とルールIDの両方で新設した場合、どちらか片方のみを指定すればそのルールを取得できる。 acRecoGrammar->GetRule(L"ルール3",3,SPRAF_TopLevel|SPRAF_Active,TRUE,&SpStateHandle); acRecoGrammar->AddWordTransition(SpStateHandle,NULL,L"ルール 3の 新設。",L" ",SPWT_LEXICAL,1,NULL); acRecoGrammar->GetRule(NULL,3,SPRAF_TopLevel|SPRAF_Active,TRUE,&SpStateHandle); acRecoGrammar->AddWordTransition(SpStateHandle,NULL,L"ルール 3を ルールIDで 取得。",L" ",SPWT_LEXICAL,1,NULL); acRecoGrammar->GetRule(L"ルール3",0,SPRAF_TopLevel|SPRAF_Active,TRUE,&SpStateHandle); acRecoGrammar->AddWordTransition(SpStateHandle,NULL,L"ルール 3を ルール名で 取得。",L" ",SPWT_LEXICAL,1,NULL); //ここで以下のような矛盾した取得は失敗する(「ID:3」には「名:ルール3」が設定済みであるにもかかわらず「名:三番目」で取得しようとした)。 //acRecoGrammar->GetRule(L"三番目",3,SPRAF_TopLevel|SPRAF_Active,TRUE,&SpStateHandle); //終了用のコマンド文字列を登録。 //誤認による予期せぬ一致終了が発生する可能性を抑えるため、単なる「終了」ではなく「コマンド 終了」と少し長くしてある。 //また「こ」と言っただけで「仮定」イベントに「コマンド終了」が通知されて終了してまうことがないよう、 //「コマンド」と「終了」を半角スペースでフレーズ分割してある。 acRecoGrammar->GetRule(NULL,MAXDWORD,SPRAF_TopLevel|SPRAF_Active,TRUE,&SpStateHandle); acRecoGrammar->AddWordTransition(SpStateHandle,NULL,L"コマンド 終了。",L" ",SPWT_LEXICAL,1,NULL); //上記登録を反映させる。 acRecoGrammar->Commit(0); //読み込んだ辞書の活動を開始させる。これは実質的には認識処理開始の合図となる。 hResult=acRecoGrammar->SetRuleIdState(0,SPRS_ACTIVE); ////////////////////// //メッセージの処理。// ////////////////////// //SAPI5のコールバックは内部に生成した隠しウインドウによって実現されているため //ウインドウ・メッセージを消化する必要がある(スリープ関数などで待ち受けていても呼び出されることがない)。 MSG msg; BOOL bRet; while((bRet=GetMessage(&msg,NULL,0,0))!=0) { if(bRet==-1)break; TranslateMessage(&msg); DispatchMessage(&msg); } ////////// //終了。// ////////// //認識結果に「終了」の文字が含まれていればメッセージ・ループが打ち切られてここに来る。 return EXIT_SUCCESS; }
- 関連記事
コメントの投稿
エラー処理について
私は、msspを使ってシステムを構築しようとしています。
このブログのサンプルプログラムは大変参考になりました。
さて、このサンプルプログラムでは、エラー処理を省略してありますが、なぜなのでしょうか。
このサンプルプログラムに、FormatMessage関数を使って、エラー処理を書いてみましたが、
うまくいきませんでした。
なにか特別な関数を書かなければいけないのでしょうか。
それとも、COMポインタを自作したことで、FormatMessage関数が使えなくなっているのでしょうか。
教えてください。
Re: エラー処理について
まず、エラー処理を省略している理由についてですが、これはサンプルであるため、要点がぼやけないよう
できるだけ必要なことに絞って記述するという方針によってこのようになっています。
FormatMessage 関数のエラーについてはお役に立てなくて申し訳ありませんが「わからない」というのが正直なところです。
なんとなく COM のスマートポインタの自作はあまり関係ないのではと思ったりはするのですが、
それとは別に、COM のスマートポインタの自作については、(この後に書きますが)二つの意味で必要のないものでした。
まず全般的な話として、この記事の内容は今でも使えますが、使う理由が大きく薄れているということがあります。
というのも、この記事を書いた背景として、
・当時個人で無償で使用できる Microsoft の C++ 環境である VC++Express には ATL が付属しなかった。
・SpeechSDK に含まれるヘルパー関数群「sphelper.h」は ATL が必要だった。
という状況があります。
そのうえで、Microsoft が公開している SpeechSDK の使用方法のほとんどは sphelper.h を前提としたものだったため、
個人で(無償で)SpeechSDK を使うユーザーにとっては参考にしづらいものだったのです。
それで ATL と sphelper.h を使わない方法について書くことにしました。
ですが現在個人で無償利用可能な VC++Community には ATL が付属しています。
なので sphelper.h も使えますし、当記事での例のような CComPtr の代替品を自作する必要もありません。
※ただし検索してみたところ sphelper.h に関しては文法が古いため若干修正しないとエラーになるようです。
というようなことから、今 VC++Community で開発するのであれば「ATL + sphelper.h」の環境で作ったほうが、
Microsoft の公開するヘルプファイル等の公式な情報や、豊富なサンプル群を参考にできるためよいのではないかと思います。
また、それはそれとして(何かしらの事情により)「ATL なし」の状況で書かないといけないとしても、
実は ATL のない状況で使える COM のスマートポインターとして Microsoft は
「_com_ptr_t」を提供しています(少し新しめの環境であれば「Microsoft::WRL::ComPtr<>」も)。
なのでこの記事のような劣化版を自作せずとも、_com_ptr_t を使えばよかったわけです。
ではなぜわざわざ自作したのかという話ですが、
たんにこれを書いたときに _com_ptr_t の存在を知らなかったというお粗末な理由によります。
ですので今から(ATL なしで)書くのであれば、
Microsoft の優秀な技術者によって書かれたであろう _com_ptr_t を使ったほうがいいと思います。
Re: エラー処理について
上記プログラムをCComPtrで作り直したのですが、結局、FormatMessageでのエラーメッセージ取得は失敗しました。
どうやら、SAPIやmsspのエラーに対するエラーメッセージは、存在しないようです。
Re: Re: エラー処理について
SAPIやMSSPに関してはネット上でも情報があまり多いとは言えませんので、
こういった検証結果を書いていただけると助かります。