繁体   English   中英

我可以从外部.dll访问我的c#对象吗? 也许使用ROT

[英]Can I access my c# objects from my external .dll? Maybe using ROT

我有一个在本地运行的ac#应用程序,我有一个用。#编写的.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服务器,但我不确定访问外部数据的最佳方法是什么。

编辑:

在做了一些更多的研究后,我想我有兴趣在MyLibrary中使用GetActiveObject("MyApplication.IMyInterface")看起来像

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

但我不知道如何在ROT中注册MyApplication.MyClass 代码将引发异常

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

我的解决方案

  • 如果有人知道更好的方法,我很想知道。
  • 我还在向COM接口添加事件时遇到问题。 我想添加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

通过COM使用MyApplication

因为它是通过COM它应该适用于任何COM语言,但我只尝试过c#。 最初,此代码将放入RTDserver for excel。 这将允许用户构建复杂的工作表,利用我的应用程序中的实时数据。

首先,我在dot网络中为我的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中注册一个对象

您需要将.net库注册为COM对象,然后实例化并从您的Excel代码中调用它。 我认为在COM和.net互操作性方面有一个很好的启动器。

另外还有一个专门针对excel的好教程- .net interop

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM