views:

1408

answers:

5

Hi,

I'm writing C# code that needs to connect to COM events. I implemented the use of IConnectionPointContainer and IConnectionPoint thus:

      IConnectionPointContainer connectionPointContainer = internalGenerator as IConnectionPointContainer;
      if (connectionPointContainer == null)
      {
        Debug.Fail("The script generator doesn't support the required interface - IConnectionPointContainer");
        throw new InvalidCastException("The script generator doesn't support the required interface - IConnectionPointContainer");
      }
      Guid IID_IScriptGeneratorEvents = typeof(IScriptGeneratorCallback).GUID;
      connectionPointContainer.FindConnectionPoint(ref IID_IScriptGeneratorEvents, out m_connectionPoint);
      m_connectionPoint.Advise(this, out m_cookie);

The problem is that when the COM server is actually implemented in .Net (say, C#), after .Net creates it, it handles it as a .Net object, not a COM object. Since the .Net object doesn't implement the IConnectionPointContainer interface, I get null when trying to cast the object to that interface.

Any idea how can i workaround this? I can of course implement IConnectionPointContainer by myself in the C# COM server, however I would like a simpler solution, which I can easily explain to other developers which need to implement the COM server.

P.S I must use IConnectionPointContainer as the COM server may be implemented in non-.Net (C++, Java).

Thanks, Inbar

+1  A: 

IConnectionPointContainer is implemented on the CCW (COM callable wrapper) that .NET automatically generates when exposing your .NET object as a COM object externally.

Try calling Marshal.GetComInterfaceForObject on the .NET object to get a COM interface for IConnectionPointContainer rather than just casting it.

update ... and if that doesn't work Marshal.GetIUnknownForObject must return something, and maybe that will then support a Marshal.QueryInterface call.

Rob Walker
A: 
IntPtr ptr = Marshal.GetComInterfaceForObject(internalGenerator, typeof(IConnectionPointContainer));

Marshal.GetComInterfaceForObject didn't work - it throws an InvalidCastException, likely for the same reason: it 'knows' it's a .Net object, so it just tries to cast it directly to the Type instead of calling queryInterface. But I think Marshal is a good direction, I'll see if I can work with it.

Thanks, Inbar

Inbar Shani
A: 

I didn't find a way to do this. Eventually I will define another interface in .Net and will write 2 code paths, one for .Net objects and one for real COM objects.

Inbar Shani
A: 

The problem is that doing a GetIUnknownForObject call returns a pointer which you can then successfully call to attain IConnectionPointContainer for the object using its GUID. But, that call to QueryInterface then simply returns the original .NET object, not an IConnectionPointContainer interface.

I'm also stuck with this, and if anyone has any further insights please do share. My scenario is that I'm exposing a .NET control as an ActiveX using COM interop. I have a ComSourceInterface defined for the event sink, but in a VB6 host, the events are not being hooked up as expected. I'm therefore trying to get the IConnectionPointContainer interface for my exposed .NET control in order to hook up the events manually, but cannot get to this interface, if indeed it is implemented - or perhaps I am simply looking at the wrong object?

MarkS
A: 

I got a little further, as I have been having the same problem manually connecting events when exposing a .NET control as an ActiveX using COM interop.

If you dig a little into the UserControl class using Reflector (e.g. Redgate Reflector), you'll see an 'ActiveXImpl' nested class member, which contains another nested static class called 'AdviseHelper', which has members ComConnectionPointContainer and ComConnectionPoint. It also has helper functions to connect points as you require.

There's a problem. When an event is hooked up by COM interop from your control (the event source) to the connection point container (which contains the event sink for your control's events), the IQuickActivate.QuickActivate function gets called, which then calls AdviseHelper class' 'AdviseConnectionPoint' function.

An IUnknown interface pointer to the event sink, on your client (i.e. not your control, the thing containing it) is passed to this QuickActivate function (parameter 'pUnkEventSink'. In reflector this function looks like this, and I've highlighted where it does the actual event hookup:

internal void QuickActivate(UnsafeNativeMethods.tagQACONTAINER pQaContainer, UnsafeNativeMethods.tagQACONTROL pQaControl)
{
    int num;
    this.LookupAmbient(-701).Value = ColorTranslator.FromOle((int) pQaContainer.colorBack);
    this.LookupAmbient(-704).Value = ColorTranslator.FromOle((int) pQaContainer.colorFore);
    if (pQaContainer.pFont != null)
    {
        Control.AmbientProperty ambient = this.LookupAmbient(-703);
        IntSecurity.UnmanagedCode.Assert();
        try
        {
            Font font2 = Font.FromHfont(((UnsafeNativeMethods.IFont) pQaContainer.pFont).GetHFont());
            ambient.Value = font2;
        }
        catch (Exception exception)
        {
            if (ClientUtils.IsSecurityOrCriticalException(exception))
            {
                throw;
            }
            ambient.Value = null;
        }
        finally
        {
            CodeAccessPermission.RevertAssert();
        }
    }
    pQaControl.cbSize = UnsafeNativeMethods.SizeOf(typeof(UnsafeNativeMethods.tagQACONTROL));
    this.SetClientSite(pQaContainer.pClientSite);
    if (pQaContainer.pAdviseSink != null)
    {
        this.SetAdvise(1, 0, (IAdviseSink) pQaContainer.pAdviseSink);
    }
    IntSecurity.UnmanagedCode.Assert();
    try
    {
        ((UnsafeNativeMethods.IOleObject) this.control).GetMiscStatus(1, out num);
    }
    finally
    {
        CodeAccessPermission.RevertAssert();
    }
    pQaControl.dwMiscStatus = num;
    if ((pQaContainer.pUnkEventSink != null) && (this.control is UserControl))
    {
        Type defaultEventsInterface = GetDefaultEventsInterface(this.control.GetType());
        if (defaultEventsInterface != null)
        {
            IntSecurity.UnmanagedCode.Assert();
            try
            {
                **AdviseHelper.AdviseConnectionPoint(this.control, pQaContainer.pUnkEventSink, defaultEventsInterface, out pQaControl.dwEventCookie);**
            }
            catch (Exception exception2)
            {
                if (ClientUtils.IsSecurityOrCriticalException(exception2))
                {
                    throw;
                }
            }
            finally
            {
                CodeAccessPermission.RevertAssert();
            }
        }
    }
    if ((pQaContainer.pPropertyNotifySink != null) && UnsafeNativeMethods.IsComObject(pQaContainer.pPropertyNotifySink))
    {
        UnsafeNativeMethods.ReleaseComObject(pQaContainer.pPropertyNotifySink);
    }
    if ((pQaContainer.pUnkEventSink != null) && UnsafeNativeMethods.IsComObject(pQaContainer.pUnkEventSink))
    {
        UnsafeNativeMethods.ReleaseComObject(pQaContainer.pUnkEventSink);
    }
}

The 'pUnkEventSink' variable is passed in to this function via the tagQACONTROL structure, but as you can see, unlike the IAdviseSink, control container, font style etc., this variable is not set to any member of the 'ActiveXImpl' class, and hence you cannot get to it after this function has initially been called by the framework.

You need to get this IUnknown pUnkEventSink variable to call AdviseHelper.AdviseConnectionPoint() function, which will do the manual event hookup. And this is the problem I've had - sadly you simply can't seem to get hold of it.

Anyone else had any further developments with this do let me know!

MarkS