Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Generics and COM Interop don't mix


:P
On this page:

COM Interop is rarely fun, but it looks like it's getting to be less and less useful as time goes on and new .NET Runtime features come along that don't work well over COM.

It appears that Generic types can't be exported over COM and be usable to a client like Visual FoxPro. When I create a class like this:

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class Person
    {
        public string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }
        private string _Name = "Rick";
        
        
        public DateTime Entered
        {
            get { return _Entered; }
            set { _Entered = value; }
        }
        private DateTime _Entered = DateTime.Now;

        
        public List<string> NickNames
        {
            get { return _NickNames; }
            set { _NickNames = value; }
        }
        private List<string> _NickNames = new List<string>();

    }

and pull this up in VFP I can't see the Nicknames List<string> property:

The generic interface is not exported - at least not to IDispatch.

Accessing the Nicknames property does return an Object to me, but I can't seem to do anything with this object. It's not NULL, but also doesn't have any properties that should be there like Count or in case of conversion to an array a length or any array elements for that matter.

It's not showing up as Collection or array, and basically any operation against it over COM fails. Apparently this is true of any Generic type which of course are pretty common in the .NET 2.0 framework and most collection types that are publicly exposed somewhere in their class hierarchy are based on one of the generic types.

If I replace the code above with an ArrayList instead I can see the list fine and COM Interop transforms that array list into a SafeArray that is visible in VFP as a standard array. This change works, but of course requires that you actually have control over the object.

Good ole' Reflection to the Rescue

So is it possible to make this work? Yes, but it's hideous. Basically you can use Reflection in a helper class to provide the type management so that .NET rather than the COM client code is accessing the collection directly.

As part of a wwDotNetBridge class I've created I got methods like this:

        public object InvokeMethod(object Instance, string Method)
        {
            return wwUtils.CallMethod(Instance,Method);
        }
        public object InvokeMethod_OneParm(object Instance, string Method, object Parm1)
        {
            return wwUtils.CallMethod(Instance, Method,Parm1);
        }

        public object InvokeMethod_TwoParms(object Instance, string Method, object Parm1, object Parm2)
        {
            return wwUtils.CallMethod(Instance, Method, Parm1, Parm2);
        }

        public object GetProperty(object Instance, string Property)
        {
            return wwUtils.GetProperty(Instance, Property);
        }

        public void SetProperty(object Instance, string Property, object Value)
        {
            wwUtils.SetProperty(Instance, Property, Value); 
        }

where these wwUtils methods are Reflection helpers. With this code I can then do:

loPerson = loBridge.GetPerson()? loPerson.ToString()
? loPerson.Name
loPerson.Name = "John"

loNick = loPerson.NickNames

? loBridge.InvokeMethod_OneParm(loNick,"Add","ricky")
? loBridge.InvokeMethod_OneParm(loNick,"Add","richard")
? loBridge.InvokeMethod_OneParm(loNick,"Add","ricci")
? loBridge.InvokeMethod(loNick,"ToString")

? loBridge.GetProperty(loNick,"Count")  && 3

As I said that's pretty ugly but I have a few wrappers around this on the VFP end so the code at least doesn't need to use the _OneParam, TwoParms etc. nastiness. It works and get around some of the issues but as I said it's ugly.

In most situations when working in an interop scenario where types are encountered that just don't want to pass the COM boundary I find it best to just build a wrapper function in a .NET class that can act as a proxy and pass the data back in a COM capable format. 

Can we make COM just go away for good? Please? <s> 

Posted in .NET  COM  FoxPro  

The Voices of Reason


 

Josh Berke
July 11, 2007

# re: Generics and COM Interop don't mix

FYI, if you run RegAsm on a type that contains a Generic you get the following message:

Type library exporter warning processing 'GenericsCOMInterop.IClass1.GetNickName
s(#0), GenericsCOMInterop'. Warning: Type library exporter encountered a generic
type instance in a signature. Generic code may not be exported to COM.

I find it interesting you were able to still access the property since the property is not defined within the IDL created when exporting the type library.

I aggree it would be nice to see the end of COM but since COM is so ingrained, I doubt we will see the end of it for a very long time. The .net runtime is a COM server, so is visual studio;-)

Rick Strahl
July 11, 2007

# re: Generics and COM Interop don't mix

Josh - the .NET framework is a COM object (mscoree), but it's mostly a bootstrapper - the actual 'COM' calls are just proxied through this bootstrapper. Getting at the types with Reflection makes sense because when you do that you're really just accessing a .NET object through Reflection and returning the 'simple' type back. The reference is just that a pointer and the actual object lives inside of the runtime.

Actually I think it's interesting that you can pass types back that arent explicitly marked as COM visible. It looks like you can pass just about anything and in most cases you can even access these object properties directly without requiring Reflection. Not quite sure how that works since there's no type library for the COM client to look at. <shrug>

Josh Berke
July 11, 2007

# re: Generics and COM Interop don't mix

The reflection makes sense, it;s what you identified that I find interesting which is passing types back that aren't COM visible.

As I think about this more there is no reason that a closed(I think this is the term they are called) generic shouldn't be accesible via COM. As long as the type is specified such as in your example List<string> should be visible.

I think the best answer is get away from COM;-)

Roman Verkovsky
November 20, 2007

# re: Generics and COM Interop don't mix

Sometimes trick with default interface can help.
Not so pretty, but usable.
    [ComVisible(true)]
    public interface IPerson
    {
        string Name { get;set;}
        DateTime Entered { get;set;}
        IList NickNamesList { get;}
        ICollection NickNamesCollection { get;}
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComDefaultInterface(typeof(IPerson))]
    public class Person:IPerson
    {
        [ComVisible(false)]
        public List<string> NickNames
        {
            get { return _NickNames; }
            set { _NickNames = value; }
        }
        private List<string> _NickNames = new List<string>();

        #region IPerson Members
        IList IPerson.NickNamesList
        {
            get { return this.NickNames; }
        }

        ICollection IPerson.NickNamesCollection
        {
            get { return this.NickNames; }
        }
        #endregion
        ....
    }

Sandeep
November 29, 2010

# re: Generics and COM Interop don't mix

I am also getting the same warnings only in Debug configuration but not in Release when I use tlbexp.
The story is as:
I have created one C# component which is consumed by another C# dll. Both of them are COM compliant. When I am directly using the second C# dll in ATL C++ exe, I can build the ATL C++ project in Release but not in Debug. In Debug, while building, it gives the following error: #import referenced a type from a missing type library; '__missing_type__' used as a placeholder

In ATL C++ application both the C# tlbs are correctly #imported along with mscorlib.tlb.
Please let me know if you know about this error.
Thanks a lot in advance for your help.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024