1

ローカルで実行される ac# アプリケーションがあり、c# で記述された .dll があります。

MyApplication が含まれています

namespace MyApplication
{
     public interface IMyInterface
     {
          IMyInterface Instance { get; }
          int MyProp { get; }
     }

     class MyClass : IMyInterface
     {
          public int MyProp { get; private set; }

          private MyClass instance
          public static IMyInterface
          {
              get
              {
                  if (instance == null)
                  {
                      instance = new MyClass();
                  }
                  return instance;
              }
          }

          private MyClass() { MyProp = 1; }
     }
}

マイライブラリ:

namespace MyLibrary
{
    public LibClass
    {
         public static int GetProp()
         {
              //I want to get the running instance of MyApplication
              //If MyApplication is not running I want to start a new instance
              //From my application instance I want to 
              //return the value from MyInterface.Instance.MyProp
         }
    }
}

いくつかのグーグルで、ある種のCOMサーバーを調べましたが、それが最善のアプローチであるかどうかは明確ではありませんでした. これについて何をグーグルで検索すればよいかさえわかりません。最終的MyInterfaceにはもっと複雑になりMyLibrary、データを更新するように通知するイベントが含まれます。これを達成するための最良の方法は何ですか?COMサーバーですか?MyApplicationそのMyLibrary用途である種の API を作成したいですか?

追加情報:

私の .dll は、Excel のリアルタイム データ サーバーとして作成されています。アプリケーション内のデータに Excel からアクセスし、Excel に更新を通知できるようにしたいと考えています。ユーザー入力によってExcelに表示される値が決まるため、アプリケーションの複数のインスタンスを持ちたくありません。RTD サーバーを作成することはできますが、外部データにアクセスする最善の方法がわかりません。

編集:

GetActiveObject("MyApplication.IMyInterface")さらに調査を行った後、内部MyLibraryを次のように使用することに興味があると思います

namespace MyLibrary
{
    public LibClass
    {
         public static int GetProp()
         {
            running_obj = System.Runtime.InteropServices.Marshal.GetActiveObject("MyApplication.IMyInterface")
            return ((IMyInterface) running_obj).MyProp;
         }
         private object running_obj = null;
    }
}

しかしMyApplication.MyClass、ROTに登録する方法がわかりません。そのままのコードは例外をスローします

Invalid class string (Exception from HRESULT: 0x800401F3 (CO_E_CLASSSTRING))
4

2 に答える 2

3

私の解決策

  • 誰かがこれを行うためのより良い方法を知っているなら、私は知りたいです.
  • また、COM インターフェイスにイベントを追加する際にもまだ問題があります。閉じたときに呼び出されるようにpublic event ComEvent MyApplicationClose;内部を追加したいと思います。MyApplication

編集

Managed Event Sinksを使用してイベントを機能させることができました。編集は以下のコードに反映されます。

ヘルパー クラス

namespace ole32
{
    public class Ole32
    {
        [DllImport( "Ole32.Dll" )]
        public static extern int CreateBindCtx( int reserved, out IBindCtx
            bindCtx );

        [DllImport( "oleaut32.dll" )]
        public static extern int RegisterActiveObject( [MarshalAs( UnmanagedType.IUnknown )] object punk,
             ref Guid rclsid, uint dwFlags, out int pdwRegister );

        [DllImport( "ole32.dll", EntryPoint = "GetRunningObjectTable" )]
        public static extern int GetRunningObjectTable( int reserved, out IRunningObjectTable ROT );

        [DllImport( "ole32.dll", EntryPoint = "CreateItemMoniker" )]
        public static extern int CreateItemMoniker( byte[] lpszDelim, byte[] lpszItem, out IMoniker ppmk );

        /// <summary>
        /// Get a snapshot of the running object table (ROT).
        /// </summary>
        /// <returns>A hashtable mapping the name of the object
        //     in the ROT to the corresponding object</returns>

