0

I've just implemented a out-of-process COM server using NET7 and C# following this sample. Everything is working like a charm, but I noticed that after the last client disconnect the COM process continues running.

After a while I get several instances of the COM server running and they never finish.

Looking for ways to close the COM server when it is not being used, I came across this class ServicedComponent, which could control the life cycle of a COM server and detect when the last client disconnect.

There is no such class in NET7.

How can I achieve the same in NET7, without relying on the clients to call a function to notify the server it is disconnecting?

dougcunha
  • 1,208
  • 12
  • 13
  • please provide a [mcve] – NineBerry Dec 23 '22 at 02:00
  • You might find relevant hints here https://learn.microsoft.com/en-us/windows/win32/com/out-of-process-server-implementation-helpers: – NineBerry Dec 23 '22 at 02:01
  • You don't have to write your own COM exe host which is often more complicated than expected. You can write a DLL and use Windows as a host, it's called a COM+ Surrogate https://stackoverflow.com/a/34977063/403671. The easiest way it is to use COM+ "Components Service", which provides all sort of extra configurable services like shown here: https://stackoverflow.com/a/42581513/403671 or here https://stackoverflow.com/a/54128090/403671 – Simon Mourier Dec 23 '22 at 10:19
  • @NineBerry I have no idea how to write a "minimal reproducible example" in this case, since there is a lot involved. I have a out-of-process COM server written in C# and also a client in Delphi. I don't know exactly who is at fault here. It can be the Delphi client who needs to release the reference to the COM object or something that need to be implemented within my C# code. – dougcunha Dec 23 '22 at 10:42
  • @SimonMourier I've tried to use the default Windows Host, but I could not find a single practical example that worked for me. – dougcunha Dec 23 '22 at 10:45
  • I've just found this [github commit](https://github.com/avarghesein/ActiveX.NET/commit/2d665c1c88c0cb938262b86cac5642cda48c2cfc) which seems to address a similar problem. It is from a project that is a bit old and is targeting NET4.5, but maybe I can find some hints there. – dougcunha Dec 23 '22 at 10:47
  • I posted 2 links that explain how to configure a COM dll to be available as an Out-Of-Process COM server. There's zero code to write. – Simon Mourier Dec 23 '22 at 10:49
  • @SimonMourier It's for a legacy plugin system which the client is a Delphi application, this has to be done programmatically, so, "zero code" it's a no in this case. – dougcunha Dec 23 '22 at 11:01
  • A COM client can be any type of application, legacy or not, Delphi or whatever language, provided it supports COM. I meant zero code for exposing a COM DLL as an Out-Of-Process COM server but obviously, you always need to code the DLL (and you can do that in any .NET version). Anyway, if you have a problem with the Microsoft sample and want to stick to it, you must provide a minimal sample that reproduces what you observe because the sample works as it should. – Simon Mourier Dec 23 '22 at 11:08
  • @SimonMourier the sample really works, but it's explicit that it does not control life cycle as we can see in [LocalServer.cs, line 72](https://github.com/dotnet/samples/blob/c2e3eac867c3ea893fb0e1d4fed609ba0cf5bfbb/core/extensions/OutOfProcCOM/COMRegistration/LocalServer.cs#L73) `This sample does not handle lifetime management of the server. For details around ref counting and locking of out-of-proc COM servers, see https://docs.microsoft.com/windows/win32/com/out-of-process-server-implementation-helpers` – dougcunha Dec 23 '22 at 13:16

1 Answers1

0

Thanks everyone who helped with tips.

I've just found a simple solution for this problem. What I did was to implement a CheckAlive function that is called by the server and is executed on the client side using a callback. When all the clients are gone I can finish the server.

internal class BasicClassFactory<T> : IClassFactory where T : new()
{
    private readonly List<T> _instances = new();
    private readonly object lockObj = new();

    public void CreateInstance(
        [MarshalAs(UnmanagedType.Interface)] object pUnkOuter,
        ref Guid riid,
        out IntPtr ppvObject
    )
    {
        Type interfaceType = GetValidatedInterfaceType(typeof(T), ref riid, pUnkOuter);

        object obj = new T();

        if (pUnkOuter != null)
            obj = CreateAggregatedObject(pUnkOuter, obj);

        ppvObject = GetObjectAsInterface(obj, interfaceType);
    
        lock (lockObj)
        {
            //Each client gets a new instance of the COM object,
            //so I keep a list of them for isAlive check purposes.
            _instances.Add((T)obj);
        }
    }

    public void CheckAlive(Func<T, bool> checkFunc)
    {
        lock (lockObj)
        {
            //Check if any instance is still alive, by calling
            //a callback that is executed on the client side.
            //If your object implements IDisposable you should 
            //call dispose as well.
            for (int i = _instances.Count - 1; i >= 0; i--)
            {
                try
                {
                    if (!checkFunc(_instances[i]))
                    {
                        _instances.Remove(_instances[i]);
                        (_instances[i] as IDisposable)?.Dispose();
                    }
                }
                catch (Exception)
                {
                    //if I get an error, it means the client is not alive
                    //and the memory got corrupted.
                    _instances.Remove(_instances[i]);                    
                    (_instances[i] as IDisposable)?.Dispose();
                }
            }
        }
    }

    public int AliveCount
    {
        get
        {
            lock (lockObj)
            {
                return _instances.Count;
            }
        }
    }
}

My local server keeps running by a waiter, this way:

public void Run()
{
    _waiter = new ManualResetEvent(false);

    _waiter.WaitOne();
}

So, all I need to do is to have a Timer checking every X seconds if there is any client alive, if not I can release the waiter and the server closes itself automatically.

var timer = new Timer
(
    _ =>
    {
        classFactory.CheckAlive(checAliveFunc);

        if (classFactory.AliveCount == 0)
            _waiter?.Set();
    },
    null,
    TimeSpan.FromSeconds(20), //Runs for the first time after 20 seconds
    TimeSpan.FromSeconds(5) //Check every 5 seconds
);
dougcunha
  • 1,208
  • 12
  • 13