Могу ли я получить доступ к своим объектам C# из моего внешнего.dll? Может быть, с помощью ROT

У меня есть приложение aC#, которое работает локально, и у меня есть.dll, написанные на C#.

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-сервер, но неясно, был ли это лучший подход. У меня возникли проблемы, даже зная, что Google для этого. в конечном счете MyInterface Будет намного сложнее и будет включать события для уведомления MyLibrary обновить данные. Как лучше всего это сделать? Это COM сервер? Хочу ли я создать какой-то API в MyApplication тот MyLibrary использует?

Дополнительная информация:

Мой.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))

2 ответа

Решение

Мое решение

  • Если кто-нибудь знает лучший способ сделать это, я хотел бы знать.
  • У меня также все еще есть проблемы с добавлением события в мой COMinterface. Я хотел бы добавить public event ComEvent MyApplicationClose; внутри MyApplication быть вызванным, когда это закрыто.

редактировать

Мне удалось заставить события работать, используя управляемые приемники событий. Изменения отражены в коде ниже.

Хелпер класс

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 уменьшает избыточность соединений с внешними источниками данных и их обработку, позволяя нескольким клиентам использовать полученные данные.

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
        }
    }
}

Примечание. Содержит событие Post-build для регистрации сборки.

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

Использование MyApplication через COM

Так как это через COM, оно должно работать с любым языком COM, однако я пробовал только C#. Первоначально этот код будет помещен в RTDserver для 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

Автоматизация конкретного экземпляра Visual Studio .NET с использованием C#

Зарегистрировать объект в ROT

Вам нужно зарегистрировать свою библиотеку.net как объект COM, а затем создать ее экземпляр и вызвать ее из кода Excel. Я думаю, что здесь есть хороший стартер по взаимодействию COM и.net.

Также есть хороший учебник, доступный специально для Excel - .NET Interop.

Другие вопросы по тегам