[英]How to Instantiate a ComVisible Class into Its Own AppDomain in a Single-Threaded Client?
The Problem 问题
When instantiating two, independent .NET COM-visible classes within the same, single-threaded COM client, .NET loads them both into the same AppDomain. 在同一个单线程COM客户端中实例化两个独立的.NET COM可见类时,.NET会将它们都加载到同一AppDomain中。
I am guessing that this is because they are being loaded into the same thread/process. 我猜这是因为它们被加载到同一线程/进程中。
An example of this behavior is shown in this GitHub repository . 此行为的示例在此GitHub存储库中显示 。
Essentially, the demonstration is as follows: 本质上,该演示如下:
SetData
on the CurrentDomain
. CurrentDomain
上的SetData
。 AppDomain
attribute AppDomain
属性 AppDomain
s, noting that it is also the same AppDomain
都获取哈希码,请注意,哈希码也相同 Why is this a problem? 为什么这是个问题?
When both classes have the AppDomain.CurrentDomain.AssemblyResolve
event implemented (or any other AppDomain event, for that matter), the events can interfere with one another. 当两个类都实现了
AppDomain.CurrentDomain.AssemblyResolve
事件(或者与此相关的任何其他AppDomain事件)时,这些事件可能会相互干扰。 This is at least one complication; 这至少是一种并发症。 I am guessing that there may be others as well.
我猜可能还会有其他人。
An Idea 一个主意
I thought the best way of handling this would be to create a new AppDomain for each COM object. 我认为处理此问题的最佳方法是为每个COM对象创建一个新的AppDomain。 Because I could not find (or Google) a way of doing this in a managed way, I thought it might be necessary to do it in unmanaged code.
因为我找不到(或Google)以托管方式执行此操作的方法,所以我认为可能有必要在非托管代码中执行此操作。
I did a little detective work. 我做了一些侦探工作。 In OleView, the InprocServer32 attribute for a .NET COM-visible class is
mscoree.dll
. 在OleView中,.NET COM可见类的InprocServer32属性是
mscoree.dll
。 So, I created a "shim" DLL which forwarded all of its EXPORTS
to mscoree.dll. 因此,我创建了一个“ shim” DLL,它将所有
EXPORTS
转发到mscoree.dll。 By process of elimination (eliminating exports until the COM would no longer load), I discovered that DllGetClassObject
in mscoree
was responsible for starting up the .NET runtime, and returning the instantiated COM object. 通过消除过程(消除导出直到COM不再加载),我发现
mscoree
中的DllGetClassObject
负责启动.NET运行时,并返回实例化的COM对象。
So, what I can do is implement my own DllGetClassObject
, like so: 因此,我可以做的是实现自己的
DllGetClassObject
,如下所示:
AppDomain
, and return it AppDomain
创建对象,然后将其返回 (I'm guessing it's not as simple as it sounds, though) (不过,我猜这并不像听起来那样简单)
The Question 问题
Before I embark on this potentially difficult and lengthy process, I'd like to know: 在开始这个可能困难且漫长的过程之前,我想知道:
If the code does not have to run in the same process, an out-of-process server would be the easiest fix. 如果代码不必在同一进程中运行,则进程外服务器将是最简单的解决方法。 Pass
CLSCTX_LOCAL_SERVER
to CoCreateInstance
and each class will be created in a dllhost
hosting process. 将
CLSCTX_LOCAL_SERVER
传递给CoCreateInstance
,每个类将在dllhost
托管进程中创建。
For example on the client: 例如在客户端上:
public static object CreateLocalServer(Guid clsid)
{
return CoCreateInstance(clsid, null, CLSCTX.LOCAL_SERVER, IID_IUnknown);
}
public static object CreateLocalServer(string progid)
{
Contract.Requires(!string.IsNullOrEmpty(progid));
Guid clsid;
CLSIDFromProgID(progid, out clsid);
return CreateLocalServer(clsid);
}
enum CLSCTX : uint
{
INPROC_SERVER = 0x1,
INPROC_HANDLER = 0x2,
LOCAL_SERVER = 0x4,
INPROC_SERVER16 = 0x8,
REMOTE_SERVER = 0x10,
INPROC_HANDLER16 = 0x20,
RESERVED1 = 0x40,
RESERVED2 = 0x80,
RESERVED3 = 0x100,
RESERVED4 = 0x200,
NO_CODE_DOWNLOAD = 0x400,
RESERVED5 = 0x800,
NO_CUSTOM_MARSHAL = 0x1000,
ENABLE_CODE_DOWNLOAD = 0x2000,
NO_FAILURE_LOG = 0x4000,
DISABLE_AAA = 0x8000,
ENABLE_AAA = 0x10000,
FROM_DEFAULT_CONTEXT = 0x20000,
ACTIVATE_32_BIT_SERVER = 0x40000,
ACTIVATE_64_BIT_SERVER = 0x80000
}
[DllImport(Ole32, ExactSpelling = true, PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
public static extern object CoCreateInstance(
[In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
CLSCTX dwClsContext,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
[DllImport(Ole32, CharSet = CharSet.Unicode, PreserveSig = false)]
public static extern void CLSIDFromProgID(string progId, out Guid rclsid);
You can also register a custom host, and swap the standard InProcServer32
for LocalServer32
. 您还可以注册自定义主机,并将标准
InProcServer32
交换为LocalServer32
。 For an example server 对于示例服务器
// StandardOleMarshalObject keeps us single-threaded on the UI thread
// https://msdn.microsoft.com/en-us/library/74169f59(v=vs.110).aspx
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId(IpcConstants.CoordinatorProgID)]
public sealed class Coordinator : StandardOleMarshalObject, ICoordinator
{
public Coordinator()
{
// required for regasm
}
#region Registration
[ComRegisterFunction]
internal static void RegasmRegisterLocalServer(string path)
{
// path is HKEY_CLASSES_ROOT\\CLSID\\{clsid}", we only want CLSID...
path = path.Substring("HKEY_CLASSES_ROOT\\".Length);
using (RegistryKey keyCLSID = Registry.ClassesRoot.OpenSubKey(path, writable: true))
{
// Remove the auto-generated InprocServer32 key after registration
// (REGASM puts it there but we are going out-of-proc).
keyCLSID.DeleteSubKeyTree("InprocServer32");
// Create "LocalServer32" under the CLSID key
using (RegistryKey subkey = keyCLSID.CreateSubKey("LocalServer32"))
{
subkey.SetValue("", Assembly.GetExecutingAssembly().Location, RegistryValueKind.String);
}
}
}
[ComUnregisterFunction]
internal static void RegasmUnregisterLocalServer(string path)
{
// path is HKEY_CLASSES_ROOT\\CLSID\\{clsid}", we only want CLSID...
path = path.Substring("HKEY_CLASSES_ROOT\\".Length);
Registry.ClassesRoot.DeleteSubKeyTree(path, throwOnMissingSubKey: false);
}
#endregion
}
Well... here's a managed proof-of-concept using RGiesecke.DllExport that works ; 嗯......这里是一个使用管理证明的概念RGiesecke.DllExport的作品 ; whether or not it's a good solution remains to be seen... so: use at your own risk.
它是否是一个好的解决方案还有待观察……所以:使用时需要您自担风险。 I'm still looking for better answers.
我仍在寻找更好的答案。
One thing that could be improved is that we don't need a new AppDomain
for each instantiation ; 可以改进的一件事是,我们不需要为每个实例都使用新的
AppDomain
; only for each object. 仅针对每个对象。 I'm sure that there are other subtleties that I'm missing.
我确定我还缺少其他细微之处。
We compile and register the DLL, then using OleView (or registry), we change the default ProcServer32 value to point to the managed DLL itself. 我们编译并注册DLL,然后使用OleView(或注册表)将默认的ProcServer32值更改为指向托管DLL本身。 This can be automated by providing a method in the DLL that is decorated with
[ComRegisterFunction()]
. 通过在DLL中提供一个用
[ComRegisterFunction()]
装饰的方法,可以自动执行此操作。
using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
using System.IO;
namespace Com_1
{
[Guid("F35D5D5D-4A3C-4042-AC35-CE0C57AF8383")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IComClass1
{
void SetAppDomainData(string data);
string GetAppDomainData();
int GetAppDomainHash();
}
//https://gist.github.com/jjeffery/1568627
[Guid("00000001-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface IClassFactory
{
void CreateInstance([MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter, ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvObject);
void LockServer(bool fLock);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("3CA12D49-CFE5-45A3-B114-22DF2D7A0CAB")]
[Description("Sample COM Class 1")]
[ProgId("Com1.ComClass1")]
public class ComClass1 : MarshalByRefObject, IComClass1, IClassFactory
{
public void SetAppDomainData(string data)
{
AppDomain.CurrentDomain.SetData("CurrentDomainCustomData", data);
}
public string GetAppDomainData()
{
return (string)AppDomain.CurrentDomain.GetData("CurrentDomainCustomData");
}
public int GetAppDomainHash()
{
return AppDomain.CurrentDomain.GetHashCode();
}
[DllExport]
public static uint DllGetClassObject(Guid rclsid, Guid riid, out IntPtr ppv)
{
ppv = IntPtr.Zero;
try
{
if (riid.CompareTo(Guid.Parse("00000001-0000-0000-c000-000000000046")) == 0)
{
//Call to DllClassObject is requesting IClassFactory.
var instance = new ComClass1();
IntPtr iUnk = Marshal.GetIUnknownForObject(instance);
//return instance;
Marshal.QueryInterface(iUnk, ref riid, out ppv);
return 0;
}
else
return 0x80040111; //CLASS_E_CLASSNOTAVAILABLE
}
catch
{
return 0x80040111; //CLASS_E_CLASSNOTAVAILABLE
}
}
public void CreateInstance([MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter, ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvObject)
{
IntPtr ppv = IntPtr.Zero;
//http://stackoverflow.com/a/13355702/864414
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName;
var curDomEvidence = AppDomain.CurrentDomain.Evidence;
AppDomain newDomain = AppDomain.CreateDomain("MyDomain", curDomEvidence, domaininfo);
Type type = typeof(ComClass1);
var instance = newDomain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
ppvObject = instance;
}
public void LockServer(bool fLock)
{
//Do nothing
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.