Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add out-of-process COM server demo (#3908)
- Loading branch information
1 parent
3bbeef4
commit 01234ae
Showing
26 changed files
with
1,092 additions
and
0 deletions.
There are no files selected for viewing
105 changes: 105 additions & 0 deletions
105
core/extensions/OutOfProcCOM/COMRegistration/BasicClassFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
core/extensions/OutOfProcCOM/COMRegistration/COMRegistration.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
52
core/extensions/OutOfProcCOM/COMRegistration/DllSurrogate.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
115
core/extensions/OutOfProcCOM/COMRegistration/LocalServer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
Oops, something went wrong.