Ok, so, please, don't ask why I've found myself writing a COM component in C#, and am using it in VB6. Just accept that fact that I need to.
This COM component needs to return an array of data to VB6. At first I thought "Why not just have my C# project reference the VB6 runtime via interop, and I'll be able to instantiate a VB6 Collection class, and return that to VB6, and it will be happy.
But no, when I try to instantiate a VBA.CollectionClass I get:
Retrieving the COM class factory for component with CLSID {A4C4671C-499F-101B-BB78-00AA00383CBB} failed due to the following error: 80040154.
After much googling, the closest answer I could get was that I was trying to instantiate a VBA.Collection on an x64 platform. Um, the last time I checked my Pentium-D was a 32 bit processor. I even went so far as to force my C# COM component to compile to x86 specifically instead of the
'Any CPU' configuration. Still, the error persisted.
So, I had to bail on that idea, and come up with
"Hey, what about just returning an array? What a great idea! I performed a test.
I created a test COM visible interface and class, something like:
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
String[] GetStrings();
}
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Test : ITest {
public String[] GetStrings() {
List foo = new List();
foo.Add("hello");
foo.Add("bye-bye");
return foo.ToArray();
}
}
Then, the corresponding test-code in VB6:
Dim testObject as Test
Dim strings() as String
set testObject = new Test
strings = testObject.GetStrings()
It worked.I was happy, and I continued working on my C# code as planned.
Days later, I have hundreds of lines of code down in C#, and I'm at a good point to test what I've written. Now, my objects weren't going to be returning string-arrays like the test case above, rather, they're going to be returning arrays of an interface defined in my C# COM component, something like:
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IBusinessObject {
Int32 GetSomething();
}
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IFoo {
IBusinessObject[] GetBusinessObjects();
}
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class BusinessObject : IBusinessObject {
/* you get the idea */
}
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Foo : IFoo {
public IBusinessObject[] GetBusinessObjects() {
List bos = new List();
/* Add some BusinessObjects to 'bos' */
return bos.ToArray();
}
}
Then, the corresponding VB6 code:
Dim foo as Foo
Dim myObjects() as BusinessObject
set foo = new Foo
myObjects = foo.GetBusinessObjects()
Unfortunately, it didn't work. While no exception was thrown in C#, somewhere between
return bos.ToArray() and VB6's assignment to the
myObjects array a "
Type Mismatch" error was thrown in VB6. I couldn't figure out why, though.
I tried catching the array returned from
.GetBusinessObjects() into a Variant like:
Dim myObjects as Variant
myObjects = foo.GetBusinessObjects()
I still received the "
Type Mismatch" error. I was really lost here, because in VB6-land a Variant is the closest thing you're going to get to a generic object pointer as you're going to get.
I again didn't garner much assistance from another thorough Google spelunking. In my searches, I did stumble across the
MarshalAs attribute, but since I'm not a COM-master I wasn't entirely sure what I should be marshaling an array of interfaces as in order to safely reach COM-world. I blindly tried a number of things, and always ended up with "
Type Mismatch". I was loosing hope. (As a side note I desperately need to get a copy of
Adam Nathan's book
.NET and COM: The Complete Interoperability Guide.)
Finally, I stumbled upon it, and I'm not entirely sure why I didn't try it first:
The Answer!
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IFoo {
[MarshalAs(UnmanagedType.AsAny)]
IBusinessObject[] GetBusinessObjects();
}
What's weird is that this produces a compiler warning making it sound like the attribute is not of any use:
Type library exporter warning processing 'MyNamespace.IFoo.GetBusinessObjects(#0), MyProject'. Warning: Type contains [MarshalAs(AsAny)], which is only valid for PInvoke. The MarshalAs directive was ignored.
If you place the attribute on the actual implementation you do not get the compiler warning...but you also get the
Type Mismatch again. So, it would seem the warning is ignorable as it is applying to something.
Something tells me that once I get my hands on Adam Nathan's book, this will become a lot more obvious to me, and/or I'll find a better answer. Either way, I've got a solution for now.
So, why this post? Mostly as a note-to-self as to how to solve the problem in the future, but there's the hope that some poor sap such as myself has had the exact same problem and that this will turn up for them while they spelunk Google.
If this proves useful to anyone, please, drop me a line.