当有多个 AWT-EventQueue 线程时如何选择

How to choose an AWT-EventQueue thread, when there are several of them

我使用 DLL 注入和一些 jni 技巧成功地将我自己的 Java 代码注入到 运行ning Oracle Forms 应用程序中。 (Windows 7、32 位,Oracle Forms 11,JRE Java 8)

我能够遍历组件树并在一些基本 Java 对象中查询和设置值,例如来自 class oracle.forms.ui.VTextField

的对象

我在尝试模拟用户点击 oracle.apps.fnd.ui.Button

时卡住了

我尝试了两件事:

  1. 调用AbstractButtonsimulatePush方法class
  2. 调用PushButtonclass
  3. activate方法

(2 classes 在 Button 的 class 层次结构中)

结果相同: 1. 起初,它工作正常:当按钮是 "Search" 按钮时,搜索完成并显示结果。 2. 然后,它立即中断应用程序,并弹出一个窗口说 FRM-92100 Your connection to the Server was interrupted.

从那里,应用程序被挂起。

更新: 似乎导致与服务器断开连接的错误是:

java.lang.SecurityException: this KeyboardFocusManager is not installed in the current thread's context at java.awt.KeyboardFocusManager.checkCurrentKFMSecurity(Unknown Source) at java.awt.KeyboardFocusManager.getGlobalFocusOwner(Unknown Source) at java.awt.KeyboardFocusManager.processSynchronousLightweightTransfer(Unknown Source) at sun.awt.windows.WComponentPeer.processSynchronousLightweightTransfer(Native Method) at sun.awt.windows.WComponentPeer.requestFocus(Unknown Source) at java.awt.Component.requestFocusHelper(Unknown Source) at java.awt.Component.requestFocusHelper(Unknown Source) at java.awt.Component.requestFocus(Unknown Source) at oracle.forms.handler.UICommon.updateFocus(Unknown Source) at oracle.forms.handler.UICommon.setFVP(Unknown Source) at oracle.forms.handler.UICommon.setFVP(Unknown Source) at oracle.forms.handler.UICommon.onUpdate(Unknown Source) at oracle.forms.handler.ComponentItem.onUpdate(Unknown Source) at oracle.forms.handler.JavaContainer.onUpdate(Unknown Source) at oracle.forms.handler.UICommon.onUpdate(Unknown Source) at oracle.forms.engine.Runform.onUpdateHandler(Unknown Source) at oracle.forms.engine.Runform.processMessage(Unknown Source) at oracle.forms.engine.Runform.processSet(Unknown Source) at oracle.forms.engine.Runform.onMessageReal(Unknown Source) at oracle.forms.engine.Runform.onMessage(Unknown Source) at oracle.forms.engine.Runform.processEventEnd(Unknown Source) at oracle.ewt.lwAWT.LWComponent.redispatchEvent(Unknown Source) at oracle.ewt.lwAWT.LWComponent.processEvent(Unknown Source) at oracle.ewt.button.PushButton.activate(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at CustomAWT.run(CustomAWT.java:34) at java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.awt.EventQueue.dispatchEventImpl(Unknown Source) at java.awt.EventQueue.access0(Unknown Source) at java.awt.EventQueue.run(Unknown Source) at java.awt.EventQueue.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.security.AccessControlContext.doIntersectionPrivilege(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source)

我的代码在这里:CustomAWT.run(CustomAWT.java:34) 并用 invokeLater 调用。问题可能是:调用 oracle.ewt.button.PushButton.activate 方法时,我不在正确的 EDT 中。

在 Java 控制台中使用 "List Threads",我得到:

Dump thread list ...
Group main,ac=30,agc=2,pri=10
    main,5,alive
    traceMsgQueueThread,5,alive,daemon
    Timer-0,5,alive
    Java Plug-In Pipe Worker Thread (Client-Side),5,alive,daemon
    AWT-Shutdown,5,alive
    AWT-Windows,6,alive,daemon
    AWT-EventQueue-0,6,alive
    SysExecutionTheadCreator,5,alive,daemon
    CacheMemoryCleanUpThread,5,alive,daemon
    CacheCleanUpThread,5,alive,daemon
    Browser Side Object Cleanup Thread,5,alive
    JVM[id=0]-Heartbeat,5,alive,daemon
    Windows Tray Icon Thread,5,alive
    Thread-13,5,alive
Group Plugin Thread Group,ac=3,agc=0,pri=10
    AWT-EventQueue-1,6,alive
    TimerQueue,5,alive,daemon
    ConsoleWriterThread,6,alive,daemon
Group http://xxxx.xxxx.xxxxx.xx:8001/OA_JAVA/-threadGroup,ac=13,agc=0,pri=4
    Applet 1 LiveConnect Worker Thread,4,alive
    AWT-EventQueue-2,4,alive
    thread applet-oracle/apps/fnd/formsClient/FormsLauncher.class-1,4,alive
    Applet 2 LiveConnect Worker Thread,4,alive
    thread applet-oracle.forms.engine.Main-2,4,alive
    Forms-StreamMessageReader,4,alive
    Forms-StreamMessageWriter,4,alive
    HeartBeat,4,alive
    Busy indicator,1,alive,daemon
    TaskScheduler timer,4,alive
    CursorIdler,4,alive
    Thread-14,4,alive
    Flush Queue,4,alive
Done.

