xref: /reactos/dll/win32/fusion/asmcache.c (revision c2c66aff)
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