Skip to content

Commit

Permalink
Add out-of-process COM server demo (#3908)
Browse files Browse the repository at this point in the history
  • Loading branch information
elinor-fung committed Sep 18, 2020
1 parent 3bbeef4 commit 01234ae
Show file tree
Hide file tree
Showing 26 changed files with 1,092 additions and 0 deletions.
105 changes: 105 additions & 0 deletions core/extensions/OutOfProcCOM/COMRegistration/BasicClassFactory.cs
@@ -0,0 +1,105 @@
using System;
using System.Runtime.InteropServices;

namespace COMRegistration
{
// https://docs.microsoft.com/windows/win32/api/unknwn/nn-unknwn-iclassfactory
[ComImport]
[ComVisible(false)]
[Guid("00000001-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IClassFactory
{
void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object pUnkOuter,
ref Guid riid,
out IntPtr ppvObject);

void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
}

[ComVisible(true)]
internal class BasicClassFactory<T> : IClassFactory where T : new()
{
public void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object pUnkOuter,
ref Guid riid,
out IntPtr ppvObject)
{
Type interfaceType = GetValidatedInterfaceType(typeof(T), ref riid, pUnkOuter);

object obj = new T();
if (pUnkOuter != null)
{
obj = CreateAggregatedObject(pUnkOuter, obj);
}

ppvObject = GetObjectAsInterface(obj, interfaceType);
}

public void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock) { }

private static readonly Guid IID_IUnknown = Guid.Parse("00000000-0000-0000-C000-000000000046");

private static Type GetValidatedInterfaceType(Type classType, ref Guid riid, object outer)
{
if (riid == IID_IUnknown)
{
return typeof(object);
}

// Aggregation can only be done when requesting IUnknown.
if (outer != null)
{
const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
throw new COMException(string.Empty, CLASS_E_NOAGGREGATION);
}

// Verify the class implements the desired interface
foreach (Type i in classType.GetInterfaces())
{
if (i.GUID == riid)
{
return i;
}
}

// E_NOINTERFACE
throw new InvalidCastException();
}

private static IntPtr GetObjectAsInterface(object obj, Type interfaceType)
{
// If the requested "interface type" is type object then return as IUnknown
if (interfaceType == typeof(object))
{
return Marshal.GetIUnknownForObject(obj);
}

IntPtr interfaceMaybe = Marshal.GetComInterfaceForObject(obj, interfaceType, CustomQueryInterfaceMode.Ignore);
if (interfaceMaybe == IntPtr.Zero)
{
// E_NOINTERFACE
throw new InvalidCastException();
}

return interfaceMaybe;
}

private static object CreateAggregatedObject(object pUnkOuter, object comObject)
{
IntPtr outerPtr = Marshal.GetIUnknownForObject(pUnkOuter);

try
{
IntPtr innerPtr = Marshal.CreateAggregatedObject(outerPtr, comObject);
return Marshal.GetObjectForIUnknown(innerPtr);
}
finally
{
// Decrement the above 'Marshal.GetIUnknownForObject()'
Marshal.Release(outerPtr);
}
}
}
}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="[4.7.0, )" />
</ItemGroup>
</Project>
52 changes: 52 additions & 0 deletions core/extensions/OutOfProcCOM/COMRegistration/DllSurrogate.cs
@@ -0,0 +1,52 @@
using System;
using System.Diagnostics;
using Microsoft.Win32;

