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

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

c#
remote-server
com+
activation
moniker
asked on Stack Overflow Jan 3, 2017 by Hamrin • edited May 23, 2017 by Community

2 Answers

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

answered on Stack Overflow Feb 20, 2017 by Uwe Hafner
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.

answered on Stack Overflow Feb 24, 2017 by VHao • edited Feb 24, 2017 by Donald Duck

User contributions licensed under CC BY-SA 3.0