673d180a907e1c8410517a7fdf18c92bab8470ad
[reactos.git] / reactos / dll / win32 / fusion / asmcache.c
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;
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;
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 };