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