Search code examples
c#delphicominteropcom-interop

How to Consume COM Server (ATL, DLL Surrogate) in .NET C# WinService?


I have a DLL Com Server, actually used only by one old Delphi exe-application.

COM Server is written many years ago (not by me) in C++ ATL. It implements callbacks (event - is it the same?) - using outgoing interface IConnectionPointImpl. Class Factory is singleton (marked with DECLARE_CLASSFACTORY_SINGLETON)

Now it is required that this COM Server have to be shared between more than one clients: both Delphi and C# (.NET 2.0, VS2008). I put it as DllSurrogate and now I can use it from multiple Delphi clients, using class inherited from TOleServer, overriding GetServer method to use always CoCreateInstance (because GetActiveObject usually fails) and it's working.

Now I need to consume it from C# WinService and I don't know from where to start. I wrote a little C# Hello-world which use WinApi CoCreateInstance and DllImport("ole32.dll") - I am able to connect to existing instance from COM Server but cannot subscribe to events.

Here is the DLL META-DATA imported by VS: enter image description here

I don't know if this is correct way. Here is aproximative code:

using System;
using System.Collections.Generic;
using System.Text;
using SWLMLib;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;


namespace TestSWLM
{
    [Flags]
    enum CLSCTX : uint
    {
        //... defines here CLSCTX
    }

    class Program
    {
        [DllImport("ole32.dll", EntryPoint = "CoCreateInstance", CallingConvention = CallingConvention.StdCall)]
        static extern UInt32 CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
           IntPtr pUnkOuter, UInt32 dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
           [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject);

        public static void AboutExpireHandler(IFeature pFeature, int HoursRemained)
        {
            Console.WriteLine("AboutExpireHandler, pFeature = {0}", pFeature.Code);        
        }

        static void Main(string[] args)
        {
            try
            {
                SWLMLib.ISWMgr lintfSWLMgr = null;

                object instance = null;
                UInt32 dwRes = CoCreateInstance(new Guid("8EAAFAD7-73F8-403B-A53B-4400E16D8EDF"), IntPtr.Zero, (uint)CLSCTX.CLSCTX_LOCAL_SERVER,
                    new Guid("00000000-0000-0000-C000-000000000046"), out instance);
                SWLMLib.SWMgrClass lSWLMgr = null;
                unsafe
                {
                    lintfSWLMgr = (instance as SWLMLib.ISWMgr);
                    Type liType = instance.GetType();
                }

                if (lintfSWLMgr != null)
                {
                    IntPtr iuknw = Marshal.GetIUnknownForObject(lintfSWLMgr);

                    IntPtr ipointer = IntPtr.Zero;
                    Guid lICPCGuid = typeof(IConnectionPointContainer).GUID;
                    Guid lICPGuid = typeof(IConnectionPoint).GUID;
                    Guid lIEv = new Guid("{C13A9D38-4BB0-465B-BF4A-487F371A5538}");
                    IConnectionPoint lCP = null;
                    IConnectionPointContainer lCPC = null;
                    Int32 r = Marshal.QueryInterface(iuknw, ref lICPCGuid, out ipointer);
                    lCPC = (IConnectionPointContainer)Marshal.GetObjectForIUnknown(ipointer);
                    lCPC.FindConnectionPoint(ref lIEv, out lCP);
                    Int32 outID;
                    lCP.Advise(???, out outID); // HERE I don't know what to do further
                    lIEvEv.FeatureAboutToExpire += AboutExpireHandler;
                }
            }
            catch (Exception E)
            {
                Console.WriteLine(E.Message);
                throw;
            }

            Console.ReadLine();
        }
    }
}

Any Advices, links and know-hows are welcome.


