【问题标题】:JNA: Invalid memory access when calling COM method of dll via jdk-11 64-bitJNA:通过 jdk-11 64 位调用 dll 的 COM 方法时内存访问无效
【发布时间】:2021-11-16 17:41:54
【问题描述】:

我正在使用 JNA (5.9.0) 访问 COM 库 gbda_aut.dll 以连接到 OPC 服务器。当我使用 64 位版本的 dll 时出现此问题。

我已经通过代码生成器TlbCodeGenerator 创建了类。结果,我有一个接口 OPCItems 使用以下方法:

/**
 * Adds an OPCItem object to the collection
 *
 * <p>id(0x6002000b)</p>
 * <p>vtableId(18)</p>
 * @param ItemID [in] {@code String}
 * @param ClientHandle [in] {@code Integer}
 */
@ComMethod(name = "AddItem", dispId = 0x6002000b)
OPCItem AddItem(String ItemID, int ClientHandle);

此dll在OLE/COM Object Viewer中的相同方法:

[id(0x6002000b), helpstring("Adds an OPCItem object to the collection")]
HRESULT AddItem(
                [in] BSTR ItemID, 
                [in] long ClientHandle, 
                [out, retval] OPCItem** ppItem);

我的测试代码:

Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED);
ObjectFactory factory = new ObjectFactory();
OPCServer opcServer = factory.createObject(OPCServer.class);
opcServer.Connect("Matrikon.OPC.Simulation", null);
OPCGroups opcGroups = opcServer.getOPCGroups();
OPCGroup opcGroup = opcGroups.Add("TestGroup");
OPCItems opcItems = opcGroup.getOPCItems();
OPCItem opcItem = opcItems.AddItem("TestGroup.DoubleTag", 1); // invalid memory access via JDK 11 x64 + Windows 10
System.out.println(opcItem.getValue()); // works with Windows before 10 or jdk 8
opcServer.Disconnect();

连接正常,其他方法正常。错误发生在 AddItem 方法中,但异常:

Exception in thread "main" java.lang.Error: Invalid memory access
    at com.sun.jna.Native.invokeInt(Native Method)
    at com.sun.jna.Function.invoke(Function.java:426)
    at com.sun.jna.Function.invoke(Function.java:361)
    at com.sun.jna.Function.invoke(Function.java:315)
    at com.sun.jna.Function.invoke(Function.java:306)
    at com.sun.jna.platform.win32.COM.COMInvoker._invokeNativeObject(COMInvoker.java:48)
    at com.sun.jna.platform.win32.COM.Dispatch.Invoke(Dispatch.java:145)
    at com.sun.jna.platform.win32.COM.util.ProxyObject.oleMethod(ProxyObject.java:726)
    at com.sun.jna.platform.win32.COM.util.ProxyObject.invokeMethod(ProxyObject.java:450)
    at com.sun.jna.platform.win32.COM.util.ProxyObject.invoke(ProxyObject.java:256)
    at com.sun.proxy.$Proxy12.AddItem(Unknown Source)
    at test.OpcTest.main(OpcTest.java:20)

我用不同版本的JDK和Windows测试,得到如下结果:

Win 7 Win 2008 Server Win 10 Win 2012 Server
Java 8 x32 OK OK OK OK
Java 8 x64 OK OK OK OK
Java 11 x32 OK OK OK OK
Java 11 x64 OK OK exception exception
Java 16 x64 OK OK exception exception

我猜错误发生在本机 dll 代码中,但我不清楚为什么在旧版本的 Java 或 Windows 上一切正常。我无权访问 dll 代码,也无法更改它。

是否可以正确调用方法使得JDK 11 64bit + Windows 10/2012不会出现错误?

如果有任何帮助,我将不胜感激。

【问题讨论】:

  • Win10/2012 并没有改变太多的东西来解释为什么它在 Java 11+ 和/或 x64 上失败(或者可能是 Java 本身,但它似乎也值得怀疑)。肯定需要看看AddItem背后的代码
  • GC 可能会提前杀死对象。您能否在#disconnect 调用之后为所有 COM 对象添加 java.lang.ref.Reference#reachabilityFence(需要 JDK 9+)调用?如果可行,我们可以讨论如何自己解决这个问题以及如何在 JNA 中修复它。

标签: java com jna


【解决方案1】:

您要映射的函数有三个参数:

HRESULT AddItem(
                [in] BSTR ItemID, 
                [in] long ClientHandle, 
                [out, retval] OPCItem** ppItem);

但是你的实现只有两个:

OPCItem AddItem(String ItemID, int ClientHandle);

您的代码库中的某处是一个实现,它将您的双参数 Java 方法映射到三参数本机函数:

  • 确保该函数中的ppItemPointerByReference 类型
  • 确保返回的OPCItem 对象是从ppItem.getValue() 实例化的

确保您正确使用 32 位或 64 位版本的 dll。 long 参数类型可能是 32 位或 64 位。虽然在 Windows 上,LONG 始终是 32 位,但小写的 long 可能表示您的 dll 中的可变宽度类型,您可以尝试 NativeLong 映射中的 ClientHandle 类型。

【讨论】:

  • 该问题针对“c.s.j.platform.win32.COM.util”包中的COM支持。这贯穿了 IDispatch#Invoke 方法(基本上是 COM 的反射)。问题中显示的签名用于接口,但我们需要 Dispinterface。如果你有Oleview,你可以比较IXMLDOMNode 接口和IXMLDOMNode Dispinterface 上nodeName 方法的签名。然后可以看到,Dispinterface方法的返回值变成了Interface的out参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多