0

作为我最近的问题的扩展(Winapi - SetWindowLongPtr in ShutdownBlockReasonCreate / Destroy implementation of JNI native code)我想知道是否有机会使用 JNA 实现相同的功能(最新版本 5.5.0 - https://github. com/java-native-access/jna)。

SetWindowSubclass()由于我在文档(http://java-native-access.github.io/jna/5.5.0/javadoc/ )中找不到任何相关的内容,我不得不使用SetWindowLongPtr().

在网上做了一些研究之后,这里有一些我负责预期功能的代码片段:

    private static LONG_PTR baseWndProc;
    private static HWND hWnd;

    public static void initiateWindowsShutdownHook() {
        Display.getDefault().syncExec(()->{
            hWnd = new WinDef.HWND(Pointer.createConstant(Display.getDefault().getActiveShell().handle));
        });

        baseWndProc = IWindowsAPIUtil.WSBINSTANCE.SetWindowLongPtr(
                hWnd, IWindowsAPIUtil.GWL_WNDPROC, new IWindowsAPIUtil.WNDPROC() {

            @Override
            public LRESULT callback(HWND hWnd, int uMsg, WPARAM wParam, LPARAM lParam) {
                switch(uMsg) {
                case IWindowsAPIUtil.WM_QUERYENDSESSION:
                    Logger.logWarning("Shutdown initiated");
                    IWindowsAPIUtil.WSBINSTANCE.PostMessage(hWnd, User32.WM_CLOSE, wParam, lParam);
                    return new LRESULT(0);
                }
                return IWindowsAPIUtil.WSBINSTANCE.CallWindowProc(baseWndProc, hWnd, uMsg, wParam, lParam);
            }
        });
    }


    public interface IWindowsAPIUtil extends User32 {

        public static final IWindowsAPIUtil WSBINSTANCE = 
                (IWindowsAPIUtil) Native.loadLibrary("user32", IWindowsAPIUtil.class, W32APIOptions.UNICODE_OPTIONS);

        interface WNDPROC extends StdCallCallback{
            LRESULT callback(HWND hWnd, int uMsg, WPARAM wParam, LPARAM lParam);
        }

        public static final int GWL_WNDPROC = -4;
        public static final int WM_QUERYENDSESSION = 17;

        LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, WNDPROC wndProc);
        LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex);
        LRESULT CallWindowProc(LONG_PTR proc, HWND hWnd, int uMsg, WPARAM wParam, LPARAM lParam);
        void PostMessage(HWND hWnd, int uMsg, WPARAM wParam, LPARAM lParam);
    }

我的新 c++ 本机代码现在看起来像这样:

注意:SetWindowSubclass()由于在本练习中我只将回调部分(在原始 c++ 本机代码中)重构为 JNA,因此此处不需要

#include <windows.h>

#include <jni.h>

#include <iostream>
#include "com_app_project_winapi_WindowsAPI.h"


#include <commctrl.h>

using namespace std;

namespace {
    // Default reason text. The actual reason text should be defined by application logic not the native code
    LPCWSTR SHUTDOWN_REASON = L"Application is still saving ...";
}


JNIEXPORT void JNICALL Java_com_app_project_winapi_WindowsAPI_shutdownBlockReasonCreate(JNIEnv *env, jclass cls, jstring title, jstring reasonText) {
    cout << "In shutdownBlockReasonCreate method" << endl;

    const jchar *str = env->GetStringChars(title, NULL);
    HWND hWnd = FindWindowW(NULL, (LPCWSTR)str);
    env->ReleaseStringChars(title, str);
    if (hWnd == NULL) {
        return;
    }

    ShutdownBlockReasonCreate(hWnd, SHUTDOWN_REASON);

    return;
}

JNIEXPORT void JNICALL Java_com_app_project_winapi_WindowsAPI_shutdownBlockReasonDestroy(JNIEnv *env, jclass cls, jstring title) {
    cout << "In shutdownBlockReasonDestroy method" << endl;

    const jchar *str = env->GetStringChars(title, NULL);
    HWND hWnd = FindWindowW(NULL, (LPCWSTR)str);
    env->ReleaseStringChars(title, str);
    if (hWnd == NULL) {
        return;
    }

    ShutdownBlockReasonDestroy(hWnd);

    return;
}

从我的代码中,您可能会看到我将 Eclipse SWT 用于我的 GUI 应用程序。保存我的代码并运行应用程序后,我遇到以下问题:

  1. 虽然在第一次应用程序保存期间激活了 blockReasonCreate(此功能的目的是在保存时阻止关闭),但它在后续保存中不再激活。原因文本显示“此应用程序正在阻止关闭”,而不是传入的原因文本。
  2. 作为上面的扩展行为,我的 GUI 应用程序冻结并且窗口无法关闭,直到我从任务管理器强制关闭它

我尝试了以下方法:

  1. 将“baseWndProc”LONG_PTR 替换为GetWindowLongPtr()in CallWindowProc()。不幸的是没有工作
  2. 我怀疑我和SetWindowLongPtr()上次有同样的问题。但如前所述,JNA 似乎没有SetWindowSubclass()提供匹配方法,所以我没有想法。

顺便说一句,上次的本机代码解决方案仍然可以完美运行。但是出于可维护性的目的,将所有功能都用 Java 实现是理想的。

真的很感谢任何人花时间在我的问题上!

干杯

4

1 回答 1

1

您应该始终准备一个最小的样本,它可以重现问题并且可以由潜在的帮助者运行。对从上下文中剥离出来的代码进行推理是很困难的。

在您的情况下,您将回调创建为匿名类(new IWindowsAPIUtil.WNDPROC() /* ... */在您的示例中)。SetWindowLongPtr在调用返回后,此回调立即变得有资格进行垃圾收集。JVM 不会立即运行 GC,但它会。

没有定义当您尝试调用回调时 OS/JVM 将做什么,即 GCed。JNA 文档在这里很清楚:

如果本机代码尝试调用已被 GC 处理的回调,您可能会导致 VM 崩溃。如果没有取消注册回调的方法(例如 C 库中的 atexit),您必须确保始终保持对回调对象的实时引用。

因此,您应该在 java 端保持对回调的强引用(例如使用静态字段,它与窗口具有相同的生命周期)。

于 2020-02-10T20:44:38.293 回答