Solution

  • It seems that I succeeded to connect to- and handle Events of DLL (in-proc) Com Server.

    1. I put COM Server as DLL Surrogate (HowTo here).
    2. Delphi Client Side - form my COM Wrapper Class, inheriting from TOleServer Class I overridden GetServer Method:

    function TSWMgr.GetServer: IUnknown; begin OleCheck(CoCreateInstance(ServerData^.ClassId, nil, CLSCTX_LOCAL_SERVER, IUnknown, Result)); end;

    1. C# (Hello-World Client) side (after consulting some HowTos like this):

    //using SWLMLib; //using System.Runtime.InteropServices; //using System.Runtime.InteropServices.ComTypes;

    [Flags]
    enum ReturnCode : uint
    {
        S_OK = 0, S_FALSE = 1, REGDB_E_CLASSNOTREG = 0x80040154, CLASS_E_NOAGGREGATION = 0x80040110, E_NOINTERFACE = 0x80004002, E_POINTER = 0x80004003
    }
    
    [Flags]
    enum CLSCTX : uint
    {
        CLSCTX_INPROC_SERVER = 0x1, CLSCTX_INPROC_HANDLER = 0x2, CLSCTX_LOCAL_SERVER = 0x4,
        //... //others
        CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER
    }
    
    /// <summary>
    /// Sink Class implementig COM Server outgoing interface SWLMLib.ISWMgrEvents 
    /// </summary>
    [ClassInterface(ClassInterfaceType.None)]
    class MySink : SWLMLib.ISWMgrEvents
    {
        public void FeatureAboutToExpire(SWLMLib.IFeature pFeature, int HoursRemained)
        {
            Console.WriteLine("{0} FeatureAboutToExpire: Feature {1} Hours={2}", DateTime.Now, pFeature.Code, HoursRemained);
            Marshal.ReleaseComObject(pFeature); //WTF??? Without this line COM Server object is not released!
        }
    
        public void FeatureExpired(SWLMLib.IFeature pFeature)
        {
            Console.WriteLine("FeatureExpired: Feature {0}", pFeature.Code);
            Marshal.ReleaseComObject(pFeature); //Without this line COM Server object is not released!
        }
    }
    
    class Program
    {
        //Import "CoCreateInstance" to play with run context of created COM-object (3rd parameter)
        [DllImport("ole32.dll", EntryPoint = "CoCreateInstance", CallingConvention = CallingConvention.StdCall)]
        static extern UInt32 CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
           IntPtr pUnkOuter, UInt32 dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
           [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject);
    
        static void Main(string[] args)
        {
            try
            {
                Guid SWMgrClassObjectGuid = typeof(SWLMLib.SWMgrClass).GUID;    //{8EAAFAD7-73F8-403B-A53B-4400E16D8EDF}
                Guid IUnknownGuid = new Guid("00000000-0000-0000-C000-000000000046"); //Can it be written in more pretty style?
    
                SWLMLib.ISWMgr lintfSWLMgr = null;
    
                /* This will create IN-PROC Server because, it seems CoCreateInstance will be invoked with CLSCTX_INPROC_SERVER flag settled
                Type type = Type.GetTypeFromCLSID(SWMgrClassObjectGuid), true);
                object instance0 = Activator.CreateInstance(type);
                lintfSWLMgr = (instance0 as SWLMLib.ISWMgr); */
    
                Guid Ev1 = typeof(ISWMgrEvents).GUID;
                object instance = null;
    
                unsafe
                {
                    UInt32 dwRes = CoCreateInstance(SWMgrClassObjectGuid,
                                                    IntPtr.Zero,
                                                    (uint)(CLSCTX.CLSCTX_LOCAL_SERVER), //if OR with CLSCTX_INPROC_SERVER then INPROC Server will be created, because of DLL COM Server
                                                    IUnknownGuid,
                                                    out instance);
                    if (dwRes != 0)
                    {
                        int iError = Marshal.GetLastWin32Error();
                        Console.WriteLine("CoCreateInstance Error = {0}, LastWin32Error = {1}", dwRes, iError);
                        return;
                    }
                    lintfSWLMgr = (instance as SWLMLib.ISWMgr);
                }
    
                if (lintfSWLMgr != null)
                {
                    //lintfSWLMgr.InitializeMethod(...); //Initialize object
    
                    //Find Connection Point for Events
                    Guid ISWMgrEventsGuid = typeof(SWLMLib.ISWMgrEvents).GUID;      //{C13A9D38-4BB0-465B-BF4A-487F371A5538} Interface for Evenets Handling
                    IConnectionPoint lCP = null;
                    IConnectionPointContainer lCPC = (instance as IConnectionPointContainer);
                    lCPC.FindConnectionPoint(ref ISWMgrEventsGuid, out lCP);
    
                    MySink lSink = new MySink();
                    Int32 dwEventsCookie;
                    lCP.Advise(lSink, out dwEventsCookie);
                    Console.WriteLine("Waiting for Events Handling...");
                    Console.WriteLine("Press ENTER to Exit...");
    
                    Console.ReadLine(); // Until Eneter is not hit, the events arrive properly
                    //Here starting to Unsubscribe for Events and Com Objects CleanUP
                    lCP.Unadvise(dwEventsCookie);
                    Marshal.ReleaseComObject(lCP); 
                    Marshal.ReleaseComObject(lintfSWLMgr); 
                }
    
            }
            catch (Exception E)
            {
                Console.WriteLine(E.Message);
                throw;
            }
        }
    }
    

    Maybe this is not the best way (like TblImp.exe and or COM Wrappers) but this raw way works.