COM+ activation on a remote server with partitions in C#
Asked Answered
G

2

11

I want to access partitioned COM+ applications on a remote server. I have tried this:

using COMAdmin
using System.Runtime.InteropServices;

_serverName = myRemoteServer;
_partionName = myPartionName;
_message = myMessage;
ICOMAdminCatalog2 catalog = new COMAdminCatalog();
        catalog.Connect(_serverName);
        string moniker = string.Empty;
        string MsgInClassId = "E3BD1489-30DD-4380-856A-12B959502BFD";

        //we are using partitions
        if (!string.IsNullOrEmpty(_partitionName))
        {
            COMAdminCatalogCollection partitions = catalog.GetCollection("Partitions");
            partitions.Populate();
            string partitionId = string.Empty;


            foreach (ICatalogObject item in partitions)
            {
                if (item.Name == _partitionName)
                {
                    partitionId = item.Key;
                    break;
                }
            }
            if (!string.IsNullOrEmpty(partitionId) )
            {
                moniker = $"partition:{partitionId}/new:{new Guid(MsgInClassId)}";
                try
                {
                    var M = (IMsgInManager)Marshal.BindToMoniker(moniker);
                    M.AddMsg(_message);
                }
                catch (Exception ex)
                {

                    throw new Exception($"We can not use: {_partitionName} with Id {partitionId}. {ex.ToString()}");
                }                
            }
            else
            {
                throw;
            }
        }
        else
//we don't have partitions and this will work
            {
                Type T = Type.GetTypeFromCLSID(new Guid(MsgInClassId), _serverName, true);
                var M = (IMsgInManager)Activator.CreateInstance(T);
                M.AddMsg(_message);
            }

        }

So when we are local on the (remote) machine, partitions are working with the moniker and Marshal.BindToMoniker. But when I try do the same remotely from my machine, I get an error from Marshal.BindToMoniker that Partitons is not enabled. Because on my machine partitions is not enabled.

Message = "COM+ partitions are currently disabled. (Exception from HRESULT: 0x80110824)"

How can I use Marshal.BindToMoniker to run on the remote server. Is it something I can add to the moniker string i.e.

moniker = $"server:_server/partition:{partitionId}/new:{new Guid(MsgInClassId)}"

My questions is very simular to this: COM+ object activation in a different partition

Garlic answered 3/1, 2017 at 10:48 Comment(6)
Are you sure this is not by design? The error message seems consistent with your settings. You should contact Microsoft I guess. Also check this: social.technet.microsoft.com/Forums/windows/en-US/…Misquote
I guess you somehow have to incorporate the server name into the moniker. Right now you only use the server name to connect to the catalog on the server. You don't use it to create the object as you do in the case where you don't use partitions. So you are actually trying to create the object on your local machine where you don't have partitions enabled. The solution is probably not to enable partitions locally as suggested by the link provided by @SimonMourier because that would only allow you to create the object locally and that is probably not what you want here.Fayfayal
@MikaelEriksson In theory maybe possible. In reality looks like currently might not be supported. BindToMoniker is implemented with a call to CreateBindCtx (gets a IBindCtx), MkParseDisplayName and finally a BindMoniker. You could implement sequence yourself and instead of using a default BindCtx (which has BIND_OPTS structure) you could create one yourself with a BIND_OPTS2 structure. That has a pServerInfo with server information. Now thats the good part. The bad part from documentation: class moniker does not currently honor the pServerInfo flag. So it sounds like it won't work right now.Creak
In queued components there is a "ComputerName=cc/new:". You could try "ComputerName:" just for the sake of it. But thats more like grasping at straws ;-).Creak
@Uwe I guess you are referring to COM's new class moniker does not currently honor the pServerInfo flag. here. If you add that as an answer you have my upvote and if nothing better comes along you will have the bounty as well.Fayfayal
@MikaelEriksson Yes, thats what I mean. I will write up some details with links for an answer then.Creak
C
3

