I have a c# 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))
public event ComEvent MyApplicationClose;
inside MyApplication
to be called when it is closed.I was able to get events to work using Managed Event Sinks. Edits are reflected in code below.
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 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
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.
Running Object Table: Provider in .NET, consumer in MFC
Automating a specific instance of Visual Studio .NET using C#
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.
User contributions licensed under CC BY-SA 3.0