Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return array of interface from a .NET method via COM4J

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 of SAFEARRAY(IUnknown*)*
    (this throws the same exception.)
  • Removing the 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

like image 958
finnw Avatar asked Oct 03 '12 14:10

finnw


1 Answers

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.

like image 120
csharptest.net Avatar answered Nov 03 '22 19:11

csharptest.net