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:
SAFEARRAY(VARIANT)*
instead of SAFEARRAY(IUnknown*)*
MarshalAs
attribute (tlbimp
fails 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
Have you tried declaring your interfaces as InterfaceIsDual and/or InterfaceIsIDispatch?
[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAnimal
{
...
Most COM interop libraries require a dispatch interface; however, I'm not sure about COM4J. You could also use an abstract base COM class rather than an interface to achieve the same goal, but I'd be surprised if the above change doesn't get you up and running.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With