1 /* 2 * Implementation of the OLEACC dll 3 * 4 * Copyright 2003 Mike McCormack for CodeWeavers 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 */ 20 21 #include "oleacc_private.h" 22 23 #include <commctrl.h> 24 #include <rpcproxy.h> 25 26 #include <wine/unicode.h> 27 28 #include "resource.h" 29 30 static const WCHAR lresult_atom_prefix[] = {'w','i','n','e','_','o','l','e','a','c','c',':'}; 31 32 static const WCHAR menuW[] = {'#','3','2','7','6','8',0}; 33 static const WCHAR desktopW[] = {'#','3','2','7','6','9',0}; 34 static const WCHAR dialogW[] = {'#','3','2','7','7','0',0}; 35 static const WCHAR winswitchW[] = {'#','3','2','7','7','1',0}; 36 static const WCHAR mdi_clientW[] = {'M','D','I','C','l','i','e','n','t',0}; 37 static const WCHAR richeditW[] = {'R','I','C','H','E','D','I','T',0}; 38 static const WCHAR richedit20aW[] = {'R','i','c','h','E','d','i','t','2','0','A',0}; 39 static const WCHAR richedit20wW[] = {'R','i','c','h','E','d','i','t','2','0','W',0}; 40 41 typedef HRESULT (WINAPI *accessible_create)(HWND, const IID*, void**); 42 43 extern HRESULT WINAPI OLEACC_DllGetClassObject(REFCLSID, REFIID, void**) DECLSPEC_HIDDEN; 44 extern BOOL WINAPI OLEACC_DllMain(HINSTANCE, DWORD, void*) DECLSPEC_HIDDEN; 45 extern HRESULT WINAPI OLEACC_DllRegisterServer(void) DECLSPEC_HIDDEN; 46 extern HRESULT WINAPI OLEACC_DllUnregisterServer(void) DECLSPEC_HIDDEN; 47 48 static struct { 49 const WCHAR *name; 50 DWORD idx; 51 accessible_create create_client; 52 accessible_create create_window; 53 } builtin_classes[] = { 54 {WC_LISTBOXW, 0x10000, NULL, NULL}, 55 {menuW, 0x10001, NULL, NULL}, 56 {WC_BUTTONW, 0x10002, NULL, NULL}, 57 {WC_STATICW, 0x10003, NULL, NULL}, 58 {WC_EDITW, 0x10004, NULL, NULL}, 59 {WC_COMBOBOXW, 0x10005, NULL, NULL}, 60 {dialogW, 0x10006, NULL, NULL}, 61 {winswitchW, 0x10007, NULL, NULL}, 62 {mdi_clientW, 0x10008, NULL, NULL}, 63 {desktopW, 0x10009, NULL, NULL}, 64 {WC_SCROLLBARW, 0x1000a, NULL, NULL}, 65 {STATUSCLASSNAMEW, 0x1000b, NULL, NULL}, 66 {TOOLBARCLASSNAMEW, 0x1000c, NULL, NULL}, 67 {PROGRESS_CLASSW, 0x1000d, NULL, NULL}, 68 {ANIMATE_CLASSW, 0x1000e, NULL, NULL}, 69 {WC_TABCONTROLW, 0x1000f, NULL, NULL}, 70 {HOTKEY_CLASSW, 0x10010, NULL, NULL}, 71 {WC_HEADERW, 0x10011, NULL, NULL}, 72 {TRACKBAR_CLASSW, 0x10012, NULL, NULL}, 73 {WC_LISTVIEWW, 0x10013, NULL, NULL}, 74 {UPDOWN_CLASSW, 0x10016, NULL, NULL}, 75 {TOOLTIPS_CLASSW, 0x10018, NULL, NULL}, 76 {WC_TREEVIEWW, 0x10019, NULL, NULL}, 77 {MONTHCAL_CLASSW, 0, NULL, NULL}, 78 {DATETIMEPICK_CLASSW, 0, NULL, NULL}, 79 {WC_IPADDRESSW, 0, NULL, NULL}, 80 {richeditW, 0x1001c, NULL, NULL}, 81 {richedit20aW, 0, NULL, NULL}, 82 {richedit20wW, 0, NULL, NULL}, 83 }; 84 85 static HINSTANCE oleacc_handle = 0; 86 87 int convert_child_id(VARIANT *v) 88 { 89 switch(V_VT(v)) { 90 case VT_I4: 91 return V_I4(v); 92 default: 93 FIXME("unhandled child ID variant type: %d\n", V_VT(v)); 94 return -1; 95 } 96 } 97 98 static accessible_create get_builtin_accessible_obj(HWND hwnd, LONG objid) 99 { 100 WCHAR class_name[64]; 101 int i, idx; 102 103 if(!RealGetWindowClassW(hwnd, class_name, sizeof(class_name)/sizeof(WCHAR))) 104 return NULL; 105 TRACE("got window class: %s\n", debugstr_w(class_name)); 106 107 for(i=0; i<sizeof(builtin_classes)/sizeof(builtin_classes[0]); i++) { 108 if(!strcmpiW(class_name, builtin_classes[i].name)) { 109 accessible_create ret; 110 111 ret = (objid==OBJID_CLIENT ? 112 builtin_classes[i].create_client : 113 builtin_classes[i].create_window); 114 if(!ret) 115 FIXME("unhandled window class: %s\n", debugstr_w(class_name)); 116 return ret; 117 } 118 } 119 120 idx = SendMessageW(hwnd, WM_GETOBJECT, 0, OBJID_QUERYCLASSNAMEIDX); 121 if(idx) { 122 for(i=0; i<sizeof(builtin_classes)/sizeof(builtin_classes[0]); i++) { 123 if(idx == builtin_classes[i].idx) { 124 accessible_create ret; 125 126 ret = (objid==OBJID_CLIENT ? 127 builtin_classes[i].create_client : 128 builtin_classes[i].create_window); 129 if(!ret) 130 FIXME("unhandled class name idx: %x\n", idx); 131 return ret; 132 } 133 } 134 135 WARN("unhandled class name idx: %x\n", idx); 136 } 137 138 return NULL; 139 } 140 141 HRESULT WINAPI CreateStdAccessibleObject( HWND hwnd, LONG idObject, 142 REFIID riidInterface, void** ppvObject ) 143 { 144 accessible_create create; 145 146 TRACE("%p %d %s %p\n", hwnd, idObject, 147 debugstr_guid( riidInterface ), ppvObject ); 148 149 switch(idObject) { 150 case OBJID_CLIENT: 151 create = get_builtin_accessible_obj(hwnd, idObject); 152 if(create) return create(hwnd, riidInterface, ppvObject); 153 return create_client_object(hwnd, riidInterface, ppvObject); 154 case OBJID_WINDOW: 155 create = get_builtin_accessible_obj(hwnd, idObject); 156 if(create) return create(hwnd, riidInterface, ppvObject); 157 return create_window_object(hwnd, riidInterface, ppvObject); 158 default: 159 FIXME("unhandled object id: %d\n", idObject); 160 return E_NOTIMPL; 161 } 162 } 163 164 HRESULT WINAPI ObjectFromLresult( LRESULT result, REFIID riid, WPARAM wParam, void **ppObject ) 165 { 166 WCHAR atom_str[sizeof(lresult_atom_prefix)/sizeof(WCHAR)+3*8+3]; 167 HANDLE server_proc, server_mapping, mapping; 168 DWORD proc_id, size; 169 IStream *stream; 170 HGLOBAL data; 171 void *view; 172 HRESULT hr; 173 WCHAR *p; 174 175 TRACE("%ld %s %ld %p\n", result, debugstr_guid(riid), wParam, ppObject ); 176 177 if(wParam) 178 FIXME("unsupported wParam = %lx\n", wParam); 179 180 if(!ppObject) 181 return E_INVALIDARG; 182 *ppObject = NULL; 183 184 if(result != (ATOM)result) 185 return E_FAIL; 186 187 if(!GlobalGetAtomNameW(result, atom_str, sizeof(atom_str)/sizeof(WCHAR))) 188 return E_FAIL; 189 if(memcmp(atom_str, lresult_atom_prefix, sizeof(lresult_atom_prefix))) 190 return E_FAIL; 191 p = atom_str + sizeof(lresult_atom_prefix)/sizeof(WCHAR); 192 proc_id = strtoulW(p, &p, 16); 193 if(*p != ':') 194 return E_FAIL; 195 server_mapping = ULongToHandle( strtoulW(p+1, &p, 16) ); 196 if(*p != ':') 197 return E_FAIL; 198 size = strtoulW(p+1, &p, 16); 199 if(*p != 0) 200 return E_FAIL; 201 202 server_proc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, proc_id); 203 if(!server_proc) 204 return E_FAIL; 205 206 if(!DuplicateHandle(server_proc, server_mapping, GetCurrentProcess(), &mapping, 207 0, FALSE, DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS)) 208 return E_FAIL; 209 CloseHandle(server_proc); 210 GlobalDeleteAtom(result); 211 212 view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); 213 CloseHandle(mapping); 214 if(!view) 215 return E_FAIL; 216 217 data = GlobalAlloc(GMEM_FIXED, size); 218 if(!data) { 219 UnmapViewOfFile(view); 220 return E_OUTOFMEMORY; 221 } 222 memcpy(data, view, size); 223 UnmapViewOfFile(view); 224 225 hr = CreateStreamOnHGlobal(data, TRUE, &stream); 226 if(FAILED(hr)) { 227 GlobalFree(data); 228 return hr; 229 } 230 231 hr = CoUnmarshalInterface(stream, riid, ppObject); 232 IStream_Release(stream); 233 return hr; 234 } 235 236 LRESULT WINAPI LresultFromObject( REFIID riid, WPARAM wParam, LPUNKNOWN pAcc ) 237 { 238 static const WCHAR atom_fmt[] = {'%','0','8','x',':','%','0','8','x',':','%','0','8','x',0}; 239 static const LARGE_INTEGER seek_zero = {{0}}; 240 241 WCHAR atom_str[sizeof(lresult_atom_prefix)/sizeof(WCHAR)+3*8+3]; 242 IStream *stream; 243 HANDLE mapping; 244 STATSTG stat; 245 HRESULT hr; 246 ATOM atom; 247 void *view; 248 249 TRACE("%s %ld %p\n", debugstr_guid(riid), wParam, pAcc); 250 251 if(wParam) 252 FIXME("unsupported wParam = %lx\n", wParam); 253 254 if(!pAcc) 255 return E_INVALIDARG; 256 257 hr = CreateStreamOnHGlobal(NULL, TRUE, &stream); 258 if(FAILED(hr)) 259 return hr; 260 261 hr = CoMarshalInterface(stream, riid, pAcc, MSHCTX_LOCAL, NULL, MSHLFLAGS_NORMAL); 262 if(FAILED(hr)) { 263 IStream_Release(stream); 264 return hr; 265 } 266 267 hr = IStream_Seek(stream, seek_zero, STREAM_SEEK_SET, NULL); 268 if(FAILED(hr)) { 269 IStream_Release(stream); 270 return hr; 271 } 272 273 hr = IStream_Stat(stream, &stat, STATFLAG_NONAME); 274 if(FAILED(hr)) { 275 CoReleaseMarshalData(stream); 276 IStream_Release(stream); 277 return hr; 278 }else if(stat.cbSize.u.HighPart) { 279 FIXME("stream size to big\n"); 280 CoReleaseMarshalData(stream); 281 IStream_Release(stream); 282 return E_NOTIMPL; 283 } 284 285 mapping = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 286 stat.cbSize.u.HighPart, stat.cbSize.u.LowPart, NULL); 287 if(!mapping) { 288 CoReleaseMarshalData(stream); 289 IStream_Release(stream); 290 return hr; 291 } 292 293 view = MapViewOfFile(mapping, FILE_MAP_WRITE, 0, 0, 0); 294 if(!view) { 295 CloseHandle(mapping); 296 CoReleaseMarshalData(stream); 297 IStream_Release(stream); 298 return E_FAIL; 299 } 300 301 hr = IStream_Read(stream, view, stat.cbSize.u.LowPart, NULL); 302 UnmapViewOfFile(view); 303 if(FAILED(hr)) { 304 CloseHandle(mapping); 305 hr = IStream_Seek(stream, seek_zero, STREAM_SEEK_SET, NULL); 306 if(SUCCEEDED(hr)) 307 CoReleaseMarshalData(stream); 308 IStream_Release(stream); 309 return hr; 310 311 } 312 313 memcpy(atom_str, lresult_atom_prefix, sizeof(lresult_atom_prefix)); 314 sprintfW(atom_str+sizeof(lresult_atom_prefix)/sizeof(WCHAR), 315 atom_fmt, GetCurrentProcessId(), HandleToUlong(mapping), stat.cbSize.u.LowPart); 316 atom = GlobalAddAtomW(atom_str); 317 if(!atom) { 318 CloseHandle(mapping); 319 hr = IStream_Seek(stream, seek_zero, STREAM_SEEK_SET, NULL); 320 if(SUCCEEDED(hr)) 321 CoReleaseMarshalData(stream); 322 IStream_Release(stream); 323 return E_FAIL; 324 } 325 326 IStream_Release(stream); 327 return atom; 328 } 329 330 HRESULT WINAPI AccessibleObjectFromPoint( POINT ptScreen, IAccessible** ppacc, VARIANT* pvarChild ) 331 { 332 FIXME("{%d,%d} %p %p: stub\n", ptScreen.x, ptScreen.y, ppacc, pvarChild ); 333 return E_NOTIMPL; 334 } 335 336 HRESULT WINAPI AccessibleObjectFromWindow( HWND hwnd, DWORD dwObjectID, 337 REFIID riid, void** ppvObject ) 338 { 339 TRACE("%p %d %s %p\n", hwnd, dwObjectID, 340 debugstr_guid( riid ), ppvObject ); 341 342 if(!ppvObject) 343 return E_INVALIDARG; 344 *ppvObject = NULL; 345 346 if(IsWindow(hwnd)) { 347 LRESULT lres; 348 349 lres = SendMessageW(hwnd, WM_GETOBJECT, 0xffffffff, dwObjectID); 350 if(FAILED(lres)) 351 return lres; 352 else if(lres) 353 return ObjectFromLresult(lres, riid, 0, ppvObject); 354 } 355 356 return CreateStdAccessibleObject(hwnd, dwObjectID, riid, ppvObject); 357 } 358 359 HRESULT WINAPI WindowFromAccessibleObject(IAccessible *acc, HWND *phwnd) 360 { 361 IDispatch *parent; 362 IOleWindow *ow; 363 HRESULT hres; 364 365 TRACE("%p %p\n", acc, phwnd); 366 367 IAccessible_AddRef(acc); 368 while(1) { 369 hres = IAccessible_QueryInterface(acc, &IID_IOleWindow, (void**)&ow); 370 if(SUCCEEDED(hres)) { 371 hres = IOleWindow_GetWindow(ow, phwnd); 372 IOleWindow_Release(ow); 373 IAccessible_Release(acc); 374 return hres; 375 } 376 377 hres = IAccessible_get_accParent(acc, &parent); 378 IAccessible_Release(acc); 379 if(FAILED(hres)) 380 return hres; 381 if(hres!=S_OK || !parent) { 382 *phwnd = NULL; 383 return hres; 384 } 385 386 hres = IDispatch_QueryInterface(parent, &IID_IAccessible, (void**)&acc); 387 IDispatch_Release(parent); 388 if(FAILED(hres)) 389 return hres; 390 } 391 } 392 393 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, 394 LPVOID lpvReserved) 395 { 396 TRACE("%p, %d, %p\n", hinstDLL, fdwReason, lpvReserved); 397 398 switch (fdwReason) 399 { 400 case DLL_PROCESS_ATTACH: 401 oleacc_handle = hinstDLL; 402 DisableThreadLibraryCalls(hinstDLL); 403 break; 404 } 405 406 return OLEACC_DllMain(hinstDLL, fdwReason, lpvReserved); 407 } 408 409 HRESULT WINAPI DllRegisterServer(void) 410 { 411 return OLEACC_DllRegisterServer(); 412 } 413 414 HRESULT WINAPI DllUnregisterServer(void) 415 { 416 return OLEACC_DllUnregisterServer(); 417 } 418 419 HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID iid, void **ppv) 420 { 421 if(IsEqualGUID(&CLSID_CAccPropServices, rclsid)) { 422 TRACE("(CLSID_CAccPropServices %s %p)\n", debugstr_guid(iid), ppv); 423 return get_accpropservices_factory(iid, ppv); 424 } 425 426 if(IsEqualGUID(&CLSID_PSFactoryBuffer, rclsid)) { 427 TRACE("(CLSID_PSFactoryBuffer %s %p)\n", debugstr_guid(iid), ppv); 428 return OLEACC_DllGetClassObject(rclsid, iid, ppv); 429 } 430 431 FIXME("%s %s %p: stub\n", debugstr_guid(rclsid), debugstr_guid(iid), ppv); 432 return E_NOTIMPL; 433 } 434 435 void WINAPI GetOleaccVersionInfo(DWORD* pVersion, DWORD* pBuild) 436 { 437 #ifdef __REACTOS__ 438 *pVersion = MAKELONG(2,4); /* Windows XP version of oleacc: 4.2.5406.0 */ 439 *pBuild = MAKELONG(0,5406); 440 #else 441 *pVersion = MAKELONG(0,7); /* Windows 7 version of oleacc: 7.0.0.0 */ 442 *pBuild = MAKELONG(0,0); 443 #endif 444 } 445 446 HANDLE WINAPI GetProcessHandleFromHwnd(HWND hwnd) 447 { 448 DWORD proc_id; 449 450 TRACE("%p\n", hwnd); 451 452 if(!GetWindowThreadProcessId(hwnd, &proc_id)) 453 return NULL; 454 return OpenProcess(PROCESS_DUP_HANDLE | PROCESS_VM_OPERATION | 455 PROCESS_VM_READ | PROCESS_VM_WRITE | SYNCHRONIZE, TRUE, proc_id); 456 } 457 458 UINT WINAPI GetRoleTextW(DWORD role, LPWSTR lpRole, UINT rolemax) 459 { 460 INT ret; 461 WCHAR *resptr; 462 463 TRACE("%u %p %u\n", role, lpRole, rolemax); 464 465 /* return role text length */ 466 if(!lpRole) 467 return LoadStringW(oleacc_handle, role, (LPWSTR)&resptr, 0); 468 469 ret = LoadStringW(oleacc_handle, role, lpRole, rolemax); 470 if(!(ret > 0)){ 471 if(rolemax > 0) lpRole[0] = '\0'; 472 return 0; 473 } 474 475 return ret; 476 } 477 478 UINT WINAPI GetRoleTextA(DWORD role, LPSTR lpRole, UINT rolemax) 479 { 480 UINT length; 481 WCHAR *roletextW; 482 483 TRACE("%u %p %u\n", role, lpRole, rolemax); 484 485 if(lpRole && !rolemax) 486 return 0; 487 488 length = GetRoleTextW(role, NULL, 0); 489 if(!length) { 490 if(lpRole && rolemax) 491 lpRole[0] = 0; 492 return 0; 493 } 494 495 roletextW = HeapAlloc(GetProcessHeap(), 0, (length + 1)*sizeof(WCHAR)); 496 if(!roletextW) 497 return 0; 498 499 GetRoleTextW(role, roletextW, length + 1); 500 501 length = WideCharToMultiByte( CP_ACP, 0, roletextW, -1, NULL, 0, NULL, NULL ); 502 503 if(!lpRole){ 504 HeapFree(GetProcessHeap(), 0, roletextW); 505 return length - 1; 506 } 507 508 if(rolemax < length) { 509 HeapFree(GetProcessHeap(), 0, roletextW); 510 lpRole[0] = 0; 511 return 0; 512 } 513 514 WideCharToMultiByte( CP_ACP, 0, roletextW, -1, lpRole, rolemax, NULL, NULL ); 515 516 if(rolemax < length){ 517 lpRole[rolemax-1] = '\0'; 518 length = rolemax; 519 } 520 521 HeapFree(GetProcessHeap(), 0, roletextW); 522 523 return length - 1; 524 } 525 526 UINT WINAPI GetStateTextW(DWORD state_bit, WCHAR *state_str, UINT state_str_len) 527 { 528 DWORD state_id; 529 530 TRACE("%x %p %u\n", state_bit, state_str, state_str_len); 531 532 if(state_bit & ~(STATE_SYSTEM_VALID | STATE_SYSTEM_HASPOPUP)) { 533 if(state_str && state_str_len) 534 state_str[0] = 0; 535 return 0; 536 } 537 538 state_id = IDS_STATE_NORMAL; 539 while(state_bit) { 540 state_id++; 541 state_bit /= 2; 542 } 543 544 if(state_str) { 545 UINT ret = LoadStringW(oleacc_handle, state_id, state_str, state_str_len); 546 if(!ret && state_str_len) 547 state_str[0] = 0; 548 return ret; 549 }else { 550 WCHAR *tmp; 551 return LoadStringW(oleacc_handle, state_id, (WCHAR*)&tmp, 0); 552 } 553 554 } 555 556 UINT WINAPI GetStateTextA(DWORD state_bit, CHAR *state_str, UINT state_str_len) 557 { 558 DWORD state_id; 559 560 TRACE("%x %p %u\n", state_bit, state_str, state_str_len); 561 562 if(state_str && !state_str_len) 563 return 0; 564 565 if(state_bit & ~(STATE_SYSTEM_VALID | STATE_SYSTEM_HASPOPUP)) { 566 if(state_str && state_str_len) 567 state_str[0] = 0; 568 return 0; 569 } 570 571 state_id = IDS_STATE_NORMAL; 572 while(state_bit) { 573 state_id++; 574 state_bit /= 2; 575 } 576 577 if(state_str) { 578 UINT ret = LoadStringA(oleacc_handle, state_id, state_str, state_str_len); 579 if(!ret && state_str_len) 580 state_str[0] = 0; 581 return ret; 582 }else { 583 CHAR tmp[256]; 584 return LoadStringA(oleacc_handle, state_id, tmp, sizeof(tmp)); 585 } 586 } 587 588 HRESULT WINAPI AccessibleChildren(IAccessible *container, 589 LONG start, LONG count, VARIANT *children, LONG *children_cnt) 590 { 591 IEnumVARIANT *ev; 592 LONG i, child_no; 593 HRESULT hr; 594 595 TRACE("%p %d %d %p %p\n", container, start, count, children, children_cnt); 596 597 if(!container || !children || !children_cnt) 598 return E_INVALIDARG; 599 600 for(i=0; i<count; i++) 601 VariantInit(children+i); 602 603 hr = IAccessible_QueryInterface(container, &IID_IEnumVARIANT, (void**)&ev); 604 if(SUCCEEDED(hr)) { 605 hr = IEnumVARIANT_Reset(ev); 606 if(SUCCEEDED(hr)) 607 hr = IEnumVARIANT_Skip(ev, start); 608 if(SUCCEEDED(hr)) 609 hr = IEnumVARIANT_Next(ev, count, children, (ULONG*)children_cnt); 610 IEnumVARIANT_Release(ev); 611 return hr; 612 } 613 614 hr = IAccessible_get_accChildCount(container, &child_no); 615 if(FAILED(hr)) 616 return hr; 617 618 for(i=0; i<count && start+i+1<=child_no; i++) { 619 IDispatch *disp; 620 621 V_VT(children+i) = VT_I4; 622 V_I4(children+i) = start+i+1; 623 624 hr = IAccessible_get_accChild(container, children[i], &disp); 625 if(SUCCEEDED(hr) && disp) { 626 V_VT(children+i) = VT_DISPATCH; 627 V_DISPATCH(children+i) = disp; 628 } 629 } 630 631 *children_cnt = i; 632 return i==count ? S_OK : S_FALSE; 633 } 634