1 /* 2 * IAssemblyCache implementation 3 * 4 * Copyright 2008 James Hawkins 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 <stdarg.h> 22 #include <stdio.h> 23 24 #define COBJMACROS 25 26 #include "windef.h" 27 #include "winbase.h" 28 #include "winuser.h" 29 #include "winver.h" 30 #include "wincrypt.h" 31 #include "winreg.h" 32 #include "shlwapi.h" 33 #include "dbghelp.h" 34 #include "ole2.h" 35 #include "fusion.h" 36 #include "corerror.h" 37 38 #include "fusionpriv.h" 39 #include "wine/debug.h" 40 #include "wine/unicode.h" 41 42 WINE_DEFAULT_DEBUG_CHANNEL(fusion); 43 44 typedef struct { 45 IAssemblyCache IAssemblyCache_iface; 46 47 LONG ref; 48 HANDLE lock; 49 } IAssemblyCacheImpl; 50 51 typedef struct { 52 IAssemblyCacheItem IAssemblyCacheItem_iface; 53 54 LONG ref; 55 } IAssemblyCacheItemImpl; 56 57 static const WCHAR cache_mutex_nameW[] = 58 {'_','_','W','I','N','E','_','F','U','S','I','O','N','_','C','A','C','H','E','_','M','U','T','E','X','_','_',0}; 59 60 static BOOL create_full_path(LPCWSTR path) 61 { 62 LPWSTR new_path; 63 BOOL ret = TRUE; 64 int len; 65 66 if (!(new_path = heap_alloc((strlenW(path) + 1) * sizeof(WCHAR)))) return FALSE; 67 68 strcpyW(new_path, path); 69 70 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\') 71 new_path[len - 1] = 0; 72 73 while (!CreateDirectoryW(new_path, NULL)) 74 { 75 LPWSTR slash; 76 DWORD last_error = GetLastError(); 77 78 if(last_error == ERROR_ALREADY_EXISTS) 79 break; 80 81 if(last_error != ERROR_PATH_NOT_FOUND) 82 { 83 ret = FALSE; 84 break; 85 } 86 87 if(!(slash = strrchrW(new_path, '\\'))) 88 { 89 ret = FALSE; 90 break; 91 } 92 93 len = slash - new_path; 94 new_path[len] = 0; 95 if(!create_full_path(new_path)) 96 { 97 ret = FALSE; 98 break; 99 } 100 101 new_path[len] = '\\'; 102 } 103 104 heap_free(new_path); 105 return ret; 106 } 107 108 static BOOL get_assembly_directory(LPWSTR dir, DWORD size, const char *version, PEKIND architecture) 109 { 110 static const WCHAR dotnet[] = {'\\','M','i','c','r','o','s','o','f','t','.','N','E','T','\\',0}; 111 static const WCHAR gac[] = {'\\','a','s','s','e','m','b','l','y','\\','G','A','C',0}; 112 static const WCHAR msil[] = {'_','M','S','I','L',0}; 113 static const WCHAR x86[] = {'_','3','2',0}; 114 static const WCHAR amd64[] = {'_','6','4',0}; 115 DWORD len = GetWindowsDirectoryW(dir, size); 116 117 if (!strcmp(version, "v4.0.30319")) 118 { 119 strcpyW(dir + len, dotnet); 120 len += sizeof(dotnet)/sizeof(WCHAR) -1; 121 strcpyW(dir + len, gac + 1); 122 len += sizeof(gac)/sizeof(WCHAR) - 2; 123 } 124 else 125 { 126 strcpyW(dir + len, gac); 127 len += sizeof(gac)/sizeof(WCHAR) - 1; 128 } 129 switch (architecture) 130 { 131 case peNone: 132 break; 133 134 case peMSIL: 135 strcpyW(dir + len, msil); 136 break; 137 138 case peI386: 139 strcpyW(dir + len, x86); 140 break; 141 142 case peAMD64: 143 strcpyW(dir + len, amd64); 144 break; 145 146 default: 147 WARN("unhandled architecture %u\n", architecture); 148 return FALSE; 149 } 150 return TRUE; 151 } 152 153 /* IAssemblyCache */ 154 155 static inline IAssemblyCacheImpl *impl_from_IAssemblyCache(IAssemblyCache *iface) 156 { 157 return CONTAINING_RECORD(iface, IAssemblyCacheImpl, IAssemblyCache_iface); 158 } 159 160 static HRESULT WINAPI IAssemblyCacheImpl_QueryInterface(IAssemblyCache *iface, 161 REFIID riid, LPVOID *ppobj) 162 { 163 IAssemblyCacheImpl *This = impl_from_IAssemblyCache(iface); 164 165 TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppobj); 166 167 *ppobj = NULL; 168 169 if (IsEqualIID(riid, &IID_IUnknown) || 170 IsEqualIID(riid, &IID_IAssemblyCache)) 171 { 172 IAssemblyCache_AddRef(iface); 173 *ppobj = &This->IAssemblyCache_iface; 174 return S_OK; 175 } 176 177 WARN("(%p, %s, %p): not found\n", This, debugstr_guid(riid), ppobj); 178 return E_NOINTERFACE; 179 } 180 181 static ULONG WINAPI IAssemblyCacheImpl_AddRef(IAssemblyCache *iface) 182 { 183 IAssemblyCacheImpl *This = impl_from_IAssemblyCache(iface); 184 ULONG refCount = InterlockedIncrement(&This->ref); 185 186 TRACE("(%p)->(ref before = %u)\n", This, refCount - 1); 187 188 return refCount; 189 } 190 191 static ULONG WINAPI IAssemblyCacheImpl_Release(IAssemblyCache *iface) 192 { 193 IAssemblyCacheImpl *cache = impl_from_IAssemblyCache(iface); 194 ULONG refCount = InterlockedDecrement( &cache->ref ); 195 196 TRACE("(%p)->(ref before = %u)\n", cache, refCount + 1); 197 198 if (!refCount) 199 { 200 CloseHandle( cache->lock ); 201 heap_free( cache ); 202 } 203 return refCount; 204 } 205 206 static void cache_lock( IAssemblyCacheImpl *cache ) 207 { 208 WaitForSingleObject( cache->lock, INFINITE ); 209 } 210 211 static void cache_unlock( IAssemblyCacheImpl *cache ) 212 { 213 ReleaseMutex( cache->lock ); 214 } 215 216 static HRESULT WINAPI IAssemblyCacheImpl_UninstallAssembly(IAssemblyCache *iface, 217 DWORD dwFlags, 218 LPCWSTR pszAssemblyName, 219 LPCFUSION_INSTALL_REFERENCE pRefData, 220 ULONG *pulDisposition) 221 { 222 HRESULT hr; 223 IAssemblyCacheImpl *cache = impl_from_IAssemblyCache(iface); 224 IAssemblyName *asmname, *next = NULL; 225 IAssemblyEnum *asmenum = NULL; 226 WCHAR *p, *path = NULL; 227 ULONG disp; 228 DWORD len; 229 230 TRACE("(%p, 0%08x, %s, %p, %p)\n", iface, dwFlags, 231 debugstr_w(pszAssemblyName), pRefData, pulDisposition); 232 233 if (pRefData) 234 { 235 FIXME("application reference not supported\n"); 236 return E_NOTIMPL; 237 } 238 hr = CreateAssemblyNameObject( &asmname, pszAssemblyName, CANOF_PARSE_DISPLAY_NAME, NULL ); 239 if (FAILED( hr )) 240 return hr; 241 242 cache_lock( cache ); 243 244 hr = CreateAssemblyEnum( &asmenum, NULL, asmname, ASM_CACHE_GAC, NULL ); 245 if (FAILED( hr )) 246 goto done; 247 248 hr = IAssemblyEnum_GetNextAssembly( asmenum, NULL, &next, 0 ); 249 if (hr == S_FALSE) 250 { 251 if (pulDisposition) 252 *pulDisposition = IASSEMBLYCACHE_UNINSTALL_DISPOSITION_ALREADY_UNINSTALLED; 253 goto done; 254 } 255 hr = IAssemblyName_GetPath( next, NULL, &len ); 256 if (hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER )) 257 goto done; 258 259 if (!(path = heap_alloc( len * sizeof(WCHAR) ))) 260 { 261 hr = E_OUTOFMEMORY; 262 goto done; 263 } 264 hr = IAssemblyName_GetPath( next, path, &len ); 265 if (FAILED( hr )) 266 goto done; 267 268 if (DeleteFileW( path )) 269 { 270 if ((p = strrchrW( path, '\\' ))) 271 { 272 *p = 0; 273 RemoveDirectoryW( path ); 274 if ((p = strrchrW( path, '\\' ))) 275 { 276 *p = 0; 277 RemoveDirectoryW( path ); 278 } 279 } 280 disp = IASSEMBLYCACHE_UNINSTALL_DISPOSITION_UNINSTALLED; 281 hr = S_OK; 282 } 283 else 284 { 285 disp = IASSEMBLYCACHE_UNINSTALL_DISPOSITION_ALREADY_UNINSTALLED; 286 hr = S_FALSE; 287 } 288 if (pulDisposition) *pulDisposition = disp; 289 290 done: 291 IAssemblyName_Release( asmname ); 292 if (next) IAssemblyName_Release( next ); 293 if (asmenum) IAssemblyEnum_Release( asmenum ); 294 heap_free( path ); 295 cache_unlock( cache ); 296 return hr; 297 } 298 299 static HRESULT WINAPI IAssemblyCacheImpl_QueryAssemblyInfo(IAssemblyCache *iface, 300 DWORD dwFlags, 301 LPCWSTR pszAssemblyName, 302 ASSEMBLY_INFO *pAsmInfo) 303 { 304 IAssemblyCacheImpl *cache = impl_from_IAssemblyCache(iface); 305 IAssemblyName *asmname, *next = NULL; 306 IAssemblyEnum *asmenum = NULL; 307 HRESULT hr; 308 309 TRACE("(%p, %d, %s, %p)\n", iface, dwFlags, 310 debugstr_w(pszAssemblyName), pAsmInfo); 311 312 if (pAsmInfo) 313 { 314 if (pAsmInfo->cbAssemblyInfo == 0) 315 pAsmInfo->cbAssemblyInfo = sizeof(ASSEMBLY_INFO); 316 else if (pAsmInfo->cbAssemblyInfo != sizeof(ASSEMBLY_INFO)) 317 return E_INVALIDARG; 318 } 319 320 hr = CreateAssemblyNameObject(&asmname, pszAssemblyName, 321 CANOF_PARSE_DISPLAY_NAME, NULL); 322 if (FAILED(hr)) 323 return hr; 324 325 cache_lock( cache ); 326 327 hr = CreateAssemblyEnum(&asmenum, NULL, asmname, ASM_CACHE_GAC, NULL); 328 if (FAILED(hr)) 329 goto done; 330 331 for (;;) 332 { 333 hr = IAssemblyEnum_GetNextAssembly(asmenum, NULL, &next, 0); 334 if (hr != S_OK) 335 { 336 hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 337 goto done; 338 } 339 hr = IAssemblyName_IsEqual(asmname, next, ASM_CMPF_IL_ALL); 340 if (hr == S_OK) break; 341 } 342 343 if (!pAsmInfo) 344 goto done; 345 346 hr = IAssemblyName_GetPath(next, pAsmInfo->pszCurrentAssemblyPathBuf, &pAsmInfo->cchBuf); 347 348 pAsmInfo->dwAssemblyFlags = ASSEMBLYINFO_FLAG_INSTALLED; 349 350 done: 351 IAssemblyName_Release(asmname); 352 if (next) IAssemblyName_Release(next); 353 if (asmenum) IAssemblyEnum_Release(asmenum); 354 cache_unlock( cache ); 355 return hr; 356 } 357 358 static const IAssemblyCacheItemVtbl AssemblyCacheItemVtbl; 359 360 static HRESULT WINAPI IAssemblyCacheImpl_CreateAssemblyCacheItem(IAssemblyCache *iface, 361 DWORD dwFlags, 362 PVOID pvReserved, 363 IAssemblyCacheItem **ppAsmItem, 364 LPCWSTR pszAssemblyName) 365 { 366 IAssemblyCacheItemImpl *item; 367 368 FIXME("(%p, %d, %p, %p, %s) semi-stub!\n", iface, dwFlags, pvReserved, 369 ppAsmItem, debugstr_w(pszAssemblyName)); 370 371 if (!ppAsmItem) 372 return E_INVALIDARG; 373 374 *ppAsmItem = NULL; 375 376 if (!(item = heap_alloc(sizeof(*item)))) return E_OUTOFMEMORY; 377 378 item->IAssemblyCacheItem_iface.lpVtbl = &AssemblyCacheItemVtbl; 379 item->ref = 1; 380 381 *ppAsmItem = &item->IAssemblyCacheItem_iface; 382 return S_OK; 383 } 384 385 static HRESULT WINAPI IAssemblyCacheImpl_CreateAssemblyScavenger(IAssemblyCache *iface, 386 IUnknown **ppUnkReserved) 387 { 388 FIXME("(%p, %p) stub!\n", iface, ppUnkReserved); 389 return E_NOTIMPL; 390 } 391 392 static HRESULT copy_file( const WCHAR *src_dir, DWORD src_len, const WCHAR *dst_dir, DWORD dst_len, 393 const WCHAR *filename ) 394 { 395 WCHAR *src_file, *dst_file; 396 DWORD len = strlenW( filename ); 397 HRESULT hr = S_OK; 398 399 if (!(src_file = heap_alloc( (src_len + len + 1) * sizeof(WCHAR) ))) 400 return E_OUTOFMEMORY; 401 memcpy( src_file, src_dir, src_len * sizeof(WCHAR) ); 402 strcpyW( src_file + src_len, filename ); 403 404 if (!(dst_file = heap_alloc( (dst_len + len + 1) * sizeof(WCHAR) ))) 405 { 406 heap_free( src_file ); 407 return E_OUTOFMEMORY; 408 } 409 memcpy( dst_file, dst_dir, dst_len * sizeof(WCHAR) ); 410 strcpyW( dst_file + dst_len, filename ); 411 412 if (!CopyFileW( src_file, dst_file, FALSE )) hr = HRESULT_FROM_WIN32( GetLastError() ); 413 heap_free( src_file ); 414 heap_free( dst_file ); 415 return hr; 416 } 417 418 static HRESULT WINAPI IAssemblyCacheImpl_InstallAssembly(IAssemblyCache *iface, 419 DWORD dwFlags, 420 LPCWSTR pszManifestFilePath, 421 LPCFUSION_INSTALL_REFERENCE pRefData) 422 { 423 static const WCHAR format[] = 424 {'%','s','\\','%','s','\\','%','s','_','_','%','s','\\',0}; 425 static const WCHAR format_v40[] = 426 {'%','s','\\','%','s','\\','v','4','.','0','_','%','s','_','_','%','s','\\',0}; 427 static const WCHAR ext_exe[] = {'.','e','x','e',0}; 428 static const WCHAR ext_dll[] = {'.','d','l','l',0}; 429 IAssemblyCacheImpl *cache = impl_from_IAssemblyCache(iface); 430 ASSEMBLY *assembly; 431 const WCHAR *extension, *filename, *src_dir; 432 WCHAR *name = NULL, *token = NULL, *version = NULL, *asmpath = NULL; 433 WCHAR asmdir[MAX_PATH], *p, **external_files = NULL, *dst_dir = NULL; 434 PEKIND architecture; 435 char *clr_version; 436 DWORD i, count = 0, src_len, dst_len = sizeof(format_v40)/sizeof(format_v40[0]); 437 HRESULT hr; 438 439 TRACE("(%p, %d, %s, %p)\n", iface, dwFlags, 440 debugstr_w(pszManifestFilePath), pRefData); 441 442 if (!pszManifestFilePath || !*pszManifestFilePath) 443 return E_INVALIDARG; 444 445 if (!(extension = strrchrW(pszManifestFilePath, '.'))) 446 return HRESULT_FROM_WIN32(ERROR_INVALID_NAME); 447 448 if (lstrcmpiW(extension, ext_exe) && lstrcmpiW(extension, ext_dll)) 449 return HRESULT_FROM_WIN32(ERROR_INVALID_NAME); 450 451 if (GetFileAttributesW(pszManifestFilePath) == INVALID_FILE_ATTRIBUTES) 452 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 453 454 hr = assembly_create(&assembly, pszManifestFilePath); 455 if (FAILED(hr)) 456 { 457 hr = COR_E_ASSEMBLYEXPECTED; 458 goto done; 459 } 460 461 hr = assembly_get_name(assembly, &name); 462 if (FAILED(hr)) 463 goto done; 464 465 hr = assembly_get_pubkey_token(assembly, &token); 466 if (FAILED(hr)) 467 goto done; 468 469 hr = assembly_get_version(assembly, &version); 470 if (FAILED(hr)) 471 goto done; 472 473 hr = assembly_get_runtime_version(assembly, &clr_version); 474 if (FAILED(hr)) 475 goto done; 476 477 hr = assembly_get_external_files(assembly, &external_files, &count); 478 if (FAILED(hr)) 479 goto done; 480 481 cache_lock( cache ); 482 483 architecture = assembly_get_architecture(assembly); 484 get_assembly_directory(asmdir, MAX_PATH, clr_version, architecture); 485 486 dst_len += strlenW(asmdir) + strlenW(name) + strlenW(version) + strlenW(token); 487 if (!(dst_dir = heap_alloc(dst_len * sizeof(WCHAR)))) 488 { 489 hr = E_OUTOFMEMORY; 490 goto done; 491 } 492 if (!strcmp(clr_version, "v4.0.30319")) 493 dst_len = sprintfW(dst_dir, format_v40, asmdir, name, version, token); 494 else 495 dst_len = sprintfW(dst_dir, format, asmdir, name, version, token); 496 497 create_full_path(dst_dir); 498 499 hr = assembly_get_path(assembly, &asmpath); 500 if (FAILED(hr)) 501 goto done; 502 503 if ((p = strrchrW(asmpath, '\\'))) 504 { 505 filename = p + 1; 506 src_dir = asmpath; 507 src_len = filename - asmpath; 508 } 509 else 510 { 511 filename = asmpath; 512 src_dir = NULL; 513 src_len = 0; 514 } 515 hr = copy_file(src_dir, src_len, dst_dir, dst_len, filename); 516 if (FAILED(hr)) 517 goto done; 518 519 for (i = 0; i < count; i++) 520 { 521 hr = copy_file(src_dir, src_len, dst_dir, dst_len, external_files[i]); 522 if (FAILED(hr)) 523 break; 524 } 525 526 done: 527 heap_free(name); 528 heap_free(token); 529 heap_free(version); 530 heap_free(asmpath); 531 heap_free(dst_dir); 532 for (i = 0; i < count; i++) heap_free(external_files[i]); 533 heap_free(external_files); 534 assembly_release(assembly); 535 cache_unlock( cache ); 536 return hr; 537 } 538 539 static const IAssemblyCacheVtbl AssemblyCacheVtbl = { 540 IAssemblyCacheImpl_QueryInterface, 541 IAssemblyCacheImpl_AddRef, 542 IAssemblyCacheImpl_Release, 543 IAssemblyCacheImpl_UninstallAssembly, 544 IAssemblyCacheImpl_QueryAssemblyInfo, 545 IAssemblyCacheImpl_CreateAssemblyCacheItem, 546 IAssemblyCacheImpl_CreateAssemblyScavenger, 547 IAssemblyCacheImpl_InstallAssembly 548 }; 549 550 /****************************************************************** 551 * CreateAssemblyCache (FUSION.@) 552 */ 553 HRESULT WINAPI CreateAssemblyCache(IAssemblyCache **ppAsmCache, DWORD dwReserved) 554 { 555 IAssemblyCacheImpl *cache; 556 557 TRACE("(%p, %d)\n", ppAsmCache, dwReserved); 558 559 if (!ppAsmCache) 560 return E_INVALIDARG; 561 562 *ppAsmCache = NULL; 563 564 if (!(cache = heap_alloc(sizeof(*cache)))) return E_OUTOFMEMORY; 565 566 cache->IAssemblyCache_iface.lpVtbl = &AssemblyCacheVtbl; 567 cache->ref = 1; 568 cache->lock = CreateMutexW( NULL, FALSE, cache_mutex_nameW ); 569 if (!cache->lock) 570 { 571 heap_free( cache ); 572 return HRESULT_FROM_WIN32( GetLastError() ); 573 } 574 *ppAsmCache = &cache->IAssemblyCache_iface; 575 return S_OK; 576 } 577 578 /* IAssemblyCacheItem */ 579 580 static inline IAssemblyCacheItemImpl *impl_from_IAssemblyCacheItem(IAssemblyCacheItem *iface) 581 { 582 return CONTAINING_RECORD(iface, IAssemblyCacheItemImpl, IAssemblyCacheItem_iface); 583 } 584 585 static HRESULT WINAPI IAssemblyCacheItemImpl_QueryInterface(IAssemblyCacheItem *iface, 586 REFIID riid, LPVOID *ppobj) 587 { 588 IAssemblyCacheItemImpl *This = impl_from_IAssemblyCacheItem(iface); 589 590 TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppobj); 591 592 *ppobj = NULL; 593 594 if (IsEqualIID(riid, &IID_IUnknown) || 595 IsEqualIID(riid, &IID_IAssemblyCacheItem)) 596 { 597 IAssemblyCacheItem_AddRef(iface); 598 *ppobj = &This->IAssemblyCacheItem_iface; 599 return S_OK; 600 } 601 602 WARN("(%p, %s, %p): not found\n", This, debugstr_guid(riid), ppobj); 603 return E_NOINTERFACE; 604 } 605 606 static ULONG WINAPI IAssemblyCacheItemImpl_AddRef(IAssemblyCacheItem *iface) 607 { 608 IAssemblyCacheItemImpl *This = impl_from_IAssemblyCacheItem(iface); 609 ULONG refCount = InterlockedIncrement(&This->ref); 610 611 TRACE("(%p)->(ref before = %u)\n", This, refCount - 1); 612 613 return refCount; 614 } 615 616 static ULONG WINAPI IAssemblyCacheItemImpl_Release(IAssemblyCacheItem *iface) 617 { 618 IAssemblyCacheItemImpl *This = impl_from_IAssemblyCacheItem(iface); 619 ULONG refCount = InterlockedDecrement(&This->ref); 620 621 TRACE("(%p)->(ref before = %u)\n", This, refCount + 1); 622 623 if (!refCount) 624 heap_free(This); 625 626 return refCount; 627 } 628 629 static HRESULT WINAPI IAssemblyCacheItemImpl_CreateStream(IAssemblyCacheItem *iface, 630 DWORD dwFlags, 631 LPCWSTR pszStreamName, 632 DWORD dwFormat, 633 DWORD dwFormatFlags, 634 IStream **ppIStream, 635 ULARGE_INTEGER *puliMaxSize) 636 { 637 FIXME("(%p, %d, %s, %d, %d, %p, %p) stub!\n", iface, dwFlags, 638 debugstr_w(pszStreamName), dwFormat, dwFormatFlags, ppIStream, puliMaxSize); 639 640 return E_NOTIMPL; 641 } 642 643 static HRESULT WINAPI IAssemblyCacheItemImpl_Commit(IAssemblyCacheItem *iface, 644 DWORD dwFlags, 645 ULONG *pulDisposition) 646 { 647 FIXME("(%p, %d, %p) stub!\n", iface, dwFlags, pulDisposition); 648 return E_NOTIMPL; 649 } 650 651 static HRESULT WINAPI IAssemblyCacheItemImpl_AbortItem(IAssemblyCacheItem *iface) 652 { 653 FIXME("(%p) stub!\n", iface); 654 return E_NOTIMPL; 655 } 656 657 static const IAssemblyCacheItemVtbl AssemblyCacheItemVtbl = { 658 IAssemblyCacheItemImpl_QueryInterface, 659 IAssemblyCacheItemImpl_AddRef, 660 IAssemblyCacheItemImpl_Release, 661 IAssemblyCacheItemImpl_CreateStream, 662 IAssemblyCacheItemImpl_Commit, 663 IAssemblyCacheItemImpl_AbortItem 664 }; 665