Click here to Skip to main content
15,885,278 members
Articles / Desktop Programming / ATL

Shell Extensions for .NET Assemblies

Rate me:
Please Sign up or sign in to vote.
4.98/5 (29 votes)
17 Nov 2005Ms-PL3 min read 184K   2.6K   81  
Shell extensions to distinguish between .NET assemblies and Win32 applications and libraries.
// AssemblyInfo.cpp : Implementation of CAssemblyInfo

#include "stdafx.h"
#include "resource.h"
#include "AssemblyInfo.h"
#include <oleauto.h>

#define MAKEPTR(cast, ptr, addValue) (cast)((DWORD_PTR)(ptr) + (DWORD_PTR)(addValue))

// Define the char array for hex characters.
const WCHAR c_szHex[] = L"0123456789abcdefg";

// CAssemblyInfo

// Returns success codes of S_OK and S_FALSE in place of the optional boolean return
// value, or an error code and IErrorInfo otherwise.
STDMETHODIMP CAssemblyInfo::IsAssembly(BSTR path, VARIANT_BOOL* retVal)
{
	HRESULT hr;
	FILEINFO FileInfo = { FILEINFOTYPE_CORHEADER };

	hr = GetFileInfo(path, &FileInfo);

	// Don't err when retVal is NULL because
	// internal implementation may pass NULL.
	if (SUCCEEDED(hr) && retVal)
	{
		*retVal = FileInfo.varfCorHeader;
	}

	return hr;
}

STDMETHODIMP CAssemblyInfo::GetFileType(BSTR path, FileType* retVal)
{
	CW2TEX<MAX_PATH> szPath(path);
	FILEINFO FileInfo = { FILEINFOTYPE(FILEINFOTYPE_CORHEADER | FILEINFOTYPE_MACHINEARCH) };
	FileType ft = Other; // Default to Other.
	HRESULT hr = NOERROR;
	LPTSTR pszExtension;

	if (path && retVal)
	{
		// Check the extension first to boost performance.
		pszExtension = PathFindExtension(szPath);
		if (_tcsicmp(pszExtension, TEXT(".dll")) == 0)
		{
			ft = Win32Library;
		}
		else if (_tcsicmp(pszExtension, TEXT(".exe")) == 0)
		{
			ft = Win32Executable;
		}
		else if (_tcsicmp(pszExtension, TEXT(".netmodule")) == 0)
		{
			ft = DotNETModule;
		}

		// For EXE, DLL, and MOD get the file information.
		if (Other != ft)
		{
			hr = GetFileInfo(path, &FileInfo);
			if (SUCCEEDED(hr))
			{
				// If this is a managed assembly and not a .netmodule increment the FileType
				// by the relative offset for managed assemblies.
				if (DotNETModule != ft && VARIANT_TRUE == FileInfo.varfCorHeader)
					ft = FileType(ft + OFFSET_DOTNET);

				// If the file targets IA64 increment relative offset for IA64.
				if (IMAGE_FILE_MACHINE_IA64 == FileInfo.wMachineArch)
					ft = FileType(ft + OFFSET_IA64);
				// Else if the file targets X64 increment by relative offset for X64.
				else if (IMAGE_FILE_MACHINE_AMD64 == FileInfo.wMachineArch)
					ft = FileType(ft + OFFSET_X64);
			}
		}

		*retVal = ft;
	}
	else
	{
		hr = E_POINTER;
	}

	return hr;
}

