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