In this post I'll show how you can inject your own .NET code into a process that is already running the .NET framework.

I needed to do this when developing for Windows Media Center because there is no official documented way to determine what page is currently being displayed in Media Center.

I wanted to know when the user is showing the Media Center "now playing" page.

Media Center addins run in a completely separate process to the main Media Center process, which is ehshell.exe.  Calls you make in your addin are remoted across to the Media Center ehshell.exe process.

The solution works using:

  • an "Injecter" C# class  which uses CreateRemoteThread to load a Bootstrap DLL into the ehshell.exe process
  • The "Bootstrap" C++ DLL which is injected into the ehshell.exe process uses the CLR Hosting API to attach to the default .NET domain, and then load an instance of the "Injectee" C# class
  • The "Injectee" C# class is loaded by the "Bootstrap" DLL and runs within the ehshell.exe process.  It uses reflection to subscribe to an event within the ehshell.exe process and then sends an interprocess message when the event fires
  • </ul>

    The "Injecter"

    Using Reflector, I looked around at the ehshell.exe assembly, and related assemblies, and eventually discovered that there is a PageChanged event fired by the ServiceBus.UIFramework.PageBasedUCPService class:

    image 

    What is more, there is a static PageBasedUCPService.DefaultUCP method to return a PageBasedUCPService instance.

    There are of course a couple of problems.  The first problem being that these classes are internal to the ehshell assembly,  the second issue being that these are all running in a totally separate process to my addin.

    I searched a little, and came across this example showing how you could use the CreateRemoteThread from .NET to inject your own native code into a remote process.

    Essentially it opens a handle to the remote process, finds the LoadLibrary system call's address in the remote process, allocates a chunk of memory in the process, and fills the memory with a LoadLibrary call to load a DLL that you specify, and then creates the thread in the remote process, passing the address of the chunk of memory as the starting point for the thread.

    I recommend taking a look at the CRT call in the example.  I'll not be duplicating the code here.

    To use the CRT call, I first find Media Center:

    public static Process FindMediaCenterProcess() {
      // Could be more than one if an extender is running too
      foreach (Process process in Process.GetProcessesByName("ehshell")) {
        if (process.SessionId == Process.GetCurrentProcess().SessionId) {
          return process;
        }
      }
      return null;
    }

    I can then call the CRT method:

    Process mediaCenterProcess = FindMediaCenterProcess();
    IntPtr hwnd;
    string error;
    CRT(mediaCenterProcess, dllPath, out error, out hwnd);
    

    The dllPath is the full path to my C++ DLL, in my case this was

    Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
                                      @"\Atadore\MceFM\InjectBootstrap.dll"
     

    The "Boostrap DLL"

    The InjectBootstrap.dll is a pretty simple C++ library project.  The DllMain calls a bootstrap function when the process is loaded:

    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
    {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
            Bootstrap();
            break;
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }
    
     
    My bootstrap function attaches to the default .NET domain and loads an instance of one of my own classes:
     
    #include "stdafx.h"
    #include 
    #include "objbase.h"
    #include "MSCorEE.h"
    #import "C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb" raw_interfaces_only
    using namespace mscorlib;
    
    void Bootstrap() {
        OutputDebugString(L"MceFM Bootstrap Started");
    
        CoInitializeEx(0, COINIT_MULTITHREADED );
        ICorRuntimeHost* pICorRuntimeHost = 0;
        HRESULT st = CoCreateInstance(CLSID_CorRuntimeHost, 0, CLSCTX_ALL,
            IID_ICorRuntimeHost, (void**)&pICorRuntimeHost);
        if(!pICorRuntimeHost) return; // Clean up and log errror ...
    
        HDOMAINENUM hEnum = NULL;
        pICorRuntimeHost->EnumDomains(&hEnum);
    
        if(!hEnum) return; // Clean up and log errror ...
    
        IUnknown* pUunk = 0;
        st = pICorRuntimeHost->NextDomain(hEnum, &pUunk);
        if(!pUunk) return; // Clean up and log errror ...
    
        _AppDomain * pCurDomain = NULL;
        st = pUunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCurDomain);
        if(!pCurDomain) return; // Clean up and log errror ...
    
        _bstr_t assemblyName =
            "Last, Version=1.1.0.0, Culture=neutral, PublicKeyToken=792d614cdf38e9ce";
        _bstr_t typeName = "MceFM.Last.Inject.Injectee";
    
        _ObjectHandle* pObjectHandle = 0;
    
        pCurDomain->CreateInstance(assemblyName, typeName, &pObjectHandle);
    
    }</pre></div>
    
     

    The "Injectee"

    Finally we come to the Injectee class, which is loaded by the DLL, and runs inside the remote (ehshell.exe) process.  It is in a strongly named assembly which is stored in the GAC, so that the bootstrap call above can find it (note the strong name in the assembly name bstr).

    I use a static constructor (which runs as soon as the class is loaded).  In this static constructor I subscribe to the event:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Net;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Threading;
    
    namespace MceFM.Last.Inject {
      public class Injectee {
    
        static Injectee() {
          try {
            Assembly ehshell = Assembly.Load("ehshell");
    
            // Get the PageBasedUCPService type
            Type pageBasedUCPServiceType =
              ehshell.GetType("ServiceBus.UIFramework.PageBasedUCPService");
    
            // Call static DefaultUCP method to get a PageBasedUCPService instance
            const BindingFlags bindingFlags = BindingFlags.DeclaredOnly |
              BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty;
            object[] args = new object[0];
            object defaultUCP = pageBasedUCPServiceType.InvokeMember("DefaultUCP",
              bindingFlags, null, null, args);
    
            // Get a reference to the PageChanged event
            EventInfo pageChangedEventInfo =
              pageBasedUCPServiceType.GetEvent("PageChanged");
            Type pageChangedDelegate = pageChangedEventInfo.EventHandlerType;
    
            // Get the MethodInfo for the method to be called when the event fires
            MethodInfo newPageHandlerMethodInfo =
              typeof(Injectee).GetMethod("NewPageHandler");
    
            // Build an array of types used for new method we shall dynamically emit
            Type pageType = ehshell.GetType("ServiceBus.UIFramework.Page");
            Type[] pageChangedDelegateParameters = new[] { pageType };
    
            // Need a dynamic method because we can't create a method that has
            // the Page type as a parameter
            DynamicMethod dynamicHandler = new DynamicMethod("", null,
              pageChangedDelegateParameters, typeof(Injectee));
            ILGenerator ilgen = dynamicHandler.GetILGenerator();
            ilgen.Emit(OpCodes.Nop);
            ilgen.Emit(OpCodes.Ldarg_0);
            ilgen.Emit(OpCodes.Call, newPageHandlerMethodInfo);
            ilgen.Emit(OpCodes.Nop);
            ilgen.Emit(OpCodes.Ret);
    
            if(pageChangedDelegate != null) {
              // Subscribe to the event
              Delegate dEmitted = dynamicHandler.CreateDelegate(pageChangedDelegate);
              pageChangedEventInfo.GetAddMethod().Invoke(defaultUCP,
                                                         new object[] {dEmitted});
            }
          } catch(Exception ex) {
            Trace.TraceError("Unexpected error in Injectee initializer: {0}", ex);
            Trace.TraceError(ex.StackTrace);
          }
        }
    
        // Queue of new pages that have been navigated to.  Decouples event handler
        // from re-despatching of the event to the addin process that is told
        // of the new event.
        private static readonly Queue<string> newPages = new Queue<string>();
    
        // Indicates whether background thread that despatches events is running
        private static bool notifierActive;
    
        // Called when Media Center navigates to a new page
        public static void NewPageHandler(object page) {
          Trace.TraceInformation("New Page: " + page);
          lock(newPages) {
            if(!notifierActive) {
              Thread thread = new Thread(NewPageNotifier) {IsBackground = true};
              thread.Start();
              notifierActive = true;
            }
            newPages.Enqueue(page.GetType().FullName);
            Monitor.Pulse(newPages);
          }
        }
    
        // Thread that despatches events to addin process telling it that
        // a new page has been navigated to
        private static void NewPageNotifier() {
          while(true) {
            string page;
    
            lock(newPages) {
              while(newPages.Count == 0) {
                Monitor.Wait(newPages);
              }
              page = newPages.Dequeue();
            }
    
            try {
              // Use whatever interprocess notification mechanism you wish.
              // WCF could be good.  Here I use a simple web call.  My
              // addin has a web server in it to receive these calls.
              WebClient webClient = new WebClient();
              webClient.QueryString[Server.PAGE_QUERY_STRING] = page;
              webClient.DownloadString(Util.LocalBaseUrl
                                       + Server.MCE_PAGE_CHANGED_ACTION);
            } catch (Exception ex) {
              Trace.TraceError("Error while sending new page notification: {0}", ex);
            }
          }
    #pragma warning disable FunctionNeverReturns
        }
    #pragma warning restore FunctionNeverReturns
      }
    }
     

    The Result

    In my web server running within my addin (which is notified by the "NewPageNotifier" method above), I keep track of which is the current page, and log information when the page changes:

     
    Server notified of page change MediaCenter.Audio.AudioNowPlayingPage
    Server notified of page change MediaCenter.Audio.AudioNowPlayingTracklistPage
    

    All this means that if you are a Last.fm user and want to tell Last.fm that you love a track that is currently playing then you can press the right-button on your remote control on the "now playing" page using my MceFM addin.

    I use low level system hooks to know when you press the right button, and the above technique to know that you are on the "now playing" page:

    image

    Word if warning.  If/When Media Center changes its internal structure this technique will completely fall over.  Use sparingly, entirely at your own risk,  to do the seemingly impossible.