【问题标题】:Mapping COM interface method in JNA Invalid Memory Access exception在 JNA 无效内存访问异常中映射 COM 接口方法
【发布时间】:2021-08-26 04:52:57
【问题描述】:

我正在使用 JNA 访问以下COM API。我创建了这个类,它映射了 API 的“IMGApplication”接口和一些方法

public class IMGApplication extends Dispatch {
    private static final GUID IID_IMGApplication = new GUID("5FD5D92B-A4B6-4B32-AC3D-A6FF7AE83CD8");
    
    public IMGApplication() {

    }

    private IMGApplication(Pointer pvInstance) {
        super(pvInstance);
    }
    
    public static IMGApplication create(CLSID.ByReference clsIdByRef) {
        try {
            PointerByReference pointerByRef = new PointerByReference();
            
            HRESULT hres = Ole32.INSTANCE.CoCreateInstance(clsIdByRef, null, WTypes.CLSCTX_INPROC_SERVER, IID_IMGApplication, pointerByRef);
            if (COMUtils.SUCCEEDED(hres)) {
                return new IMGApplication(pointerByRef.getValue());
            } else {
                return null;
            }
        } catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public void minimize() {
        try {
            HRESULT hres = (HRESULT)this._invokeNativeObject(8, new Object[]{this.getPointer()}, HRESULT.class);
            COMUtils.checkRC(hres);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public void maximize() {
        try {
            HRESULT hres = (HRESULT)this._invokeNativeObject(9, new Object[]{this.getPointer()}, HRESULT.class);
            COMUtils.checkRC(hres);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public void setPosition(long x, long y, long width, long height) {
        try {
            HRESULT hres = (HRESULT)this._invokeNativeObject(11, new Object[]{this.getPointer(), new NativeLong(x), new NativeLong(y), new NativeLong(width), new NativeLong(height)}, HRESULT.class);
            COMUtils.checkRC(hres);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public void shutdownMG() {
        try {
            HRESULT hres = (HRESULT)this._invokeNativeObject(12, new Object[]{this.getPointer()}, HRESULT.class);
            COMUtils.checkRC(hres);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    public void startMG(int startMode) {
        try {
            HRESULT hres = (HRESULT)this._invokeNativeObject(20, new Object[]{this.getPointer(), startMode}, HRESULT.class);
            COMUtils.checkRC(hres);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

还有 Main(它是一个 Lotus Notes Java 代理)

public class JavaAgent extends lotus.domino.AgentBase {

    public void NotesMain() {
        private boolean comWasInitialized = false;

        try {
            if(!COMUtils.comIsInitialized()) {
                Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_APARTMENTTHREADED);
            } else {
                comWasInitialized = true;
            }

            CLSID.ByReference clsIdRef = new CLSID.ByReference();
            HRESULT hRes = Ole32.INSTANCE.CLSIDFromProgID("mgAPI.mg_API", clsIdRef);
            COMUtils.checkRC(hRes);

            IMGApplication ComIMGApplication = IMGApplication.create(clsIdRef);
            
            ComIMGApplication.startMG(0); //<--- Works
                        
            ComIMGApplication.maximize(); //<--- Invalid Memory Access exception
            
            ComIMGApplication.minimize(); //<--- Invalid Memory Access exception
            
            ComIMGApplication.shutdownMG(); //<--- Invalid Memory Access exception

            ComIMGApplication.Release();
            
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            if(comWasInitialized == false) {
                if(COMUtils.comIsInitialized()) {
                    Ole32.INSTANCE.CoUninitialize();
                }
            }
        }
    }
}

我已经使用 OLE/COM 对象查看器检查了 COM API。 “IMGApplication”接口扩展IDispatchIUnknown

所以它有来自IUnknown的3个方法QueryInterfaceAddRefRelease和来自IDispatch的4个方法GetTypeInfoCountGetTypeInfoGetIDsOfNamesInvoke

OLE/COM Object Viewer

“IMGApplication”接口中第一个方法(BringToFront())的vtblId必须在索引7处,最后一个在索引21处。

不幸的是,唯一有效的方法是startMG() (vtblId 20)。在所有其他方法上,我得到“无效的内存访问异常”。

【问题讨论】:

  • documentation for StartMG 状态为Mode 参数:目前您只能使用定义为mgAPI::esmStandard = 0 的EMGStartMode "esmStandard"。你正在通过 1。
  • 可能不相关,但可能是一个问题,您正在有条件地初始化 COM,但总是取消初始化它,即使它已经在其他地方初始化。仅当您成功初始化后才取消初始化。同样不相关,您应该扩展Dispatch 而不是Unknown。这不会影响您看到的错误。
  • StartMG 调用的返回值是多少?是INVALID_MODE (-1610350590) 吗?如果是这样,您可能还没有真正开始它。
  • MGStarted() 的文档说您应该等待它成功启动,然后再传递其他 API 请求。您可能处于竞争状态,试图在它完成初始化之前最大化它。
  • ComIMGApplication.startMG(1) 是一个错字。在我的程序中,我将 0 作为参数传递(我已在上面的代码中更正了它)。 startMG(0) 工作正常。当我调用它时,程序正在启动。当然,我不会一个接一个地调用所有函数。在我的示例代码中,我只想展示哪些方法有效,哪些无效。问题是,在程序完全启动并初始化后,maximize()minimize() 等其他方法都不起作用。我得到“无效的内存访问异常”。错误。谢谢!!

标签: java com jna


【解决方案1】:

我想我刚刚找到了问题的原因(感谢 Daniel 的初始化提示)。一旦 Lotus Notes 代理完成运行并终止,所有实例当然都会丢失。每次我再次启动代理时,它都会创建一个“mgAPI.mg_API”的新实例。

        CLSID.ByReference clsIdRef = new CLSID.ByReference();
        HRESULT hRes = Ole32.INSTANCE.CLSIDFromProgID("mgAPI.mg_API", clsIdRef);
        COMUtils.checkRC(hRes);

        HRESULT hres = Ole32.INSTANCE.CoCreateInstance(clsIdByRef, null, WTypes.CLSCTX_INPROC_SERVER, IID_IMGApplication, pointerByRefDispatch);
        if (COMUtils.SUCCEEDED(hres)) {
            return new IMGApplication(pointerByRefDispatch.getValue());
        } else {
            return null;
        }

当然,除了startMG(0),我不能调用任何方法,因为在这种情况下程序还没有运行。这就是为什么我得到“无效的内存访问异常”......

我试过了,它确实有效。

        if(!COMUtils.comIsInitialized()) {
            Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED);
        } else {
            comWasInitialized = true;
        }

        CLSID.ByReference clsIdRef = new CLSID.ByReference();
        HRESULT hRes = Ole32.INSTANCE.CLSIDFromProgID("mgAPI.mg_API", clsIdRef);
        COMUtils.checkRC(hRes);
        
        PointerByReference pointerByRefUnknown = new PointerByReference();
        HRESULT hres = OleAuto.INSTANCE.GetActiveObject(clsIdRef, null, pointerByRefUnknown);
        
        IMGApplication ComIMGApplication = IMGApplication.create(clsIdRef);
        ComIMGApplication.startMG(0);
        
        TimeUnit.SECONDS.sleep(30);
        
        ComIMGApplication.minimize();
        ComIMGApplication.shutdownMG();

现在我必须弄清楚如果程序已经在运行,如何获取它的实例。

我用OleAuto.INSTANCE.GetActiveObject 试过了,但是没有用。

        if(!COM.COMUtils.comIsInitialized()) {
            Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED);
        }

        CLSID.ByReference clsIdRef = new CLSID.ByReference();
        HRESULT hRes = Ole32.INSTANCE.CLSIDFromProgID("mgAPI.mg_API", clsIdRef);
        COMUtils.checkRC(hRes);
            
        PointerByReference pointerByRefUnknown = new PointerByReference();
        HRESULT hres = OleAuto.INSTANCE.GetActiveObject(clsIdRef, null, pointerByRefUnknown);

【讨论】:

  • 您的 COM 初始化比以前更好,但是检查它是否在其他地方初始化会让您受到其他应用程序/线程初始化它的支配,并且可能在您仍在使用它时取消初始化它!再次初始化它以防止这种情况并没有什么坏处,但是如果您尝试不同的模式,它可能会失败。 See how I've handled all these issues here.
  • 这是一个非常有趣的解决方案。我想我也会在我的程序中使用它。谢谢!
猜你喜欢
  • 2020-11-09
  • 1970-01-01
  • 1970-01-01
  • 2017-05-27
  • 1970-01-01
  • 1970-01-01
  • 2021-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多