#include #include #include "..\id\sxsid.h" #define WZ_CACHE_LOCALROOTDIR L"Application Store\\" #define WZ_MANIFEST_STAGING_DIR L"ManifestStagingDir\\" #define WZ_WILDCARDSTRING L"*" extern BOOL IsEqualAssemblyFileInfo(LPASSEMBLY_FILE_INFO pAsmFileInfo1, LPASSEMBLY_FILE_INFO pAsmFileInfo2); // --------------------------------------------------------------------------- // CreateAssemblyCacheImport // --------------------------------------------------------------------------- STDAPI CreateAssemblyCacheImport( LPASSEMBLY_CACHE_IMPORT *ppAssemblyCacheImport, LPASSEMBLY_IDENTITY pAssemblyIdentity, DWORD dwFlags) { HRESULT hr = S_OK; LPWSTR pwzSearchDisplayName = NULL; BOOL bNewAsmId = FALSE; CAssemblyCache *pAssemblyCache = NULL; pAssemblyCache = new(CAssemblyCache); if (!pAssemblyCache) { hr = E_OUTOFMEMORY; goto exit; } if (FAILED(hr = pAssemblyCache->Init(NULL))) goto exit; if (dwFlags == CACHEIMP_CREATE_RETRIEVE_MAX_COMPLETED || dwFlags == CACHEIMP_CREATE_RETRIEVE_EXIST || dwFlags == CACHEIMP_CREATE_RESOLVE_REF || dwFlags == CACHEIMP_CREATE_RESOLVE_REF_EX || dwFlags == CACHEIMP_CREATE_RETRIEVE_EXIST_COMPLETED) { LPWSTR pwzBuf = NULL; DWORD dwCC = 0; CString sManifestFilename; CString sDisplayName; if (pAssemblyIdentity == NULL) { hr = E_INVALIDARG; goto exit; } // get the identity name if ((hr = pAssemblyIdentity->GetAttribute(SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_NAME, &pwzBuf, &dwCC)) != S_OK) { // BUGBUG?: should this work regardless the existence of asm name? hr = E_INVALIDARG; goto exit; } // filename of the manifest must be the same as the assembly name // BUGBUG??: this implies manifest filename (and asm name) be remained unchange because // the assembly name from the new AsmId is used for looking up in the older cached version... sManifestFilename.TakeOwnership(pwzBuf, dwCC); if (FAILED(hr = sManifestFilename.Append(L".manifest"))) goto exit; if (dwFlags == CACHEIMP_CREATE_RETRIEVE_MAX_COMPLETED) { LPASSEMBLY_IDENTITY pNewAsmId = NULL; if (FAILED(hr = CloneAssemblyIdentity(pAssemblyIdentity, &pNewAsmId))) goto exit; pAssemblyIdentity = pNewAsmId; bNewAsmId = TRUE; // force Version to be a wildcard if (FAILED(hr = pAssemblyIdentity->SetAttribute(SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_VERSION, WZ_WILDCARDSTRING, lstrlen(WZ_WILDCARDSTRING)+1))) goto exit; } if (dwFlags == CACHEIMP_CREATE_RETRIEVE_MAX_COMPLETED || dwFlags == CACHEIMP_CREATE_RESOLVE_REF || dwFlags == CACHEIMP_CREATE_RESOLVE_REF_EX) { // issues: what if other then Version is already wildcarded? does version comparison make sense here? if (FAILED(hr = pAssemblyIdentity->GetDisplayName(ASMID_DISPLAYNAME_WILDCARDED, &pwzSearchDisplayName, &dwCC))) goto exit; if (SearchForHighestVersionInCache(&pwzBuf, pwzSearchDisplayName, CAssemblyCache::COMPLETED, pAssemblyCache) == S_OK) { sDisplayName.TakeOwnership(pwzBuf); // BUGBUG - make GetDisplayName call getassemblyid/getdisplayname instead if (FAILED(hr = (pAssemblyCache->_sDisplayName).Assign(sDisplayName))) goto exit; } else { // can't resolve hr = S_FALSE; if (dwFlags != CACHEIMP_CREATE_RESOLVE_REF_EX) goto exit; } } if (dwFlags == CACHEIMP_CREATE_RETRIEVE_EXIST || dwFlags == CACHEIMP_CREATE_RETRIEVE_EXIST_COMPLETED || (hr == S_FALSE && dwFlags == CACHEIMP_CREATE_RESOLVE_REF_EX)) { // make the name anyway if resolving a ref that does not have any completed cache counterpart // BUGBUG: this may no longer be necessary if shortcut code/UI changes - it's expecting a path // plus this is inefficient as it searchs the disk at above, even if ref is fully qualified if (FAILED(hr = pAssemblyIdentity->GetDisplayName(ASMID_DISPLAYNAME_NOMANGLING, &pwzBuf, &dwCC))) goto exit; hr = sDisplayName.TakeOwnership(pwzBuf, dwCC); // BUGBUG - make GetDisplayName call getassemblyid/getdisplayname instead if (FAILED(hr = (pAssemblyCache->_sDisplayName).Assign(sDisplayName))) goto exit; } // Note: this will prepare for delay initializing _pManifestImport if (FAILED(hr = (pAssemblyCache->_sManifestFileDir).Assign(pAssemblyCache->_sRootDir))) goto exit; // build paths if (FAILED(hr = (pAssemblyCache->_sManifestFileDir).Append(sDisplayName))) goto exit; if (dwFlags == CACHEIMP_CREATE_RETRIEVE_EXIST ||dwFlags == CACHEIMP_CREATE_RETRIEVE_EXIST_COMPLETED) { // simple check if dir is in cache or not if (GetFileAttributes((pAssemblyCache->_sManifestFileDir)._pwz) == (DWORD)-1) { // cache dir not exists hr = S_FALSE; goto exit; } } if (dwFlags == CACHEIMP_CREATE_RETRIEVE_EXIST_COMPLETED) { if (!(pAssemblyCache ->IsStatus (sDisplayName._pwz, CAssemblyCache::COMPLETED))) { // cache dir not completed hr = S_FALSE; goto exit; } } if (FAILED(hr = (pAssemblyCache->_sManifestFileDir).Append(L"\\"))) goto exit; if (FAILED(hr = (pAssemblyCache->_sManifestFilePath).Assign(pAssemblyCache->_sManifestFileDir))) goto exit; if (FAILED(hr = (pAssemblyCache->_sManifestFilePath).Append(sManifestFilename))) goto exit; } exit: SAFEDELETE(pwzSearchDisplayName); if (bNewAsmId) SAFERELEASE(pAssemblyIdentity); if (FAILED(hr) || hr == S_FALSE) { // hr == S_FALSE for not found SAFERELEASE(pAssemblyCache); } *ppAssemblyCacheImport = static_cast (pAssemblyCache); return hr; } // --------------------------------------------------------------------------- // CreateAssemblyCacheEmit // --------------------------------------------------------------------------- STDAPI CreateAssemblyCacheEmit( LPASSEMBLY_CACHE_EMIT *ppAssemblyCacheEmit, LPASSEMBLY_CACHE_EMIT pAssemblyCacheEmit, DWORD dwFlags) { HRESULT hr = S_OK; CAssemblyCache *pAssemblyCache = NULL; pAssemblyCache = new(CAssemblyCache); if (!pAssemblyCache) { hr = E_OUTOFMEMORY; goto exit; } hr = pAssemblyCache->Init(static_cast (pAssemblyCacheEmit)); if (FAILED(hr)) { SAFERELEASE(pAssemblyCache); goto exit; } exit: *ppAssemblyCacheEmit = static_cast (pAssemblyCache); return hr; } // --------------------------------------------------------------------------- // FindVersionInDisplayName // --------------------------------------------------------------------------- LPCWSTR FindVersionInDisplayName(LPCWSTR pwzDisplayName) { int cNumUnderscoreFromEndToVersionString = 2; int count = 0; int ccLen = lstrlen(pwzDisplayName); LPWSTR pwz = (LPWSTR) (pwzDisplayName+ccLen-1); LPWSTR pwzRetVal = NULL; // return a pointer to the start of Version string inside a displayName while (*pwz != NULL && pwz > pwzDisplayName) { if (*pwz == L'_') count++; if (count == cNumUnderscoreFromEndToVersionString) break; pwz--; } if (count == cNumUnderscoreFromEndToVersionString) pwzRetVal = ++pwz; return pwzRetVal; } // --------------------------------------------------------------------------- // CompareVersion // --------------------------------------------------------------------------- int CompareVersion(LPCWSTR pwzVersion1, LPCWSTR pwzVersion2) { // BUGBUG: this should compare version by its major minor build revision! // possible break if V1=10.0.0.0 and V2=2.0.0.0? // plus pwzVersion1 is something like "1.0.0.0_en" return wcscmp(pwzVersion1, pwzVersion2); } // --------------------------------------------------------------------------- // SearchForHighestVersionInCache // Look for a copy in cache that has the highest version and the specified status // pwzSearchDisplayName should really be created from a partial ref // // return: S_OK - found a version from the ref // S_FALSE - not found any version from the ref, or // ref not partial and that version is not there/not in that status // E_* // --------------------------------------------------------------------------- HRESULT SearchForHighestVersionInCache(LPWSTR *ppwzResultDisplayName, LPWSTR pwzSearchDisplayName, CAssemblyCache::CacheStatus eCacheStatus, CAssemblyCache* pCache) { HRESULT hr = S_OK; HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA fdAppDir; DWORD dwLastError = 0; BOOL fFound = FALSE; CString sDisplayName; CString sSearchPath; *ppwzResultDisplayName = NULL; sDisplayName.Assign(pwzSearchDisplayName); if (FAILED(hr=sSearchPath.Assign(pCache->_sRootDir))) goto exit; if (FAILED(hr=sSearchPath.Append(sDisplayName))) goto exit; hFind = FindFirstFileEx(sSearchPath._pwz, FindExInfoStandard, &fdAppDir, FindExSearchLimitToDirectories, NULL, 0); if (hFind == INVALID_HANDLE_VALUE) { hr = S_FALSE; goto exit; } while (dwLastError != ERROR_NO_MORE_FILES) { // ???? check file attribute to see if it's a directory? needed only if the file system does not support the filter... // ???? check version string format? if (pCache->IsStatus(fdAppDir.cFileName, eCacheStatus)) { int iRetVal = CompareVersion(FindVersionInDisplayName(fdAppDir.cFileName), FindVersionInDisplayName(sDisplayName._pwz)); if (iRetVal > 0) { sDisplayName.Assign(fdAppDir.cFileName); fFound = TRUE; } else if (iRetVal == 0) fFound = TRUE; // else keep the newest } if (!FindNextFile(hFind, &fdAppDir)) { dwLastError = GetLastError(); continue; } } if (fFound) { *ppwzResultDisplayName = sDisplayName._pwz; sDisplayName.ReleaseOwnership(); hr = S_OK; } else hr = S_FALSE; exit: if (hFind != INVALID_HANDLE_VALUE) { if (!FindClose(hFind)) { // can return 0, even when there's an error. DWORD dw = GetLastError(); hr = dw ? HRESULT_FROM_WIN32(dw) : E_FAIL; } } return hr; } // --------------------------------------------------------------------------- // ctor // --------------------------------------------------------------------------- CAssemblyCache::CAssemblyCache() : _dwSig('hcac'), _cRef(1), _hr(S_OK), _pManifestImport(NULL) {} // --------------------------------------------------------------------------- // dtor // --------------------------------------------------------------------------- CAssemblyCache::~CAssemblyCache() { SAFERELEASE(_pManifestImport); } // --------------------------------------------------------------------------- // Init // --------------------------------------------------------------------------- HRESULT CAssemblyCache::Init(CAssemblyCache *pAssemblyCache) { if (!pAssemblyCache) GetCacheRootDir(_sRootDir, Base); else _sRootDir.Assign(pAssemblyCache->_sManifestFileDir); return S_OK; } // --------------------------------------------------------------------------- // GetManifestFilePath // --------------------------------------------------------------------------- HRESULT CAssemblyCache::GetManifestFilePath(LPOLESTR *ppwzManifestFilePath, LPDWORD pccManifestFilePath) { CString sPathOut; sPathOut.Assign(_sManifestFilePath); *ppwzManifestFilePath = sPathOut._pwz; *pccManifestFilePath = sPathOut._cc; sPathOut.ReleaseOwnership(); return S_OK; } // --------------------------------------------------------------------------- // GetManifestFileDir // --------------------------------------------------------------------------- HRESULT CAssemblyCache::GetManifestFileDir(LPOLESTR *ppwzManifestFileDir, LPDWORD pccManifestFileDir) { CString sDirOut; sDirOut.Assign(_sManifestFileDir); *ppwzManifestFileDir = sDirOut._pwz; *pccManifestFileDir = sDirOut._cc; sDirOut.ReleaseOwnership(); return S_OK; } // --------------------------------------------------------------------------- // GetManifestImport // --------------------------------------------------------------------------- HRESULT CAssemblyCache::GetManifestImport(LPASSEMBLY_MANIFEST_IMPORT *ppManifestImport) { *ppManifestImport = _pManifestImport; (*ppManifestImport)->AddRef(); return S_OK; } // --------------------------------------------------------------------------- // GetDisplayName // --------------------------------------------------------------------------- HRESULT CAssemblyCache::GetDisplayName(LPOLESTR *ppwzDisplayName, LPDWORD pccDiaplyName) { CString sDisplayNameOut; sDisplayNameOut.Assign(_sDisplayName); *ppwzDisplayName = sDisplayNameOut._pwz; *pccDiaplyName= sDisplayNameOut._cc; sDisplayNameOut.ReleaseOwnership(); return S_OK; } // --------------------------------------------------------------------------- // FindExistMatching // return: // S_OK // S_FALSE -not exist or not match // E_* // --------------------------------------------------------------------------- HRESULT CAssemblyCache::FindExistMatching(LPASSEMBLY_FILE_INFO pAssemblyFileInfo, LPOLESTR *ppwzPath) { LPWSTR pwzBuf = NULL; DWORD ccBuf = 0; CString sFileName; CString sTargetPath; LPASSEMBLY_FILE_INFO pFoundFileInfo = NULL; if (pAssemblyFileInfo == NULL || ppwzPath == NULL) { _hr = E_INVALIDARG; goto exit; } *ppwzPath = NULL; if (_pManifestImport == NULL) { if (_sManifestFilePath._cc == 0) { // no manifest path _hr = CO_E_NOTINITIALIZED; goto exit; } // lazy init if (FAILED(_hr = CreateAssemblyManifestImport(&_pManifestImport, _sManifestFilePath._pwz))) goto exit; } // file name parsed from manifest. if (FAILED(_hr = pAssemblyFileInfo->Get(ASM_FILE_NAME, &pwzBuf, &ccBuf))) goto exit; sFileName.TakeOwnership(pwzBuf, ccBuf); if (FAILED(_hr = sTargetPath.Assign(_sManifestFileDir))) goto exit; if (FAILED(_hr = sTargetPath.Append(sFileName._pwz))) goto exit; // optimization: check if the target exists if (GetFileAttributes(sTargetPath._pwz) == (DWORD)-1) { // file doesn't exist - no point looking into the manifest file _hr = S_FALSE; goto exit; } // find the specified file entry in the manifest // BUGBUG: check for missing attribute case if (FAILED(_hr = _pManifestImport->QueryFile(sFileName._pwz, &pFoundFileInfo)) || _hr == S_FALSE) goto exit; // check if the entries match if (IsEqualAssemblyFileInfo(pAssemblyFileInfo, pFoundFileInfo)) { // BUGBUG:? should now check if the actual file has the matching hash etc. *ppwzPath = sTargetPath._pwz; sTargetPath.ReleaseOwnership(); } else _hr = S_FALSE; exit: SAFERELEASE(pFoundFileInfo); return _hr; } // --------------------------------------------------------------------------- // CopyFile // --------------------------------------------------------------------------- HRESULT CAssemblyCache::CopyFile(LPOLESTR pwzSourcePath, LPOLESTR pwzFileName, DWORD dwFlags) { LPWSTR pwzBuf = NULL; DWORD ccBuf = 0; CString sDisplayName; LPASSEMBLY_MANIFEST_IMPORT pManifestImport = NULL; LPASSEMBLY_IDENTITY pIdentity = NULL; LPASSEMBLY_FILE_INFO pAssemblyFile= NULL; if (dwFlags == MANIFEST) { DWORD n = 0; CreateAssemblyManifestImport(&pManifestImport, pwzSourcePath); pManifestImport->GetAssemblyIdentity(&pIdentity); pIdentity->GetDisplayName(ASMID_DISPLAYNAME_NOMANGLING, &pwzBuf, &ccBuf); sDisplayName.TakeOwnership(pwzBuf, ccBuf); _sDisplayName.Assign(sDisplayName); // Construct target path _sManifestFileDir.Assign(_sRootDir); _sManifestFileDir.Append(sDisplayName); _sManifestFileDir.Append(L"\\"); _sManifestFilePath.Assign(_sManifestFileDir); _sManifestFilePath.Append(pwzFileName); CreateDirectoryHierarchy(NULL, _sManifestFilePath._pwz); // Copy the manifest from staging area into cache. ::CopyFile(pwzSourcePath, _sManifestFilePath._pwz, FALSE); SAFERELEASE(pManifestImport); ::DeleteFile(pwzSourcePath); // Create the manifest import interface CreateAssemblyManifestImport(&_pManifestImport, _sManifestFilePath._pwz); // Enumerate files from manifest and pre-generate nested // directories required for background file copy. while (_pManifestImport->GetNextFile(n++, &pAssemblyFile) == S_OK) { CString sPath; pAssemblyFile->Get(ASM_FILE_NAME, &pwzBuf, &ccBuf); sPath.TakeOwnership(pwzBuf, ccBuf); sPath.PathNormalize(); CreateDirectoryHierarchy(_sManifestFileDir._pwz, sPath._pwz); // RELEASE pAssebmlyFile everytime through the while loop SAFERELEASE(pAssemblyFile); } } else { CString sTargetPath; // Construct target path sTargetPath.Assign(_sManifestFileDir); sTargetPath.Append(pwzFileName); CreateDirectoryHierarchy(NULL, sTargetPath._pwz); // Copy non-manifest files into cache. Presumably from previous cached location to the new ::CopyFile(pwzSourcePath, sTargetPath._pwz, FALSE); } if (pIdentity) pIdentity->Release(); return _hr; } // --------------------------------------------------------------------------- // Commit // --------------------------------------------------------------------------- HRESULT CAssemblyCache::Commit(DWORD dwFlags) { if (!(_sDisplayName._pwz)) { _hr = E_FAIL; goto exit; } // mark cache completed _hr = SetStatus(_sDisplayName._pwz, COMPLETED); exit: return _hr; } #define REG_KEY_FUSION_SETTINGS TEXT("Software\\Microsoft\\Fusion\\Installer\\1.0.0.0\\Cache\\") #define WZ_STATUS_CONFIRMED L"Confirmed" #define WZ_STATUS_COMPLETED L"Complete" #define WZ_STATUS_CRITICAL L"Critical" #define SET_VALUE 1 // --------------------------------------------------------------------------- // IsStatus // return FALSE if absent // --------------------------------------------------------------------------- BOOL CAssemblyCache::IsStatus(LPWSTR pwzDisplayName, CacheStatus eStatus) { CString sStatus; HUSKEY hRegKey; DWORD dwType = 0; DWORD dwValue = -1; DWORD dwSize = sizeof(dwValue); DWORD dwDefault = -1; LPCWSTR pwzQueryString = NULL; BOOL bRelVal = FALSE; if (eStatus == COMPLETED) pwzQueryString = WZ_STATUS_COMPLETED; else if (eStatus == CONFIRMED) pwzQueryString = WZ_STATUS_CONFIRMED; else if (eStatus == CRITICAL) pwzQueryString = WZ_STATUS_CRITICAL; else { _hr = E_INVALIDARG; goto exit; } if (FAILED(_hr=sStatus.Assign(REG_KEY_FUSION_SETTINGS))) goto exit; if (FAILED(_hr=sStatus.Append(pwzDisplayName))) goto exit; // Open registry entry if (SHRegOpenUSKey(sStatus._pwz, KEY_ALL_ACCESS, NULL, &hRegKey, FALSE) != ERROR_SUCCESS) { _hr = E_FAIL; goto exit; } // Query if(SHRegQueryUSValue(hRegKey, pwzQueryString, &dwType, (LPVOID) &dwValue, &dwSize, FALSE, (LPVOID) &dwDefault, sizeof(dwDefault)) != ERROR_SUCCESS) _hr = E_FAIL; if (dwValue == SET_VALUE) bRelVal = TRUE; if(SHRegCloseUSKey(hRegKey) != ERROR_SUCCESS) _hr = E_FAIL; exit: return bRelVal; } // --------------------------------------------------------------------------- // SetStatus // --------------------------------------------------------------------------- HRESULT CAssemblyCache::SetStatus(LPWSTR pwzDisplayName, CacheStatus eStatus) { CString sStatus; HUSKEY hRegKey; DWORD dwValue = SET_VALUE; LPCWSTR pwzValueNameString = NULL; // BUGBUG: should this be in-sync with what server does to register update? if (eStatus == COMPLETED) pwzValueNameString = WZ_STATUS_COMPLETED; else if (eStatus == CONFIRMED) pwzValueNameString = WZ_STATUS_CONFIRMED; else if (eStatus == CRITICAL) pwzValueNameString = WZ_STATUS_CRITICAL; else { _hr = E_INVALIDARG; goto exit; } if (FAILED(_hr=sStatus.Assign(REG_KEY_FUSION_SETTINGS))) goto exit; if (FAILED(_hr=sStatus.Append(pwzDisplayName))) goto exit; // Create registry entry if (SHRegCreateUSKey(sStatus._pwz, KEY_ALL_ACCESS, NULL, &hRegKey, SHREGSET_FORCE_HKCU) != ERROR_SUCCESS) //SHREGSET_DEFAULT for strongly named apps? { _hr = E_FAIL; goto exit; } // Write if (SHRegWriteUSValue(hRegKey, pwzValueNameString, REG_DWORD, (LPVOID) &dwValue, sizeof(dwValue), SHREGSET_FORCE_HKCU) != ERROR_SUCCESS) _hr = E_FAIL; if (SHRegCloseUSKey(hRegKey) != ERROR_SUCCESS) _hr = E_FAIL; exit: return _hr; } // --------------------------------------------------------------------------- // CreateDirectoryHierarchy // --------------------------------------------------------------------------- HRESULT CAssemblyCache::CreateDirectoryHierarchy(LPWSTR pwzRootDir, LPWSTR pwzFilePath) { LPWSTR pwzPath, pwzEnd; CString sCombinedPath; if (pwzRootDir) sCombinedPath.Assign(pwzRootDir); sCombinedPath.Append(pwzFilePath); pwzPath = sCombinedPath._pwz; pwzEnd = pwzPath + sizeof("C:\\"); while (pwzEnd = StrChr(pwzEnd, L'\\')) { *pwzEnd = L'\0'; if (GetFileAttributes(pwzPath) == -1) CreateDirectory(pwzPath, NULL); *(pwzEnd++) = L'\\'; } return S_OK; } // --------------------------------------------------------------------------- // GetCacheRootDir // --------------------------------------------------------------------------- HRESULT CAssemblyCache::GetCacheRootDir(CString &sCacheDir, CacheFlags eFlags) { HRESULT hr = S_OK; WCHAR wzPath[MAX_PATH]; // BUGBUG?: MAX_PATH restriction. Seems reasonable in this case? if(GetEnvironmentVariable(L"ProgramFiles", wzPath, MAX_PATH/*-lstrlen(WZ_CACHE_LOCALROOTDIR)*/-1) == 0) { hr = CO_E_PATHTOOLONG; goto exit; } if (FAILED(hr = sCacheDir.Assign(wzPath))) goto exit; if (FAILED(hr = sCacheDir.PathCombine(WZ_CACHE_LOCALROOTDIR))) goto exit; if (eFlags == Staging) hr = sCacheDir.PathCombine(WZ_MANIFEST_STAGING_DIR); exit: return hr; } // IUnknown methods // --------------------------------------------------------------------------- // CAssemblyCache::QI // --------------------------------------------------------------------------- STDMETHODIMP CAssemblyCache::QueryInterface(REFIID riid, void** ppvObj) { if ( IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IAssemblyCacheImport) ) { *ppvObj = static_cast (this); AddRef(); return S_OK; } else if (IsEqualIID(riid, IID_IAssemblyCacheEmit)) { *ppvObj = static_cast (this); AddRef(); return S_OK; } else { *ppvObj = NULL; return E_NOINTERFACE; } } // --------------------------------------------------------------------------- // CAssemblyCache::AddRef // --------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CAssemblyCache::AddRef() { return InterlockedIncrement ((LONG*) &_cRef); } // --------------------------------------------------------------------------- // CAssemblyCache::Release // --------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CAssemblyCache::Release() { ULONG lRet = InterlockedDecrement ((LONG*) &_cRef); if (!lRet) delete this; return lRet; }