java - 采用另一个进程的子窗口

标签 java windows winapi applet jvm

我正在编写一种网络小程序模拟器。我看了一个网页,找到小程序参数,下载小程序运行。 小程序在其自己的进程中运行(即不是模拟器进程)非常重要。但是,它应该在模拟器进程窗口中呈现。

Java 插件是如何做到的?separate_jvm设置标志后,插件会在单独的 JVM 进程中加载​​小程序,但小程序仍会出现在同一浏览器面板中。

我通过创建一个加载器类取得了一些进展,该加载器类在另一个 JVM 上将目标 Applet 添加到一个未修饰的、不可见的框架中,并将该框架的窗口句柄发送给模拟器​​ JVM。后者将其绑定(bind)到 Canvas带有 user32.SetParent 的实例通过 JNA,显示效果很好。

但是,只发送鼠标事件:不转发键盘输入。小程序报告Component#isFocusOwner为假,requestFocusInWindow不会使其成为焦点所有者,返回 false。 如何将键盘焦点传递给 Applet 窗口句柄?我目前的方法涉及服务器(模拟器),它从客户端(applet)接收窗口句柄。只有鼠标事件似乎有效,因为 Applet 无法获得焦点。

服务器类处理小程序的显示。

import com.sun.jna.*;
import com.sun.jna.platform.win32.User32;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import static com.sun.jna.platform.win32.User32.*;

public class Loader {
    private static final String APPLET_DIRECTORY = ""; // TODO: Set this to the directory containing the compiled applet

    private static ServerSocket serverSocket;
    private static JFrame frame;
    private static Canvas nativeDisplayCanvas;