namespace COMRegistration
{
public static class DllSurrogate
{
public static void Register(Guid clsid, string tlbPath)
{
Trace.WriteLine($"Registering server with system-supplied DLL surrogate:");
Trace.Indent();
Trace.WriteLine($"CLSID: {clsid:B}");
Trace.Unindent();

string serverKey = string.Format(KeyFormat.CLSID, clsid);

// Register App ID - use the CLSID as the App ID
using RegistryKey regKey = Registry.LocalMachine.CreateSubKey(serverKey);
regKey.SetValue("AppID", clsid.ToString("B"));

// Register DLL surrogate - empty string for system-supplied surrogate
string appIdKey = string.Format(KeyFormat.AppID, clsid);
using RegistryKey appIdRegKey = Registry.LocalMachine.CreateSubKey(appIdKey);
appIdRegKey.SetValue("DllSurrogate", string.Empty);

// Register type library
TypeLib.Register(tlbPath);
}

public static void Unregister(Guid clsid, string tlbPath)
{
Trace.WriteLine($"Unregistering server:");
Trace.Indent();
Trace.WriteLine($"CLSID: {clsid:B}");
Trace.Unindent();

// Remove the App ID value
string serverKey = string.Format(KeyFormat.CLSID, clsid);
using RegistryKey regKey = Registry.LocalMachine.OpenSubKey(serverKey, writable: true);
if (regKey != null)
regKey.DeleteValue("AppID");

// Remove the App ID key
string appIdKey = string.Format(KeyFormat.AppID, clsid);
Registry.LocalMachine.DeleteSubKey(appIdKey, throwOnMissingSubKey: false);

// Unregister type library
TypeLib.Unregister(tlbPath);
}
}
}
10 changes: 10 additions & 0 deletions core/extensions/OutOfProcCOM/COMRegistration/KeyFormat.cs
@@ -0,0 +1,10 @@
namespace COMRegistration
{
internal static class KeyFormat
{
public const string CLSID = @"SOFTWARE\Classes\CLSID\{0:B}";
public const string AppID = @"SOFTWARE\Classes\AppID\{0:B}";

public static readonly string LocalServer32 = @$"{CLSID}\LocalServer32";
}
}
115 changes: 115 additions & 0 deletions core/extensions/OutOfProcCOM/COMRegistration/LocalServer.cs
@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

using Microsoft.Win32;

namespace COMRegistration
{
public sealed class LocalServer : IDisposable
{
public static void Register(Guid clsid, string exePath, string tlbPath)
{
// Register local server
Trace.WriteLine($"Registering server:");
Trace.Indent();
Trace.WriteLine($"CLSID: {clsid:B}");
Trace.WriteLine($"Executable: {exePath}");
Trace.Unindent();

string serverKey = string.Format(KeyFormat.LocalServer32, clsid);
using RegistryKey regKey = Registry.LocalMachine.CreateSubKey(serverKey);
regKey.SetValue(null, exePath);

// Register type library
TypeLib.Register(tlbPath);
}

public static void Unregister(Guid clsid, string tlbPath)
{
Trace.WriteLine($"Unregistering server:");
Trace.Indent();
Trace.WriteLine($"CLSID: {clsid:B}");
Trace.Unindent();

// Unregister local server
string serverKey = string.Format(KeyFormat.LocalServer32, clsid);
Registry.LocalMachine.DeleteSubKey(serverKey, throwOnMissingSubKey: false);

// Unregister type library
TypeLib.Unregister(tlbPath);
}

private readonly List<int> registrationCookies = new List<int>();

public void RegisterClass<T>(Guid clsid) where T : new()
{
Trace.WriteLine($"Registering class object:");
Trace.Indent();
Trace.WriteLine($"CLSID: {clsid:B}");
Trace.WriteLine($"Type: {typeof(T)}");

int cookie;
int hr = Ole32.CoRegisterClassObject(ref clsid, new BasicClassFactory<T>(), Ole32.CLSCTX_LOCAL_SERVER, Ole32.REGCLS_MULTIPLEUSE | Ole32.REGCLS_SUSPENDED, out cookie);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}

registrationCookies.Add(cookie);
Trace.WriteLine($"Cookie: {cookie}");
Trace.Unindent();

hr = Ole32.CoResumeClassObjects();
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
}

public void Run()
{
// This sample does not handle lifetime management of the server.
// For details around ref counting and locking of out-of-proc COM servers, see
// https://docs.microsoft.com/windows/win32/com/out-of-process-server-implementation-helpers
Console.ReadLine();
}

public void Dispose()
{
Trace.WriteLine($"Revoking class object registrations:");
Trace.Indent();
foreach (int cookie in registrationCookies)
{
Trace.WriteLine($"Cookie: {cookie}");
int hr = Ole32.CoRevokeClassObject(cookie);
Debug.Assert(hr >= 0, $"CoRevokeClassObject failed ({hr:x}). Cookie: {cookie}");
}

Trace.Unindent();
}

private class Ole32
{
// https://docs.microsoft.com/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx
public const int CLSCTX_LOCAL_SERVER = 0x4;

// https://docs.microsoft.com/windows/win32/api/combaseapi/ne-combaseapi-regcls
public const int REGCLS_MULTIPLEUSE = 1;
public const int REGCLS_SUSPENDED = 4;

// https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-coregisterclassobject
[DllImport(nameof(Ole32))]
public static extern int CoRegisterClassObject(ref Guid guid, [MarshalAs(UnmanagedType.IUnknown)] object obj, int context, int flags, out int register);

// https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-coresumeclassobjects
[DllImport(nameof(Ole32))]
public static extern int CoResumeClassObjects();

// https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-corevokeclassobject
[DllImport(nameof(Ole32))]
public static extern int CoRevokeClassObject(int register);
}
}
}
90 changes: 90 additions & 0 deletions core/extensions/OutOfProcCOM/COMRegistration/TypeLib.cs
@@ -0,0 +1,90 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

