Using the .NET Fusion API to Manipulate the GAC

Developers who have used the wonders of the CLR and native code, whether via Managed C++, C++/CLI, or some other managed language, know the Global Assembly Cache (GAC) well. On a recent development project for a client, I needed to add an existing assembly to the GAC. The assembly, which shipped with Microsoft Office 2003, defined a custom code group that allowed shared drives on network servers to be trusted for documents that used Visual Studio Tools for Office (VSTO) functionality (the default security policy allows only local Office documents to use VSTO).

This MSDN document describes the custom code group and how to use it. It specifies two ways to add the Microsoft Office assembly to the GAC:

  • Using the .NET Configuration Utility
  • Using the GACUTIL Framework SDK tool

The .NET Configuration Utility requires administrative privileges to add an assembly to the GAC, and the GACUTIL is available only when the Framework SDK is installed. A machine with the .NET Framework but without the SDK won’t have GACUTIL. Based on the advice in the MSDN article, a developer would have to make every potential user of a VSTO application into a local administrator (at least temporarily) or get the Framework SDK installed so GACUTIL can be called from an MSI package, which runs under the privileges of an administrator. Thankfully, the C++ developer has a third option: programmatically modifying the GAC with the Fusion APIs.

Fusion is the code name Microsoft has given the .NET Framework sub-system responsible for locating and loading assemblies. The GAC falls under Fusion’s area of responsibility, and the GAC manipulation methods are exposed via COM interfaces implemented in fusion.dll. For those thinking that COM interop is generally easy because type libraries can be used to generate managed wrappers without the need to manually define interoperability method signatures, Fusion throws added complexity into the mix by shipping without a type library. The great Microsoft Fusion blogger Junfeng Zhang explains the absence of the type library in this post. Thankfully, Microsoft has documented the GAC functions and interfaces in KB Article 317540.

Dusting off one of the great command-line tools of the COM era, the IDL code from the KB article can be pasted into a local IDL file and compiled with the Microsoft IDL (MIDL) compiler to produce a C++ header file. This file then can be #included in any C++ project. To allow developers not lucky enough to be fluent in C++ to add assemblies to the GAC programmatically, a Managed C++ assembly will be developed to wrap and expose the Fusion APIs. Using Managed C++ rather than C++/CLI allows the wrapper to be used on systems with .NET 1.1 and .NET 2.0 installed.

Rather than use CoCreateInstance, the Fusion COM class implementing the GAC manipulation interface is created with a C-style DLL call to Fusion (the method signature for this function is defined in the KB article). The first tasks are to use a typedef to make the C-style function pointer that will be loaded from fusion.dll easier to use, and to define two global variables to hold the module handle and function pointer from Fusion:

typedef HRESULT (__stdcall *CreateAsmCache)(IAssemblyCache
   **ppAsmCache, DWORD dwReserved);

HMODULE g_FusionDll;
CreateAsmCache g_pfnCreateAssemblyCache;

These global variables can be populated from within a method that will be called from the exposed managed type’s static constructor:

void InitFunctionPointers(){
   LoadLibraryShim(L"fusion.dll", 0, 0, &g_FusionDll);
   g_pfnCreateAssemblyCache =
      (CreateAsmCache)GetProcAddress(g_FusionDll,
          "CreateAssemblyCache");
}

All error conditions from the managed assembly should be expressed via exceptions. However, to allow the flexibility to communicate non-fatal issues in the future, a managed enum that currently has only a single value indicating success will be defined:

public __value enum InstallResult {
   OK,
};

After all the build-up, the managed type that exposes the ability to add an assembly to the GAC is quite unremarkable and simple:

public __gc class Installer
{
   public:
   static Installer(){
      InitFunctionPointers();
   }

   static InstallResult InstallAssembly(String* fileName){
      CComPtr<IAssemblyCache> pCache = NULL;
      HRESULT hr = g_pfnCreateAssemblyCache(&pCache, 0);

      if (hr == S_OK){
         const __wchar_t __pin * wFileName =
            PtrToStringChars(fileName);
         hr = pCache->InstallAssembly(0, wFileName, NULL);

         if (hr == S_OK){
            return InstallResult::OK;
      }
   }

   System::Runtime::InteropServices::Marshal::ThrowExceptionForHR(hr);
   }
};

A few points about the type are worth highlighting:

  • ATL smart-pointers are used to cut down the tedious AddRef and Release calls needed by COM.
  • The PtrToStringChars helper method defined in vcclr.h is used to convert the managed System::String to a C-style string.
  • The Marshal::ThrowExceptionForHR method is used to translate between the HRESULT COM world and the System::Exception .NET world.

C++ Saves Time and Effort

The entire source code file for the managed wrapper for installing an assembly in the GAC, including white space, typedefs, pre-processor directives, and production-ready error handling comes in at exactly fifty lines. Compared with the equivalent time and effort required to accomplish this task from managed code-only languages such as C# and Visual Basic .NET, C++ clearly comes out on top.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read