    public static void main(String[] argv) throws Exception {
        nativeDisplayCanvas = new Canvas();
        frame = new JFrame("Frame redirect");
        frame.setLayout(new BorderLayout());
        frame.add(nativeDisplayCanvas, BorderLayout.CENTER);
        frame.setSize(300, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        (new Thread() {
            public void run() {
                try {
                    serve();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        spawnAltJVM(APPLET_DIRECTORY, "AppletDemo");
    }

    public static void serve() throws Exception {
        serverSocket = new ServerSocket(6067);
        serverSocket.setSoTimeout(10000);

        while (true) {
            try {
                System.out.println("Waiting for applet on port " + serverSocket.getLocalPort() + "...");
                Socket server = serverSocket.accept();
                System.out.println("Connected to " + server.getRemoteSocketAddress());
                BufferedReader in = new BufferedReader(new InputStreamReader(server.getInputStream()));
                DataOutputStream out = new DataOutputStream(server.getOutputStream());
                while (true) {
                    String msg = in.readLine();
                    if (msg != null && msg.startsWith("child_hwnd")) {
                        windowCreatedHandler(msg);
                        out.writeUTF("hwnd_recv\n");
                        out.flush();
                    }
                }
            } catch (IOException ex) {
                System.out.println("Something happened to the socket...");
                break;
            }
        }
    }

    public static void windowCreatedHandler(String message) {
        String[] tokens = message.split(":");
        final User32 user32 = User32.INSTANCE;
        HWND child_applet = new HWND(Pointer.createConstant(Long.parseLong(tokens[1])));
        final HWND child_frame = new HWND(Pointer.createConstant(Long.parseLong(tokens[2])));

        frame.addComponentListener(
                new ComponentAdapter() {
                    @Override
                    public void componentResized(ComponentEvent e) {
                        user32.SetWindowPos(child_frame, new HWND(Pointer.NULL), 0, 0, frame.getWidth(), frame.getHeight(), 0);
                    }
                }
        );
        HWND parent = new HWND(Native.getComponentPointer(nativeDisplayCanvas));

        user32.SetParent(child_applet, parent);

        int style = user32.GetWindowLong(child_frame, GWL_STYLE) & ~WS_POPUP | (WS_CHILD | WS_VISIBLE);
        user32.SetWindowLong(child_applet, GWL_STYLE, style);
        user32.SetWindowPos(child_applet, new HWND(Pointer.NULL), 0, 0, nativeDisplayCanvas.getWidth(), nativeDisplayCanvas.getHeight(), 0);
    }

    public static void spawnAltJVM(String cp, String clazz) throws IOException, InterruptedException, ClassNotFoundException {
        ProcessBuilder processBuilder = new ProcessBuilder(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java", "-cp", cp, clazz);
        Process applet = processBuilder.start();
        final BufferedReader in = new BufferedReader(new InputStreamReader(applet.getInputStream()));
        final BufferedReader err = new BufferedReader(new InputStreamReader(applet.getErrorStream()));
        (new Thread() {
            public void run() {
                while (true) {
                    try {
                        System.out.println("[client] " + in.readLine());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

与此同时,客户端类只是实例化句柄并向其发送消息。

import sun.awt.windows.WComponentPeer;
import javax.swing.*;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;

public class AppletDemo extends Applet {
    private Canvas canvas;
    private static Color backgroundColor = Color.RED;

    public AppletDemo() {
        setLayout(new BorderLayout());
        canvas = new Canvas();
        add(canvas, BorderLayout.CENTER);
        setBackground(Color.CYAN);
        canvas.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                refreshColors();
            }
        });
        canvas.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                refreshColors();
            }
        });
    }

    private void refreshColors() {
        SwingUtilities.invokeLater(
                new Runnable() {
                    @Override
                    public void run() {
                        backgroundColor = (backgroundColor == Color.RED ? Color.GREEN : Color.RED);
                        canvas.setBackground(backgroundColor);
                    }
                }
        );
    }

    public static void main(String[] argv) throws Exception {
        System.setErr(System.out);

        final AppletDemo app = new AppletDemo();
        Frame frame = new Frame("AppletViewer");
        frame.setLayout(new BorderLayout());
        frame.add(app, BorderLayout.CENTER);
        frame.setUndecorated(true);
        frame.pack(); // Create the native peers
        frame.setSize(300, 200);

        final Socket client = new Socket("localhost", 6067);
        final LinkedBlockingDeque<String> messageQueue = new LinkedBlockingDeque<>();
        final DataOutputStream out = new DataOutputStream(client.getOutputStream());
        final BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
        (new Thread() {
            public void run() {
                while (true) {
                    try {
                        out.writeBytes(messageQueue.take() + "\n");
                        out.flush();
                    } catch (IOException | InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }).start();
        (new Thread() {
            public void run() {
                while (true) {
                    try {
                        if ("hwnd_recv".equals(in.readLine())) {
                            // Attempt to grab focus in the other process' frame
                            System.out.println("Trying to request focus...");
                            System.out.println(app.requestFocusInWindow());
                        }
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }).start();

        messageQueue.add("child_hwnd:" + ((WComponentPeer) app.getPeer()).getHWnd() + ":" + ((WComponentPeer) frame.getPeer()).getHWnd());
    }
}

它们都有点冗长,因为它们需要一些套接字工作,但它们是可编译的,应该可以证明问题。它们需要 JNA 来编译。我以一些良好做法为代价尽可能地缩短了它们。

Loader 运行时,应该会出现一个重定向 AppletDemo Canvas 的窗口。发送鼠标事件: Canvas 在按下鼠标时在红色和绿色之间切换。理想情况下,击键也应该发生相同的行为。

我使用 WinSpy 获取 notepad.exe 窗口和文本 Pane 的句柄,并将句柄硬编码到 Loader 中。键盘焦点与多行编辑控件完美配合,但与顶层窗口本身配合不佳。为什么?这与我遇到的问题有关吗?

我在 WinSpy 中打开一个运行小程序的 Chrome 窗口,发现该插件没有创建虚拟 Frame — 小程序 Canvas 直接设置为 Chrome 的子项。但是,我无法为 Applet 创建本地对等点,因为它似乎要求它是可显示的。

我读过 dangers of cross-process parent/child or owner/owned window relationship , 但我想不出更好的方法将子小程序移植到模拟器中。

最佳答案

由于您真正想要的是将 applet 创建为子窗口,因此简单的解决方案是说服 applet 成为您的子窗口,而不是强行采用它,并同时针对 Windows 和 JVM 工作。

幸运的是,Sun/Oracle Java VM 带有一个名为 WComponentFrame 的类(正如名称所暗示的那样,仅限 Windows)。它可以从 hwnd 构建,您可以从父进程发送它。然后可以将小程序添加为窗口的子项。

import sun.awt.windows.WComponentPeer;

frame = new WEmbeddedFrame(hwnd);
frame.setLayout(new BorderLayout());
frame.add(applet, BorderLayout.CENTER);

关于java - 采用另一个进程的子窗口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27810513/

相关文章:

.net - Metro 应用程序中的 Storyboard动画和附加属性

Windows 资源管理器外壳扩展:通过 ITransferSource::RemoveItem 递归删除?

java正则表达式用破折号替换特殊字符和空格

java - 为什么我无法在 Windows 中创建名为 'con' 的文件夹?

java - Java 客户端/服务器应用程序的负载测试

windows - 在Windows中设置Kafka日志目录属性

java - 测试 Ajax - 在 Apache Wicket 中提交表单

c++ - Qt OpenGL-如何根据鼠标点击获取对象

delphi - 如何在Delphi中引入前端或启动浏览器

windows - C++窗口最大化问题