Possible to register a C# program to the R.O.T. for OLE automation?

Hi all,
I was wondering if it was possible to "register" your own created c# program for OLE automation This would be the equivalent of accessing something in Excel via OLE. Like:

Dim ExcelApp As Object
ExcelApp = System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application")

Edit: and just to clarify, I understand how the GetActiveObject works (by looking up the object in the Running Object Table). I guess I need to know if it is possible to register a program with the Running Object Table.




Answer this question

Possible to register a C# program to the R.O.T. for OLE automation?

  • Ramanujam Sampath

    This article will give you the definitive answers about COM Interop:

    http://www.codeproject.com/dotnet/cominterop.asp

    Yours,
    Alois Kraus


  • dvh

    Alois,
    Thank you again for all of your help. The article you linked helped some, but I am having issues with grabbing the interface of an already running object. I can use

    objTypeNameVett = Type.GetTypeFromProgID("NameVett.MyForm1");
    objNameVettLateBound = Activator.CreateInstance(objTypeNameVett);
    status=(int)(objTypeNameVett.InvokeMember("getStatus",System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.InvokeMethod, null, objNameVettLateBound, inputParams));


    and the code technically works. But, the result returned is incorrect, as the code creates an instance, when I need the already running instance. I tried using

    objNameVettLateBound = Activator.GetObject(objTypeNameVett,"http://myip");
    status=(int)(objTypeNameVett.InvokeMember("getStatus",System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.InvokeMethod, null, objNameVettLateBound, inputParams));

    but the program crashes with this error:

    System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Net.WebException: The remote server returned an error: (405) Method Not Allowed.

    Server stack trace:
    at System.Runtime.Remoting.Channels.Http.HttpClientTransportSink.ProcessResponseException(WebException webException, HttpWebResponse& response)
    at System.Runtime.Remoting.Channels.Http.HttpClientTransportSink.ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, ITransportHeaders& responseHeaders, Stream& responseStream)
    at System.Runtime.Remoting.Channels.SoapClientFormatterSink.SyncProcessMessage(IMessage msg)

    Exception rethrown at [0]:
    at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
    at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
    at NameVett.Form1.getStatus(String id)
    --- End of inner exception stack trace ---
    at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
    at System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args)
    at WebService1.getStatus(String id) in c:\web\NameVett.asmx:line 50

    I cannot tell if the error is being thrown by the C# application who's method I'm invoking or not. I am sure it has to do with me using the wrong method.

    I am also not sure whether I should go ahead and create another thread about this or not, as you answered my original question.


  • g e o r g e

    Great, thanks again Alois. Hopefully this is my last question: I've managed to register my program with the ROT, and I can see it fine with the correct name! :) Now, accessing it is giving me some trouble. I've managed to get the moniker that "points" to the object, and now I want to grab a pointer to the interface of the running program. The way to do so seems to be with the BindToObject method. I've implemented this with

    monikers[0].BindToObject(ibc, monikers[0], ref guid, out myProgObject);

    now, when I run this command I get this error:

    System.ArgumentException: Value does not fall within the expected range.
    at System.Runtime.InteropServices.ComTypes.IMoniker.BindToObject(IBindCtx pbc, IMoniker pmkToLeft, Guid& riidResult, Object& ppvResult)


    I've specified in my program to make my interface com visible, and my program implements it as well.

    namespace NameVett
    {
    [ComVisible(true)]
    public interface _Form1
    {
    int getStatus(String id);

    }


    public partial class Form1 : Form,_Form1
    {

    Any ideas of what I could be doing wrong







  • Michell Cronberg

    When a dll or executable does conatain IL code it is considered as assembly by .NET. You can register your public types with regasm to make them visible to COM.


    http://msdn2.microsoft.com/en-us/library/tzat5yw6(vs.80).aspx

    Yours,

    Alois Kraus


  • Maheswar

    ah, excellent. Well, it registered the class, but the interface isn't showing any methods.

    Ok, now I've got it showing the methods within the interface. Now I just need to know how to use them.

  • Codeme

    I don't think it's an access problem as I can see the actual object running in the ROT. I have not tried to create an instance of it, as I want to grab the already running instance. How would I grab create an instance of the COM object I can run the program on it's own if thats what you mean.

    Corey

  • mrpu

    Yes it is possible.
    With .NET you can create for all public classes of your managed dll COM wrappers by calling
    TlbExp AssemblyName [Options]
    which will create a type library which can be consumed by unmanaged C++ clients via the #import statement.
    To register your public C# classes of your assembly you can call simply regasm /codebase <yourassembly> to register them as normal COM objects.

    A managed (e.g. C#) interface can be registerd into the ROT by calling UCOMIRunningObjectTable.Register. The Running Object Table interface can be retrieved quite easily by calling the folling function via PInvoke:

    [DllImport("ole32.dll")]
    static extern int GetRunningObjectTable(uint reserved,
    out System.Runtime.InteropServices.ComTypes.IRunningObjectTable pprot);

    You can call Register with a moniker which you need to create befor you can use it. Then you should be able to get the COM interface from any running process you would like to.
    This article should give you some insight into C# and Monikers:

    http://www.codeproject.com/csharp/automatingvisualstudio.asp

    Hope this helps,
    Alois Kraus



  • george_v

    You could simply create a new application where you do in the Add Reference Dialog select the COM tab where you select your previously registered managed COM object.

    Now you can create with

    new YourInterOpNS.YourClassNameClass();

    a instance of your COM object. If this does work you know for sure that the object is correctly registered.

    Yours,

    Alois Kraus


  • rathlar

    Did you run regasm /codebase YouAssembly successfully After this your objects should show up in the COM tab with some prefix which is not always easy to guess but they should be definitely show up then.


  • Janny

    Here is some smaple code I did create with VS2003 quite a while ago. You can simply enumerate all monikers and then pick the one you want. This class fragment does implement the Interface IFormData which is published as COM interface where you have both the registration and Get code inside one class.


    private const int ROTFLAGS_REGISTRATIONKEEPSALIVE = 1;
    private const int ROTFLAGS_ALLOWANYCLIENT = 2;

    [DllImport("ole32.dll", ExactSpelling=true, PreserveSig=false)]
    private static extern UCOMIRunningObjectTable GetRunningObjectTable(
    int reserved);

    [DllImport("ole32.dll", CharSet=CharSet.Unicode,
    ExactSpelling=true, PreserveSig=false)]
    private static extern UCOMIMoniker CreateItemMoniker(
    [In] string lpszDelim, [In] string lpszItem);

    [DllImport("ole32.dll", ExactSpelling=true, PreserveSig=false)]
    public static extern UCOMIBindCtx CreateBindCtx(int reserved);


    public event MsgEventHandler MsgEvent;

    bool myIsDisposed = false;
    public bool IsDisposed
    {
    get { return myIsDisposed; }
    }

    int myCookie = 0;

    public FormData()
    {
    MsgEvent = null;
    AddToROT();
    }

    public IFormData GetActiveObject()
    {
    IFormData formdata = null;
    UCOMIRunningObjectTable runningObjectTable;
    UCOMIEnumMoniker monikerEnumerator;
    UCOMIMoniker[] monikers = new UCOMIMoniker[1];

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

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

    string runningObjectName;
    monikers[0].GetDisplayName(ctx, null, out runningObjectName);
    if( runningObjectName.StartsWith("!IFormData") )
    {

    object runningObjectVal;
    runningObjectTable.GetObject( monikers[0], out runningObjectVal);
    formdata = runningObjectVal as IFormData;
    }
    }

    return formdata;
    }

    public void AddToROT()
    {
    UCOMIRunningObjectTable rot = null;
    UCOMIMoniker moniker = null;
    try
    {
    // Get the ROT
    rot = GetRunningObjectTable(0);

    // Create a moniker for the graph
    moniker = CreateItemMoniker("!", item);

    // Registers the graph in the running object table
    int cookieValue;
    // ROTFLAGS_ALLOWANYCLIENT|
    rot.Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, (IFormData)this,
    moniker, out cookieValue);


    myCookie = cookieValue;
    }
    finally
    {
    // Releases the COM objects
    if (moniker != null)
    while(Marshal.ReleaseComObject(moniker)>0);
    if (rot != null) while(Marshal.ReleaseComObject(rot)>0);
    }
    }

    public void RemoveFromROT()
    {
    if (myCookie != 0 )
    {
    UCOMIRunningObjectTable rot = null;
    try
    {
    // Get the running object table and revoke the cookie
    rot = GetRunningObjectTable(0);
    rot.Revoke(myCookie);
    myCookie = 0;
    }
    finally
    {
    if (rot != null) while(Marshal.ReleaseComObject(rot)>0);
    }
    }
    }

    Yours,
    Alois Kraus


  • lrryklly

    I thought that regasm was for DLLs

  • JohnCP

    If it is an access problem you could try the value ROTFLAGS_ALLOWANYCLIENT during registration to allow processes of other users to see your COM object.

    Without further information it is quite difficult to diagnose your exact problem. Can you create an instance of your COM obect via C# without taking it from the ROT This test would ensure that nothing is wrong with your COM registration.

    Please mark the answer to this question as answered if you think that it has solved your problem.

    Yours,

    Alois Kraus


  • Ampers

    Alois,
    That does help a lot, thank you. Do you have a link actually showing an example of how to register a running object I found http://www.developmentnow.com/g/21_2004_5_0_0_105861/Register-an-Object-in-the-ROT.htm but I'm not seeing how I would tie my C# program into it. Thanks again.

    Corey

    Edit: I *think* my only problem is how do I create a moniker for my program

    Edit2: Alright, I'm making progress. I've create the moniker and managed to implement it via

    IRunningObjectTable table;
    IMoniker moniker;
    GetRunningObjectTable(0, out table);
    CreateItemMoniker("!", "NameVett", out moniker);
    table.Register(0, this, moniker);

    now my next problem is when it registers the object, there is no name specified for it.(I want it to show "NameVett") How do I solve this issue I should also note that changing the second parameter is only changing the Hash Value of the object in the ROT.

  • Mark-Edgerton

    That probably is my problem then, because the Namespace and class are not listed under the COM tab.

    Corey


    Edit: I modified my code to:

    [ComVisible(true)]
        [Guid("{6C3CCB0E-8968-4724-A7F3-5C7CB3A2FD6E}")]
        public interface _Form1
        {
            [DispId(0)]
            int getStatus(String id);
           
        }


        [ComVisible(true)]
        [ProgId("NameVett.Form1")]
        [Guid("{7E1421B0-F480-4d59-A944-478D40E0ACE2}")]
        [ClassInterface(ClassInterfaceType.None)]
        public partial class Form1 : Form,_Form1

    But it is still not appearing. I have also selected under the build options to make the assembly COM-Visible....the weird thing though is it requires yet another GUID there.

  • Possible to register a C# program to the R.O.T. for OLE automation?