STDMETHODIMP CAssemblyInfo::GetPublicKeyToken(BSTR path, BSTR* retVal)
{
	BYTE b;
	CComBSTR* bstrToken = NULL;
	HRESULT hr;
	LPBYTE pToken = NULL;
	ULONG cbToken = 0;
	ULONG i, j;

	static BOOL fError = FALSE;

	if (path && retVal)
	{
		// Default the return value to NULL.
		*retVal = NULL;

		// First make sure the path points to an assembly.
		hr = IsAssembly(path, NULL);
		if (S_OK == hr)
		{
			try
			{
				if (!fError && g_mscoree.StrongNameTokenFromAssembly(path, &pToken, &cbToken))
				{
					bstrToken = new CComBSTR(cbToken * 2); // 2 chars / byte for hex.

					// Encode the BYTE[] to hexadecimal.
					for (i = 0, j = 0; i < cbToken; i++)
					{
						b = pToken[i];
						bstrToken->m_str[j++] = c_szHex[b >> 4];
						bstrToken->m_str[j++] = c_szHex[b & 0xf];
					}

					// Free the buffer allocated by StrongNameTokenFromAssembly.
					g_mscoree.StrongNameFreeBuffer(pToken);

					bstrToken->CopyTo(retVal);
				}
			}
			catch (...)
			{
				fError = TRUE;
			}
		}
	}
	else
	{
		hr = E_POINTER;
	}

	if (bstrToken)
	{
		delete bstrToken;
		bstrToken = NULL;
	}

	return hr;
}

STDMETHODIMP CAssemblyInfo::GetPublicKey(BSTR path, SAFEARRAY** retVal)
{
	HRESULT hr;
	LPBYTE pKeyBlob = NULL;
	LPBYTE pToken = NULL;
	SAFEARRAY* psa;
	ULONG cbKeyBlob = 0;
	ULONG cbToken = 0;
	LPBYTE pbData = NULL;
	
	static BOOL fError = FALSE;

	if (path && retVal)
	{
		// First make sure the path points to an assembly.
		hr = IsAssembly(path, NULL);
		if (S_OK == hr)
		{
			try
			{
				if (!fError && g_mscoree.StrongNameTokenFromAssemblyEx(path, &pToken, &cbToken, &pKeyBlob, &cbKeyBlob) && cbKeyBlob > 0)
				{
					psa = SafeArrayCreateVector(VT_UI1, 0, cbKeyBlob);
					if (psa)
					{
						// Lock the SA and copy the element data.
						hr = SafeArrayAccessData(psa, (LPVOID*)&pbData);
						if (SUCCEEDED(hr))
						{
							memcpy(pbData, pKeyBlob, (size_t)cbKeyBlob);
							hr = SafeArrayUnaccessData(psa);
							if (SUCCEEDED(hr))
								*retVal = psa;
						}
					}
					else
						hr = E_OUTOFMEMORY;

					// Free the buffers allocated by StrongNameTokenFromAssembly.
					g_mscoree.StrongNameFreeBuffer(pToken);
					g_mscoree.StrongNameFreeBuffer(pKeyBlob);

				}
			}
			catch (...)
			{
				fError = TRUE;
			}
		}
	}
	else
	{
		hr = E_POINTER;
	}

	return hr;
}

HRESULT CAssemblyInfo::GetFileInfo(BSTR path, LPFILEINFO pFileInfo)
{
	CAtlFile file;
	CAtlFileMappingBase map;
	HRESULT hr;
	LPVOID pFile;

	if (path && pFileInfo)
	{
		// Open the file image referenced by the path parameter.
		hr = file.Create(CW2TEX<MAX_PATH>(path), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
			FILE_ATTRIBUTE_READONLY | FILE_FLAG_RANDOM_ACCESS);
		if (FAILED(hr))
			return AtlReportError(GetObjectCLSID(), ERR_FILE_OPEN, IID_IAssemblyInfo, hr);

		// Map the file into memory. The destructor will unmap the file, which will
		// release the file handle when unmapped.
		hr = map.MapFile(file);
		if (FAILED(hr))
		{
			file.Close();
			return AtlReportError(GetObjectCLSID(), ERR_FILE_MAP, IID_IAssemblyInfo, hr);
		}

		pFile = map.GetData();

		// Determine if the COM+ header is found in the file.
		if (0 != (pFileInfo->fit & FILEINFOTYPE_CORHEADER))
		{
			hr = HasCorHeader(pFile);
			if (SUCCEEDED(hr))
			{
				pFileInfo->varfCorHeader = (S_OK == hr) ? VARIANT_TRUE : VARIANT_FALSE;
			}
		}

		// Get the machine architecture from the executable.
		if (SUCCEEDED(hr) && 0 != (pFileInfo->fit & FILEINFOTYPE_MACHINEARCH))
		{
			hr = GetMachineArch(pFile, &pFileInfo->wMachineArch);
		}

		// Close the file and return.
		file.Close();
	}
	else
	{
		hr = E_POINTER;
	}

	return hr;
}

