通过JNA发送用户定义的Windows消息

huangapple go评论70阅读模式
英文:

Send a user-defined Windows-message through JNA

问题

以下是已经翻译好的内容:

我想要一个简单但困难的事情:从JNA方发送一个特定用户定义的Windows消息,以便这个消息可以被C++方的消息循环(GetMessage()/DispatchMessage())捕获,然后中断该消息循环。实际上,应该通过Swing GUI中的按钮单击来执行此操作。以下是我的问题和考虑:

  1. 例如,假设我在C++方面将我的自定义消息定义为 #define WM_CUSTOM_MSG (WM_USER+42),当然在C++方面添加一个适当的if语句以在我的消息循环中进行中断。但我的目的是从Java发送此消息。

  2. 为了做到这一点,我编写了以下内容:

public class User32Ext {
    // ... (这里省略了代码,只保留重要部分)
    public void sendInterruptMessage(final String windowName) {
        try {
            final User32.HWND hwnd = u32.FindWindowEx(null, null, null, windowName);
            final int msg = 0x400 + 42;
            final User32.WPARAM wparam = new User32.WPARAM();
            final User32.LPARAM lparam = new User32.LPARAM();
            final LRESULT res = u32.SendMessage(hwnd, msg, wparam, lparam);
        } catch (final RuntimeException e) {
            e.printStackTrace();
        }
    }
}

然后只需创建这个虚拟类的对象,并在按钮事件中使用我的 JFrame 的标题作为参数调用 sendInterruptMessage

不幸的是,在两个方面都没有检测到任何效果。我非常确定我犯了一些致命错误,因为我在Windows和JNA编程方面经验不足。所以,你能告诉我,至少我在概念上是对的吗?实现类似这样的期望结果?或者在Java方面只是一些编程错误。谢谢!

附注:如果可以在不发送自定义消息的情况下实现,而是使用一些标准的Windows消息,那当然是可以的,我只想确保这个消息唯一的作用是中断我的C++消息循环。

英文:

I want a simple but hard thing: to send a specific user-defined Windows-message from the JNA side in order this message to be catched by a message-loop (GetMessage()/DispatchMessage()) on the C++ side and that message loop will be then interrupted. Actually it should be performed by the button click in Swing GUI. My questions and considerations:

  1. Suppose for example I defined on the C++ side my own message as #define WM_CUSTOM_MSG (WM_USER+42) and of course add an appropriate if-statement for interruption inside my message-loop on C++ side. But my purpose is to send this message from java.

  2. To do this I wrote the following:

    public class User32Ext {
    interface User32Interface extends User32 {
    User32Interface INSTANCE = Native.load("user32",

                 User32Interface.class, W32APIOptions.DEFAULT_OPTIONS);
    
         @Override
         HWND FindWindowEx(HWND lpParent, HWND lpChild, String lpClassName, String lpWindowName);
    
         HWND GetTopWindow(HWND hwnd);
    
         HWND GetParent(HWND hwnd);
    
         @Override
         HWND GetDesktopWindow();
    
         int SendMessage(HWND hWnd, int Msg, IntByReference wParam, IntByReference lParam);
    
         void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
    
         void SwitchToThisWindow(HWND hWnd, boolean fAltTab);
    
     }
    
     private final User32Interface u32 = User32Interface.INSTANCE;
    
     public User32Ext() {
         super();
         // TODO Auto-generated constructor stub
     }
    
     public void sendInterruptMessage(final String windowName) {
         try {
             final User32.HWND hwnd = u32.FindWindowEx(null, null, null, windowName);
             final int msg = 0x400 + 42;
             final User32.WPARAM wparam = new User32.WPARAM();
             final User32.LPARAM lparam = new User32.LPARAM();
             final LRESULT res = u32.SendMessage(hwnd, msg, wparam, lparam);
         } catch (final RuntimeException e) {
             e.printStackTrace();
         }
     }
    

    }

and then just create an object of this dummy class and call sendInterruptMessage with my JFrame's title as parameter in the button event.

Sadly, on the both sides absolutely no effect was detected. I'm pretty much sure I did some fatal error, because I'm not expirienced enough with Windows and JNA programming. So, could you tell me, at least am I right conceptually, implementing the desired result like this? Or there are just some programming errors on the Java side. Thanks!

P.S. if it could be implemented without sending a custom message, but with some standard Windows message, that's perfectly fine, I just want to be sure, the only thing this message does is to interrupt my C++ message loop.

答案1

得分: 2

使用JNA映射WINAPI函数时,必须确保准确匹配Windows API定义与适当的Java/JNA类型。你的一些映射是错误的。SendMessage的最后两个参数是WPARAMLPARAM,而你在sendInterruptMessage()映射中似乎在使用它们。这些调用了JNA项目中已经映射的SendMessage函数1。你自己的SendMessage映射中放入什么并不重要,因为你甚至没有调用它,因为你使用了不同的类型。删除它。还要从超类中删除你在上面加了@Override的两个方法。你没有理由覆盖它们已经在做的事情。

