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