所以,有 三个 AWT-EventQueue 个线程...现在的问题是:如何 query/retrieve 正确的线程,以及如何使 Runnable在"Good Thread"传给invokeLater传给运行(我猜好的是最后一个(AWT-EventQueue-2)

您应该能够扩展 VButton class 您的 class 定义应该是这样的:

public class AmazingButton extends VButton implements FocusListener

那么你需要一个 init class 比如:

public void init(IHandler handler)
  {
    m_handler = handler;
    super.init(handler);
    addMouseListener(new ButtonMouseAdapter());
    addFocusListener(this);
  }  

然后你需要实现监听器并在其中做一些事情:

public void focusGained(FocusEvent e)
     {
         if (e.getComponent() == this)
         {
             // put the focus on the component
             e.getComponent().requestFocus();
             bFocus = true ;
         }
     }

  public void focusLost(FocusEvent e)
     {     
       bFocus = false ;
     }

  /**
   * Private class to handle user mouse actions
   */
  class ButtonMouseAdapter extends MouseAdapter
  {
    /**
     * User moved the mouse over the button
     */
    public void mouseEntered(MouseEvent me)
    {
      bFocus=true ;
      mouseON();
    }

    /**
     * User moved the mouse out of the button
     */
    public void mouseExited(MouseEvent me)
    {
      bFocus=false ;
      mouseOFF();
    }
    /**
     * User moved the mouse out of the button
     */
    public void mousePressed(MouseEvent me)
    {
      bPressed = true ;
    }
    /**
     * User moved the mouse out of the button
     */
    public void mouseReleased(MouseEvent me)
    {
      bPressed = false ;
    }

  }

希望这段代码对你有用。

此致

I successfully injected my own Java code in a running Oracle Forms application, using DLL Injection and some jni trickery.

才是真正的问题,IMO。

您患有目标固定,这意味着您,程序员,对他们想要什么样的解决方案有着固定的想法,这使您对其他一切视而不见。目标固定导致了飞机失事,因为即使是经验丰富且聪明的飞行员(实际上是整个驾驶舱!)也会以一种心态专注于一个问题,以至于他们让其他灾难从身边溜走。

摆脱这种心态。

您想要的解决方案没有奏效,所以请继续尝试其他方法。就像@nightfox79 已经向您提供的明智选项及其变体。

您正试图绕过一个复杂的对象 class,而您可能应该只是扩展现有的 class,而您正试图绕过它。这就是 OOP 开发的全部基础。

DLL/JNI IMO,诡计在明智的解决方案中没有立足之地。

我很同情那些必须维护和修复任何基于 DLL/JNI 黑客攻击的代码解决方案的人。疯狂的谎言就是这样。

你认为 invokeLater() 不是 运行ning 在正确的 EDT 下的理论可能是错误的。根据文档,invokeLater()总是 将您请求的代码排队到 AWT 事件处理程序的待处理代码列表中,这正是它应该在的位置。试图绕过它几乎肯定会导致可怕的问题。 invokeLater() 的全部目的是推迟您从中调用它的 EDT 中的重量级处理,运行 它稍后在完全相同的线程上。这是 invokeLater() 中的错误,如果不是,IMO。

但是,如果您想检查 运行ning 中的线程代码,那么我所知道的唯一测试就是在您的代码中使用它;

if (SwingUtilities.isEventDispatchThread())
{
    System.err.println("Is running on EDT");
}
else
{
    System.err.println("Is not running on EDT");
}

经过大量实验和 google 使用 EventQueueThreadGroup 等关键字进行搜索后,我终于找到了解决方案(在 Works For Me 类别,请注意)。

我用的是sun.awt.AppContextclass。一些文档和来源 here (grepcode.com)

  1. 使用 getAppContexts 方法获取 运行 AppContext 的集合。
  2. 对于每个检索到的 AppContext,使用 getThreadGroup 方法获取他的 ThreadGroup
  3. 对于ThreadGroup对象,使用getName方法。
  4. 当线程组的名称以您的表单应用程序的 http: 地址开头时,检索 Object 属性 键名 sun.awt.AppContext.EVENT_QUEUE_KEY,使用 get AppContext 的方法。
  5. 检索到的对象是 EventQueue。创建一个 java.awt.event.InvocationEvent 对象,将你的 Runnable 传递给 CTOR,并使用 EventQueue.
  6. postEvent 方法
  7. 您的 run 方法将在正确的线程中执行。

备注:

  • 这个答案是一个特定的、对我有用的解决方案,适用于通过 Internet Explorer link 和 运行 在 java.exe 进程中启动的 Oracle Forms 应用程序。在那种情况下,问题中显示了 3 个线程组:mainPlugin Thread Grouphttp://xxxx.xxxx.xxxxx.xx:8001/OA_JAVA/-threadGroup 您的里程可能会有所不同。
  • 如果你不使用全反射,而是导入 sun.awt.AppContext,编译器可能会以 warning: sun.awt.AppContext is Sun proprietary API and may be removed in a future release 的形式发出警告,这不是很酷,但我会接受,因为暂时.
  • run方法中,我用oracle.ewt.lwAWT.AbstractButtonsimulatePush方法测试OK。
  • 这里模拟的方法是invokeLater。对于 invokeAndWaitpostEvent 调用需要更多代码。查看 EventQueue class 的一些来源作为起点。

要获得正确的 EDT 线程,而不管您的线程组如何,您可以使用 SunToolkit.targetToAppContext(Object target),并且对于参数,您可以将您打算操作的 AWT 组件提供给它。示例 source.

然后使用EventQueue eq = SunToolkit.getSystemEventQueueImplPP(appContext);

获取EventQueue

最后,使用您的可运行对象创建一个新的 InvocationEvent 并在 EQ 上调用 postEvent。