简体   繁体   中英

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

I have ac# application that runs locally, and I have a .dll written in c#.

MyApplication contains

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

MyLibrary:

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

Some googling has me looking at a COM server of sorts, but it wasn't clear if that was the best approach. I am having trouble even knowing what to google for this one. Ultimately MyInterface Will get a lot more complex and will include events to notify MyLibrary to refresh the data. How is the best way to accomplish this? Is it a COM server? Do I want to create some sort of API in MyApplication that MyLibrary uses?

Additional Information:

My .dll is being created as a real time data server for excel. I want to be able to access the data within my application from excel and notify excel of refreshes. I don't want to have multiple instances of my application since user input will determine the values displayed in excel. I am able to create the rtd server, however I am not sure what the best way is to access my outside data.

EDIT:

After doing some more research I think I am interested in using GetActiveObject("MyApplication.IMyInterface") inside MyLibrary to look like

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

But I am not sure how to register MyApplication.MyClass in the ROT. The code as is throws an exception

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

My Solution

  • If anyone knows of a better way to do this I would love to know.
  • I am also still having problems adding an event to my COMinterface. I would like to add public event ComEvent MyApplicationClose; inside MyApplication to be called when it is closed.

Edit

I was able to get events to work using Managed Event Sinks . Edits are reflected in code below.

Helper class

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

My application registered in the ROT

My application will act as a data server. It receives and processes data from multiple sources. Access to this data is exposed through COM. Having only one instance of MyApplication reduces redundancy to of connections to outside data sources and the processing, while allowing multiple clients to use the data it gets.

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

Note: This contains a Post-build event to register the assembly

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

Using MyApplication through COM

Since it is through COM it should work with any COM language, however I have only tried c#. Initially this code will be put into RTDserver for excel. This will allow the user to build complex worksheets that utilize real time data from my application.

First I create a wrapper in dot net for my COM object.

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;

    }
}

Now I can use my wrapper in my .net application

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.");
        }
    }
}

Where the output is

Val = 1
Val = 2

And after you close the COM server application you see the close message.

Val = 1
Val = 2
Com Application Closed.

References

Running Object Table: Provider in .NET, consumer in MFC

Automating a specific instance of Visual Studio .NET using C#

Register an Object in the ROT

You need to register your .net library as a COM object, and then instantiate and call it from your excel code. I think there is a good starter here on COM and .net interoperability .

Also there is a good tutorial available specifically on excel - .net interop .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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