通过COM4J从.NET方法返回接口数组

20

我怎样才能通过COM4J从C#方法返回一个实现了COM接口的对象数组给Java方法呢?

生成数组的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的类似内容似乎是可能的。


愚蠢的问题,但是是 GetAnimal 还是 getAnimal?错误显示无法解析 getAnimal。 - zam664
@zam664 两种都是正确的;COM4J 在生成 Java 类时会删除方法名的首字母大写。 - finnw
猪和牛既不是ComVisible,也没有我能看到的Java类。这可能是缺失的东西吗? - Jan Hruby
@JanHruby:它们并不打算成为ComVisible,也不应该需要成为。它们实现了一个ComVisible接口并由一个ComVisible方法返回,这已足以使它们可访问。正如我在回答评论中所说的那样,如果我通过集合而不是数组导出相同的对象,则它可以工作。 - finnw
1个回答

1

你尝试过将你的接口声明为InterfaceIsDual和/或InterfaceIsIDispatch吗?

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

大多数 COM 互操作库需要一个分配接口;但是,我不确定 COM4J 是否需要。你也可以使用抽象基础 COM 类来实现相同的目标,而不是使用接口,但如果上述变更不能让您运行起来,我会感到惊讶。


COM4J使用IUnknown接口 - 不需要Dispatch。我的问题只在于数组(导出集合可以正常工作)。 - finnw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接