Naprawianie Microsoft Speech API (SAPI)

Z przyczyn... życiowych zaniedbałem ostatnio devblog, ale już to nadrabiam. W dzisiejszym odcinku dowiemy się, jak naprawić Microsoft Speech API pod platformą MSVC++ 2005 Express i PlatformSDK. Instalacja SAPI jest prosta i sprowadza się do standardowych kroków: ściągnij bibliotekę, zainstaluj, ustaw katalogi include i lib w Visualu. Potem można spróbować skompilować jakiś przykład... i tu pojawiają się schody. Speech API w wersji 5.1 było pisane bardzo dawno temu, z ewidentnym brakiem dobrego stylu programowania. Dlatego na nowych Visualach, które są bardzo restrykcyjne, kod się po prostu nie skompiluje z powodu ewidentnych błędów programistycznych w samej bibliotece (które najwyraźniej uchodziły kiedyś jako tzw. skróty myślowe). Ale my się tym nie zrazimy i zaraz Speech API naprawimy ;) Zacząć należy od dodania podkatalogu atl/ w katalogu include/ Platform SDK do ścieżek Visuala - plik sphelper.h dołącza nagłówek atlbase.h. A teraz zaczyna się zabawa w hacking SAPI. Otwieramy plik sphelper.h (główny bohater dzisiejszych zmagań) i znajdujemy poniższą linię kodu (u mnie 769):
const ulLenVendorPreferred = wcslen(pszVendorPreferred);
I zamieniamy ją na
const size_t ulLenVendorPreferred = wcslen(pszVendorPreferred);
Zamiast size_t możemy tez wpisać unsigned long - zależy to od tego, czy bardziej trzymamy się typu zwracanego funkcji wcslen() czy notacji węgierskiej użytej przy nazywaniu zmiennej ulLenVendorPreferred - w praktyce oba typy to i tak to samo. Kolejny hack to znalezienie linii o poniższej treści (u mnie 1418):
static CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
i zamiana na
static HRESULT CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
Następnie odnajdujemy poniższy fragment (u mnie linia 2372 i 2373):
for (const WCHAR * psz = (const WCHAR *)lParam; *psz; psz++) {}
return psz + 1;
i przebudowujemy tą pętlę do następującej postaci:
const WCHAR * psz = (const WCHAR *)lParam;
for (; *psz; psz++) {}
return psz + 1;
(Dodałem jeszcze jedną linię kodu w obu fragmentach dla uwidocznienia przyczyny błędu) Ewidentny przykład tego, jak stare kompilatory Microsoftu obsługiwały zmienne w pętli for niezgodnie ze standardem. Ale prawdziwa zabawa dopiero się zaczyna; kolejne dwa błędy zmuszają nas do sięgnięcia po narzędzie zwane absolute_cast, o którym możemy przeczytać u Regedita. Dlatego na początku nagłówka (zaraz pod include'ami) dodajemy następujący kod:
template <typename destT, typename srcT>
destT & absolute_cast(srcT &v)
{
  return reinterpret_cast<destT&>(v);
}

template <typename destT, typename srcT>
const destT & absolute_cast(const srcT &v)
{
  return reinterpret_cast<const destT&>(v);
}
Odnajdujemy linię (u mnie 2560 PRZED dodaniem absolute_cast do kodu):
SPPHONEID* pphoneId = dsPhoneId;
i zamieniamy na
SPPHONEID* pphoneId = absolute_cast<SPPHONEID*>(dsPhoneId);
Następnie odnajdujemy linię (u mnie 2634, jw.):
pphoneId += wcslen(pphoneId) + 1;
i w jej miejsce wstawiamy:
pphoneId += wcslen(absolute_cast<wchar_t *>(pphoneId) ) + 1;
Od tej pory programy SAPI powinny ładnie przechodzić fazę kompilacji. Kto jednak spróbuje zbudować przykładowe programy może się zdziwić, że pojawia się błąd linkowania. Otóż wspomniany na początku opowieści nagłówek atlbase.h zawiera odwołania do biblioteki atlthunk.lib, której typowy użytkownik VC++ 2005 Express nie posiada. Żeby się pozbyć tego błędu, musimy zhack'ować nagłówek atlbase.h. Metoda ta została opisana na CodeProject.com. Odnajdujemy poniższy fragment
PVOID __stdcall __AllocStdCallThunk(VOID);
VOID  __stdcall __FreeStdCallThunk(PVOID);

#define AllocStdCallThunk() __AllocStdCallThunk()
#define FreeStdCallThunk(p) __FreeStdCallThunk(p)

#pragma comment(lib, "atlthunk.lib")
I umieszczamy go w komentarzu. Tuż pod nim umieszczamy następujący kod:
#define AllocStdCallThunk() HeapAlloc(GetProcessHeap(), 0, sizeof(_stdcallthunk))
#define FreeStdCallThunk(p) HeapFree(GetProcessHeap(), 0, p)
W ten sposób pozbyliśmy się odwołań do biblioteki atlthunk.lib zapewniając przy okazji alternatywną definicję dla funkcji z tej biblioteki, których używa nagłówek atlbase.h. Warto wyciągnąć przy tej okazji naukę - dbajmy o to, jak piszemy kod. Nie stosujmy niezgodnych ze standardem skrótów myślowych, nie pomijajmy typów zwracanych, nie stosujmy w taki sposób niejawnych rzutowań. Moim zdaniem jest to nie lada upokorzenie dla Microsoftu i programistów SAPI, skoro trzeba tą bibliotekę intensywnie hack'ować, żeby w ogóle dała się używać z ich nowymi kompilatorami. Życzę miłej zabawy z rozpoznawaniem mowy, a ja już wkrótce wrzucę w międzyczasie wrzuciłem opis obiecanego Voice Control System'u.