Winapi - Eclipse SWT GUI 窗口的本机 WinProc 的 JNA 实现存在问题

Posted

技术标签:

【中文标题】Winapi - Eclipse SWT GUI 窗口的本机 WinProc 的 JNA 实现存在问题【英文标题】:Winapi - JNA implementation of native WinProc for Eclipse SWT GUI Window is having issues 【发布时间】:2020-02-05 03:39:48 【问题描述】:

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

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

在网上做了一些研究后,这是我的一些代码 sn-ps 负责预期的功能:

    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++ 原生代码现在看起来像这样:

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

#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 应用程序。保存我的代码并运行应用程序后,我遇到以下问题:

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

我尝试了以下方法:

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

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

非常感谢任何花时间解决我的问题的人!

干杯

【问题讨论】:

RE:因为我找不到与SetWindowSubclass() 相关的任何内容......我不得不使用SetWindowLongPtr()。 JNA 由用户贡献支持。如果当前映射中不存在某个方法,您可以/应该添加它并将其贡献给存储库以帮助未来的用户。为什么不自己编写一个Comctl32 类,加载 DLL 并映射您需要的函数? 【参考方案1】:

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

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

当你尝试调用一个被 GC 处理的回调时 OS/JVM 会做什么没有定义。 JNA 文档在这里很清楚:

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

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

【讨论】:

以上是关于Winapi - Eclipse SWT GUI 窗口的本机 WinProc 的 JNA 实现存在问题的主要内容,如果未能解决你的问题,请参考以下文章

Eclipse中如何配置SWT

swt是啥文件

Eclipse SWT 中用于循环组合的 Java 侦听器

swt是啥

Eclipse SWT开发教程以及一个连连看游戏的代码实现下载

SWT是啥?与swing/awt相比有啥优缺点