using ComTypes = System.Runtime.InteropServices.ComTypes;

namespace COMRegistration
{
internal static class TypeLib
{
public static void Register(string tlbPath)
{
Trace.WriteLine($"Registering type library:");
Trace.Indent();
Trace.WriteLine(tlbPath);
Trace.Unindent();

int hr = OleAut32.LoadTypeLibEx(tlbPath, OleAut32.REGKIND.REGKIND_REGISTER, out ComTypes.ITypeLib _);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
}

public static void Unregister(string tlbPath)
{
Trace.WriteLine($"Unregistering type library:");
Trace.Indent();
Trace.WriteLine(tlbPath);
Trace.Unindent();

ComTypes.ITypeLib typeLib;
int hr = OleAut32.LoadTypeLibEx(tlbPath, OleAut32.REGKIND.REGKIND_NONE, out typeLib);
if (hr < 0)
{
Trace.WriteLine($"Unregistering type library failed: 0x{hr:x}");
return;
}

IntPtr attrPtr = IntPtr.Zero;
try
{
typeLib.GetLibAttr(out attrPtr);
if (attrPtr != IntPtr.Zero)
{
ComTypes.TYPELIBATTR attr = Marshal.PtrToStructure<ComTypes.TYPELIBATTR>(attrPtr);
hr = OleAut32.UnRegisterTypeLib(ref attr.guid, attr.wMajorVerNum, attr.wMinorVerNum, attr.lcid, attr.syskind);
if (hr < 0)
{
Trace.WriteLine($"Unregistering type library failed: 0x{hr:x}");
}
}
}
finally
{
if (attrPtr != IntPtr.Zero)
{
typeLib.ReleaseTLibAttr(attrPtr);
}
}
}

private class OleAut32
{
// https://docs.microsoft.com/windows/api/oleauto/ne-oleauto-regkind
public enum REGKIND
{
REGKIND_DEFAULT = 0,
REGKIND_REGISTER = 1,
REGKIND_NONE = 2
}

// https://docs.microsoft.com/windows/api/oleauto/nf-oleauto-loadtypelibex
[DllImport(nameof(OleAut32), CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern int LoadTypeLibEx(
[In, MarshalAs(UnmanagedType.LPWStr)] string fileName,
REGKIND regKind,
out ComTypes.ITypeLib typeLib);

// https://docs.microsoft.com/windows/api/oleauto/nf-oleauto-unregistertypelib
[DllImport(nameof(OleAut32))]
public static extern int UnRegisterTypeLib(
ref Guid id,
short majorVersion,
short minorVersion,
int lcid,
ComTypes.SYSKIND sysKind);
}
}
}

0 comments on commit 01234ae

Please sign in to comment.