Возврат массива интерфейса из метода .NET через COM4J

Как я могу вернуть массив объектов (реализующий COM-интерфейс) из метода С# в метод Java через COM4J?

Пример класса C#, создающего массив:

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";
        }
    }
}

Объявление интерфейса в полученном .tlb выглядит так:

[
  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-клиента:

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();
            }
        }
    }
}

Это компилируется, но я получаю это исключение во время выполнения:

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)

Другие вещи, которые я пробовал:

  • Упорядочивание как SAFEARRAY(VARIANT)* вместо SAFEARRAY(IUnknown*)*
    (это вызывает то же исключение).
  • Удаление атрибута MarshalAs (tlbimp не может создать прокси-метод)

Есть ли способ маршалировать массив, чтобы COM4J мог преобразовать его в действительный массив Java?

В качестве альтернативы есть способ выделить массив в Java и разрешить методу .NET его заполнять? (Я пробовал это, но метод .NET получает копию массива, а код Java никогда не видит объекты, вставленные в копию. Может быть, есть способ переопределить это?)


Изменить: это может быть связано: https://stackoverflow.com/a/6340144/12048 - что-то похожее возможно из VBScript


person finnw    schedule 03.10.2012    source источник
comment
Тупой вопрос, но это GetAnimal или getAnimal. В ошибке говорилось, что не удалось разрешить getAnimal.   -  person zam664    schedule 18.02.2013
comment
@ zam664 Оба верны; COM4J сбрасывает начальные ограничения на имена методов при создании классов Java.   -  person finnw    schedule 18.02.2013
comment
Pig и Cow не являются ComVisible, и я не вижу для них классов Java. Может быть, это то, чего не хватает?   -  person Jan Hruby    schedule 03.04.2013
comment
@JanHruby: они не предназначены для того, чтобы быть ComVisible, и в этом нет необходимости. Они реализуют интерфейс ComVisible и возвращаются методом ComVisible, чего достаточно, чтобы сделать их доступными. И, как я сказал в комментарии к ответу, это работает, если я экспортирую одни и те же объекты через коллекцию вместо массива.   -  person finnw    schedule 04.04.2013


Ответы (1)


Вы пытались объявить свои интерфейсы как InterfaceIsDual и/или InterfaceIsIDispatch?

[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAnimal
{
...

Большинству библиотек COM-взаимодействия требуется диспетчерский интерфейс; однако я не уверен насчет COM4J. Вы также можете использовать абстрактный базовый COM-класс, а не интерфейс, для достижения той же цели, но я был бы удивлен, если вышеуказанное изменение не поможет вам начать работу.

person csharptest.net    schedule 20.12.2012
comment
COM4J использует интерфейсы IUnknown — Dispatch не требуется. Моя проблема только с массивами (экспорт коллекции работает нормально). - person finnw; 21.12.2012