How can I return an array of objects (implementing a COM interface) from a C# method to a Java method via COM4J?
Example C# class that generates an array:
using System;
using System.Runtime.InteropServices;
namespace Example
{
[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAnimal
{
string Speak();
}
[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFarm
{
[return:MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType=VarEnum.VT_UNKNOWN)]
IAnimal[] GetAnimals();
}
[ComVisible(true), ClassInterface(ClassInterfaceType.None)]
public class Farm : IFarm
{
public IAnimal[] GetAnimals()
{
return new IAnimal[] { new Cow(), new Pig() };
}
}
internal class Cow: IAnimal
{
public string Speak()
{
return "Moo";
}
}
internal class Pig: IAnimal
{
public string Speak()
{
return "Oink";
}
}
}
The interface declaration in the resulting .tlb looks like this:
[
odl,
uuid(1FB5E376-E78D-3A2E-BEF3-F3C798FCF44C),
version(1.0),
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Example.IFarm")
]
interface IFarm : IUnknown
{
HRESULT _stdcall GetAnimals([out, retval] SAFEARRAY(IUnknown*)* pRetVal);
};
Java client code:
import com4j.*;
public class Example {
public static void main(String[] args) {
IFarm farm = ClassFactory.createFarm();
Com4jObject[] animals = farm.getAnimals();
for (Com4jObject o: animals) {
IAnimal animal = o.queryInterface(IAnimal.class);
if (animal != null) {
animal.speak();
}
}
}
}
This compiles but I get this exception at runtime:
Exception in thread "main" com4j.ComException:
unexpected conversion type: 500 : .\invoke.cpp:470
at com4j.Wrapper.invoke(Wrapper.java:185)
at $Proxy5.getAnimals(Unknown Source)
at MainClass.main(MainClass.java:7)
Caused by: com4j.ComException: unexpected conversion type: 500 : .\invoke.cpp:470
at com4j.Native.invoke(Native Method)
at com4j.StandardComMethod.invoke(StandardComMethod.java:35)
at com4j.Wrapper$InvocationThunk.call(Wrapper.java:354)
at com4j.Task.invoke(Task.java:55)
at com4j.ComThread.run0(ComThread.java:157)
at com4j.ComThread.run(ComThread.java:137)
Other things I have tried:
- Marshalling as a
SAFEARRAY(VARIANT)*instead ofSAFEARRAY(IUnknown*)*
(this throws the same exception.) - Removing the
MarshalAsattribute (tlbimpfails to create the proxy method)
Is there a way to marshal the array so that COM4J can convert it to a valid Java array?
Alternatively is there a way to allocate an array in Java and allow the .NET method to populate it? (I tried this but the .NET method receives a copy of the array and the Java code never sees the objects inserted into the copy. Maybe there's a way to override this?)
Edit: This may be related: https://stackoverflow.com/a/6340144/12048 - something similar appears to be possible from VBScript
ComVisibleand they should not need to be. They implement aComVisibleinterface and are returned by aComVisiblemethod, which is enough to make them accessible. And as I said in a comment on an answer, it works if I export the same objects through a collection instead of an array.