3

問題

同じシングルスレッド COM クライアント内で 2 つの独立した .NET COM 可視クラスをインスタンス化する場合、.NET はそれらを両方とも同じ AppDomain に読み込みます。

これは、それらが同じスレッド/プロセスにロードされているためだと推測しています。

この動作の例は、この GitHub リポジトリに示されています。

基本的に、デモンストレーションは次のとおりです。

  1. 1 つの COM クラスをインスタンス化する
  2. バックエンドで を呼び出す最初の COM オブジェクトに属性を設定しSetDataますCurrentDomain
  3. 2 番目の独立した COM クラスをインスタンス化する (異なるインターフェイス名、GUID など)
  4. AppDomain属性を読む
  5. 同じように見えることを証明する
  6. また、両方の からハッシュ コードを取得します。AppDomainこれも同じであることに注意してください。

なぜこれが問題なのですか?

両方のクラスにAppDomain.CurrentDomain.AssemblyResolveイベント (またはその他の AppDomain イベント) が実装されている場合、イベントは互いに干渉する可能性があります。これは、少なくとも 1 つの合併症です。他にもありそうな予感。

アイデア

これを処理する最善の方法は、COM オブジェクトごとに新しい AppDomain を作成することだと思いました。管理された方法でこれを行う方法を見つけることができなかった (または Google) ため、アンマネージ コードで行う必要があるのではないかと考えました。

ちょっとした探偵の仕事をしました。OleView では、.NET COM 可視クラスの InprocServer32 属性はmscoree.dll. EXPORTSそこで、すべてを mscoree.dllに転送する「shim」DLL を作成しました。排除のプロセス (COM が読み込まれなくなるまでエクスポートを排除する) によって、.NET ランタイムの起動と、インスタンス化された COM オブジェクトの返還を担当していたことがわかりましたDllGetClassObjectmscoree

したがって、私ができることは、次のDllGetClassObjectように独自の を実装することです。

  1. CLRCreateInstanceを使用してアンマネージ アセンブリで .NET ランタイムをホストする
  2. new でオブジェクトを作成し、AppDomainそれを返します

(言うほど単純じゃないと思うけど)

質問

この困難で時間がかかる可能性のあるプロセスに着手する前に、次のことを知りたいです。

  1. 独自の AppDomain で実行する .NET COM 可視クラスを取得する管理された方法はありますか?
  2. そうでない場合、これは「正しい」方法ですか、それとも明らかな解決策がありませんか?
4

2 に答える 2

3

コードを同じプロセスで実行する必要がない場合は、アウトプロセス サーバーが最も簡単な解決策になります。に渡すCLSCTX_LOCAL_SERVERCoCreateInstance、各クラスがdllhostホスティング プロセスで作成されます。

たとえば、クライアントでは次のようになります。

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);

InProcServer32カスタム ホストを登録して、標準を に交換することもできますLocalServer32。サンプルサーバーの場合

// 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
}
于 2017-01-18T06:39:03.657 に答える
1

ええと...これは、機能するRGiesecke.DllExportを使用した管理された概念実証です。それが良い解決策であるかどうかはまだわかりません...そうです:自己責任で使用してください。私はまだより良い答えを探しています。

改善できることの 1 つは、インスタンス化AppDomainごとに new が必要ないことです。各オブジェクトのみ。私が見逃している他の微妙な点があると確信しています。

DLL をコンパイルして登録し、OleView (またはレジストリ) を使用して、既定の ProcServer32 値を変更し、マネージ DLL 自体を指すようにします。これは、で装飾された 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
        }
    }
}
于 2017-01-12T17:46:18.137 に答える