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