HRESULT CAssemblyInfo::GetNtHeaders(LPVOID pFile, PIMAGE_NT_HEADERS* ppNtHeaders)
{
	HRESULT hr;
	PIMAGE_DOS_HEADER pDosHeader;

	if (pFile && ppNtHeaders)
	{
		// Get the PE header from the file.
		pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(pFile);
		if (IMAGE_DOS_SIGNATURE == pDosHeader->e_magic)
		{
			*ppNtHeaders = MAKEPTR(PIMAGE_NT_HEADERS, pDosHeader, pDosHeader->e_lfanew);
			hr = S_OK;
		}
		else
		{
			// The PE/COFF executable is not supported.
			hr = CO_E_NOT_SUPPORTED;
		}
	}
	else
	{
		hr = E_POINTER;
	}

	return hr;
}

HRESULT CAssemblyInfo::GetComDirectory(PIMAGE_NT_HEADERS pNtHeaders, PIMAGE_DATA_DIRECTORY pComDirectory)
{
	HRESULT hr;

	if (pNtHeaders && pComDirectory)
	{
		// Get the NT headers.
		if (IMAGE_NT_SIGNATURE == pNtHeaders->Signature)
		{
			// Get the data directories from the appropriate optional NT header.
			switch (pNtHeaders->OptionalHeader.Magic)
			{
			case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
				*pComDirectory = reinterpret_cast<PIMAGE_NT_HEADERS32>
					(pNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
				hr = S_OK;
				break;

			case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
				*pComDirectory = reinterpret_cast<PIMAGE_NT_HEADERS64>
					(pNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
				hr = S_OK;
				break;

			default:
				hr = CO_E_NOT_SUPPORTED;
			}
		}
		else
		{
			// The header signature is invalid or the header format is not supported.
			hr = CO_E_NOT_SUPPORTED;
		}
	}
	else
	{
		hr = E_POINTER;
	}

	return hr;
}

HRESULT CAssemblyInfo::HasCorHeader(LPVOID pFile)
{
	HRESULT hr;
	PIMAGE_NT_HEADERS pNtHeaders = NULL;
	IMAGE_DATA_DIRECTORY ComDirectory;

	hr = GetNtHeaders(pFile, &pNtHeaders);
	if (SUCCEEDED(hr))
	{
		hr = GetComDirectory(pNtHeaders, &ComDirectory);
		if (SUCCEEDED(hr))
		{
			// If the VirtualAddress is set, a valid COM+ header is present.
			hr = (ComDirectory.VirtualAddress && ComDirectory.Size) ? S_OK : S_FALSE;
		}
	}

	return hr;
}

HRESULT CAssemblyInfo::GetMachineArch(LPVOID pFile, LPWORD pwMachine)
{
	HRESULT hr;
	PIMAGE_NT_HEADERS pNtHeaders = NULL;

	if (pwMachine)
	{
		hr = GetNtHeaders(pFile, &pNtHeaders);
		if (SUCCEEDED(hr) && pNtHeaders)
		{
			*pwMachine = pNtHeaders->FileHeader.Machine;
		}
	}
	else
	{
		hr = E_POINTER;
	}

	return hr;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer Microsoft
United States United States
Principal Software Engineer currently working on Azure SDKs at Microsoft. My opinions are my own. I work on a number of OSS projects for work and personally in numerous languages including C++, C#, JavaScript, Go, Rust, et. al. See a problem, fix a problem (or at least create an issue)!

Avid outdoor adventurer 🏔️❄️👞🚴‍♂️, husband, father.

Comments and Discussions