        public static Hashtable GetRunningObjectTable()
        {
            Hashtable result = new Hashtable();

            IntPtr numFetched = IntPtr.Zero;
            IRunningObjectTable runningObjectTable;
            IEnumMoniker monikerEnumerator;
            IMoniker[] monikers = new IMoniker[1];

            GetRunningObjectTable( 0, out runningObjectTable );
            runningObjectTable.EnumRunning( out monikerEnumerator );
            monikerEnumerator.Reset();

            while ( monikerEnumerator.Next( 1, monikers, numFetched ) == 0 )
            {
                IBindCtx ctx;
                CreateBindCtx( 0, out ctx );

                string runningObjectName;
                monikers[0].GetDisplayName( ctx, null, out runningObjectName );

                object runningObjectVal;
                runningObjectTable.GetObject( monikers[0], out runningObjectVal );

                result[runningObjectName] = runningObjectVal;
            }

            return result;
        }
    }
}

ROT に登録された私のアプリケーション

私のアプリケーションはデータサーバーとして機能します。複数のソースからデータを受信して​​処理します。このデータへのアクセスは、COM を通じて公開されます。MyApplication のインスタンスを 1 つだけにすることで、複数のクライアントが取得したデータを使用できるようにしながら、外部データ ソースへの接続と処理の冗長性を減らします。

namespace MyNamespace
{
    [ComVisible( true ),
    GuidAttribute( "14C09983-FA4B-44e2-9910-6461728F7883" ),
    InterfaceType( ComInterfaceType.InterfaceIsDual )]
    public interface ICOMApplication
    {    
        [DispId(1)]
        int GetVal();
    }

    //Events for my com interface. Must be IDispatch
    [Guid( "E00FA736-8C24-467a-BEA0-F0AC8E528207" ),
    InterfaceType( ComInterfaceType.InterfaceIsIDispatch ),
    ComVisible( true )]
    public interface ICOMEvents
    {
        [DispId( 1 )]
        void ComAppClose( string s );
    }

    public delegate void ComEvent( string p );

    [ComVisible(true)]
    [Guid( "ECE6FD4C-52FD-4D72-9668-1F3696D9A99E" )]
    [ComSourceInterfaces( typeof( ICOMWnEvents) )]
    [ClassInterface( ClassInterfaceType.None )]
    public class MyApplication : ICOMApplication, IDisposable
    {
        //ICOMEvent
        public event ComEvent ComAppClose; 

        protected MyApplication ()
        {
            //check if application is registered.
            //if not registered then register the application
            if (GetApiInstance() == null)
            {
                Register_COMI();
            }
        }

        // UCOMI-Version to register in the ROT
        protected void Register_COMI()
        {
            int errorcode;
            IRunningObjectTable rot;
            IMoniker moniker;
            int register;

            errorcode = Ole32.GetRunningObjectTable( 0, out rot );
            Marshal.ThrowExceptionForHR( errorcode );
            errorcode = BuildMoniker( out moniker );
            Marshal.ThrowExceptionForHR( errorcode );
            register = rot.Register( 0, this, moniker );
        }

        public void Dispose()
        {
            Close( 0 ); //close and clean up    
        }

        //Will look for an existing instance in the ROT and return it
        public static ICOMApplication GetApiInstance()
        {
            Hashtable runningObjects = Ole32.GetRunningObjectTable();

            IDictionaryEnumerator rotEnumerator = runningObjects.GetEnumerator();
            while ( rotEnumerator.MoveNext() )
            {
                string candidateName = (string) rotEnumerator.Key;
                if ( !candidateName.Equals( "!MyNamespace.ICOMApplication" ) )
                    continue;

                ICOMApplication wbapi = (ICOMApplication ) rotEnumerator.Value;
                if ( wbapi != null )
                    return wbapi;

                //TODO: Start the application so it can be added to com and retrieved for use
            }
            return null;
        }

        //Builds the moniker used to register and look up the application in the ROT
        private static int BuildMoniker( out IMoniker moniker )
        {
            UnicodeEncoding enc = new UnicodeEncoding();
            string delimname = "!";
            byte[] del = enc.GetBytes( delimname );
            string itemname = "MyNamespace.ICOMApplication";
            byte[] item = enc.GetBytes( itemname );
            return Ole32.CreateItemMoniker( del, item, out moniker );
        }