tl;dr
According to MS documentation there is a way to do this by setting the pServerInfo in BIND_OPTS2 structure for binding the moniker. Unfortunately this is not working for the COM class moniker.

see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms694513(v=vs.85).aspx where it says for *pServerInfo:

COM's new class moniker does not currently honor the pServerInfo flag.

But maybe just try your scenario and at some future time it might be supported (or already is and documentation is wrong).

also see: http://thrysoee.dk/InsideCOM+/ch11c.htm
where it also says in the footnote it does not work for class moniker: http://thrysoee.dk/InsideCOM+/footnotes.htm#CH1104

Theory and suggested solution if it was supported in c#
Disclaimer: I couldn't test the code as I don't have a test setup. This is off the top of my head. A bit pseudo code.

To do this you would have to code the COM/Moniker calls yourself. For this you could look at the source of microsofts implementation as a starting point. There BindToMoniker is implemented like:

    public static Object BindToMoniker(String monikerName) 
    {
        Object obj = null; 
        IBindCtx bindctx = null; 
        CreateBindCtx(0, out bindctx);

        UInt32 cbEaten;
        IMoniker pmoniker = null;
        MkParseDisplayName(bindctx, monikerName, out cbEaten, out pmoniker);

        BindMoniker(pmoniker, 0, ref IID_IUnknown, out obj);
        return obj; 
    } 

CreateBindCtx, MkParseDisplayName and BindMoniker are OLE32.dll functions.

IBindCtx has methods to change the binding context. For this you call IBindCtx.GetBindContext(out BIND_OPTS2) and change the settings to what you need. Then set the new binding context with IBindCtx.SetBindContext(BIND_OPTS2). So essentially your own version of code would look something like this (pseudo code):

    public static Object BindToMoniker(String monikerName) 
    {
        Object obj = null; 
        IBindCtx bindctx = null; 
        CreateBindCtx(0, out bindctx);

        BIND_OPTS2 bindOpts;
        bindOpts.cbStruct = Marshal.SizeOf(BIND_OPTS2);
        bindctx.GetBindOptions(ref bindOpts);
        // Make your settings that you need. For example:
        bindOpts.dwClassContext = CLSCTX_REMOTE_SERVER;
        // Anything else ?
        bindOpts.pServerInfo = new COSERVERINFO{pwszName = "serverName"};
        bindctx.SetBindOptions(ref bindOpts);

        UInt32 cbEaten;
        IMoniker pmoniker = null;
        MkParseDisplayName(bindctx, monikerName, out cbEaten, out pmoniker);

        BindMoniker(pmoniker, 0, ref IID_IUnknown, out obj);
        return obj; 
    } 

As said, unfortunately this code is not possible to write in C# out of the box. Even the OLE32.dll method declarations CreateBindCtx, MkParseDisplayName and BindMoniker are privately declared in Marshal.cs so you will have to declare them in your project again.

But we are lucky with the IBindCtx declaration using a BIND_OPTS2 and the BIND_OPTS2 structure definition itself. They are declared in Microsoft.VisualStudio.OLE.Interop (interesting declarations in this namespace anyway). So you can try using them because inside the Marshal object and marshal.cs only the BIND_OPTS structure is used. I don't know if this is part of the framework and redistributable (I doubt it) but for testing this should be good enough. If it works these things can be declared again in your own solution.

Some Info on the used functions:
BindMoniker
CreateBindCtx
MkParseDisplayName
BIND_OPTS2

Creak answered 20/2, 2017 at 20:51 Comment(0)
A
0

The remote COM needs to be accessed by Queue or DCOM. You need to export the application proxy on the server when accessing by DCOM. And install the proxy in the client PC.

The COM activation type must be configured as "Server Application" to export application proxy.

After installing application proxy, the client can directly call

moniker = $"new:{new Guid(MsgInClassId)}";
try
{
    var M = Marshal.BindToMoniker(moniker);
}

For the partition, it's designed to show each user with own application set. If the current partition is associated to the user, the partition needs not to be written in codes.

Acuity answered 24/2, 2017 at 13:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.