实际上,进一步检查后,似乎你复制了别人在2014年编写的代码来自这个回答。然而,你需要的映射在2017年被添加到了JNA项目中3。不了解代码功能就盲目复制并不是成功的方法。你的整个接口是不必要的。只需调用JNA的User32接口。

在你对FindWindowEx的调用中,你将前三个参数传递为null,只在第四个参数中放了一个字符串。这似乎不符合API,该API似乎不允许第三个参数为null。你是否检查了返回的句柄是否为null?很可能是,根据API,你可以使用GetLastError来查看错误代码,这可能与不正确的参数有关。

关于第三个参数的文档指明,

> 如果lpszClass是一个字符串,则指定窗口类名。类名可以是任何已在RegisterClassRegisterClassEx中注册的名称,或者是任何预定义的控制类名,或者可以是MAKEINTATOM(0x8000)。在后一种情况下,0x8000是菜单类的原子。有关更多信息,请参见本主题的“备注”部分。

看起来你并没有按要求这样做。

如果第一个调用成功了(这会让我感到惊讶),那么评估现有SendMessage调用返回的LRESULT并查看它是否表示成功或生成了错误代码,这将很有帮助。实际上,你可能传递了一个null句柄,这样什么都不会发生也就不足为奇了。

从检查表示成功/失败的方法返回值开始,并评估错误代码,这应该有助于你进行调试。

最后,正如评论中由iinspectable指出的那样,你传递的代码不是自定义消息。根据文档

> 消息标识符值用法如下:
>
> - 系统在0x0000到0x03FF(WM_USER - 1的值)范围内保留系统定义的消息标识符值。应用程序不能将这些值用于私有消息。
>
> - 范围在0x0400(WM_USER的值)到0x7FFF的值可用于私有窗口类的消息标识符。
>
> - 如果你的应用程序标记为版本4.0,你可以使用范围在0x8000(WM_APP)到0xBFFF的消息标识符值来进行私有消息。
> - 当应用程序调用RegisterWindowMessage函数注册消息时,系统会返回范围在0xC000到0xFFFF的消息标识符。由此函数返回的消息标识符在整个系统中是唯一的。使用这个函数可以防止如果其他应用程序将相同的消息标识符用于不同目的时可能出现的冲突。

阅读WINAPI文档以了解每个参数中预期的内容将有助于你找到要传递的正确值,并且对于从代码获得预期结果至关重要。

英文:

When mapping WINAPI functions with JNA, you must take care to exactly match the Windows API definitions with appropriate Java/JNA types. Some of your mappings are wrong. The last two arguments to SendMessage are a WPARAM and LPARAM, which you appear to be using in your sendInterruptMessage() mapping. These are calling the already-mapped SendMessage function in the JNA project. It doesn't matter what you're putting in your own SendMessage mapping since you're not even calling it, as you're using different types. Delete that. Also delete the two methods you have put @Override on from the superclass. You have no reason to overwrite what they are already doing.

Actually, on further inspection, it appears as if you've copied the code someone else wrote in 2014 from this answer. However, the mappings you need were added to the JNA project in 2017. Copying code without knowing what it does is not a recipe for success. Your entire interface is not needed. Just call JNA's User32 interface.

In your call to FindWindowEx you're passing null for the first three arguments and only placing a String in the fourth argument. This seems to mismatch the API which does not appear to allow null as the third argument. Have you checked whether the returned handle is null? It probably is, and per the API you can use GetLastError to look at that error code, which is likely associated with incorrect arguments.

The docs for that third argument specify,

> If lpszClass is a string, it specifies the window class name. The
> class name can be any name registered with RegisterClass or
> RegisterClassEx, or any of the predefined control-class names, or it
> can be MAKEINTATOM(0x8000). In this latter case, 0x8000 is the atom
> for a menu class. For more information, see the Remarks section of
> this topic.

It does not appear as if you are doing this as required.

If that first call is succeeding (which would surprise me), it would be instructive for you to evaluate the LRESULT return from the existing SendMessage call and see if it indicates success or generates an error code. In fact, you are probably passing it a null handle, in which case it's no surprise that nothing is happening.

Starting out by checking method return values that indicate success/failure and evaluating the error codes should help you debug.

Finally, as ponited out by iinspectable in the comments, the code you're passing isn't a custom message. According to the documentation,

> Message-identifier values are used as follows:
>
> - The system reserves message-identifier values in the range 0x0000 through 0x03FF (the value of WM_USER – 1) for system-defined
> messages. Applications cannot use these values for private messages.
>
> - Values in the range 0x0400 (the value of WM_USER) through 0x7FFF are available for message identifiers for private window classes.
>
> - If your application is marked version 4.0, you can use message-identifier values in the range 0x8000 (WM_APP) through 0xBFFF
> for private messages.
> - The system returns a message identifier in the range 0xC000 through 0xFFFF when an application calls the RegisterWindowMessage function
> to register a message. The message identifier returned by this
> function is guaranteed to be unique throughout the system. Use of
> this function prevents conflicts that can arise if other applications
> use the same message identifier for different purposes.

Reading the WINAPI documentation to know what's expected in each parameter will help you find the right values to pass and is critical to getting expected results from your code.

huangapple
  • 本文由 发表于 2020年5月29日 23:01:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/62088946.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定