        protected void Close( int i )
        {
            //Deregistering from ROT should be automatic
            //Additional cleanup

            if (ComAppClose != null) ComAppClose("");
        }

        ~MyApplication()
        {
            Dispose();
        }

        //implement ICOMApplication interface
        private static int i = 0;
        public int GetVal()
        {
            return i++; //test value to return
        }
    }
}

注:これには、アセンブリを登録するためのビルド後のイベントが含まれています

C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe $(TargetFileName) /codebase /tlb:$(TargetName)TypeLib.tlb

COM 経由で MyApplication を使用する

COM 経由なので、どの COM 言語でも動作するはずですが、私は c# しか試していません。最初に、このコードは RTDserver for Excel に入れられます。これにより、ユーザーはアプリケーションからのリアルタイム データを利用する複雑なワークシートを作成できます。

最初に、ドット ネットで COM オブジェクトのラッパーを作成します。

namespace COMTest
{
    //extend both the com app and its events and use the event sink to get the events
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public sealed class MyAppDotNetWrapper, ICOMEvents, ICOMApplication
    {
        private ICOMApplication comapp;
        public MyAppDotNetWrapper()
        {
            StartEventSink()
        }




        //Manage the sink events
        private void StartEventSink()
        {
            //get the instance of the app;
            comapp = MyApplication .GetApiInstance();

            if (comapp != null)
            {
                serverconnected = true;

                //Start the event sink
                IConnectionPointContainer connectionPointContainer = (IConnectionPointContainer) comapp;
                Guid comappEventsInterfaceId = typeof (ICOMApplicationEvents).GUID;
                connectionPointContainer.FindConnectionPoint(ref comappEventsInterfaceId, out connectionPoint);
                connectionPoint.Advise(this, out cookie);
            }
        }

        private void StopEventSink()
        {
            if (serverconnected)
            {
                //unhook the event sink
                connectionPoint.Unadvise(cookie);
                connectionPoint = null;
            }
        }


        //Implement ICOMApplication methods
        public int GetVal()
        {
            return comapp.GetVal();
        }


        //receive sink events and forward
        public event ComEvent ComAppCloseEvent;
        public void ComAppClose(string s)
        {
            serverconnected = false;
            ComAppCloseEvent(s);
        }

        private ICOMApplication comapp;
        IConnectionPoint connectionPoint;
        private int cookie;
        private bool serverconnected;

    }
}

これで、.net アプリケーションでラッパーを使用できるようになりました

namespace COMTest
{
    class Program
    {
        private static MyAppDotNetWrapper app;
        static void Main( string[] args )
        {

            //create a new instance of the wrapper
            app = new MyAppDotNetWrapper();

            //Add the onclose event handler
            app.ComAppCloseEvent += OnAppClose;

            //call my com interface method
            Console.WriteLine("Val = " + app.GetVal().ToString());
            Console.WriteLine("Val = " + app.GetVal().ToString());
            string s = Console.ReadLine();
        }
        static voic OnClose(string s)
        {
            Console.WriteLine("Com Application Closed.");
        }
    }
}

出力の場所

Val = 1
Val = 2

COM サーバー アプリケーションを閉じると、終了メッセージが表示されます。

Val = 1
Val = 2
Com Application Closed.

参考文献

実行中のオブジェクト テーブル: .NET のプロバイダー、MFC のコンシューマー

C# を使用して Visual Studio .NET の特定のインスタンスを自動化する

オブジェクトを ROT に登録する

于 2013-05-01T20:55:22.227 に答える
1

.net ライブラリを COM オブジェクトとして登録し、インスタンス化して Excel コードから呼び出す必要があります。ここには、 COM と .net の相互運用性に関する優れたスターターがあると思います。

また、 excel-.net interopに特化した優れたチュートリアルも用意されています。

于 2013-04-30T19:31:09.617 に答える