未收到来自 Java Access Bridge 的回调

问题描述 投票:0回答:3

我正在尝试使用 Java Access Bridge 从 C++ 应用程序内部获取有关 Swing 组件的信息。 但是,我注册的回调都没有被调用。 我尝试枚举窗口,然后在每个句柄上调用 IsJavaWindow(),但它总是返回 false。 关于为什么它显然不起作用的任何想法?

我认为这是我的应用程序的问题,而不是桥安装的问题,因为演示 Monkey 和 Ferret 程序可以工作,initializeAccessBridge() 返回 true,并且调试器显示 WindowsAccessBridge dll 已加载。

我在 Windows Vista 上使用 Java 6,更新 13,我认为访问桥的版本为 2.0.1。

JavaAccess::JavaAccess(void)
{
   using namespace std;

   BOOL isInitialized = initializeAccessBridge();
   if(isInitialized)
   {
      cout << "Bridge Initialized!" << endl;
   }
   else
   {
      cout << "Initialization failed!" << endl;
      return;
   }

   EnumWindows((WNDENUMPROC)EnumWndProc, NULL);

   SetJavaShutdown(OnJavaShutdown);
   SetFocusGained(OnFocusGained);
   SetMouseClicked(OnMouseClicked);
}

JavaAccess::~JavaAccess(void)
{
   shutdownAccessBridge();
}

void JavaAccess::OnJavaShutdown( long vmID )
{
   using namespace std;
   cout << "Java shutdown!" << endl;
}

void JavaAccess::OnFocusGained( long vmID, FocusEvent event, AccessibleContext context )
{
   using namespace std;
   cout << "Focus Gained!" << endl;

   ReleaseJavaObject(vmID, event);
   ReleaseJavaObject(vmID, context);
}

void JavaAccess::OnMouseClicked( long vmID, jobject event, jobject source )
{
   std::cout << "Mouse clicked!" << std::endl;

   ReleaseJavaObject(vmID, event);
   ReleaseJavaObject(vmID, source);
}

BOOL CALLBACK JavaAccess::EnumWndProc( HWND hwnd, LPARAM lparam )
{
   if (IsJavaWindow(hwnd))
   {
      std::cout << "Found Java Window!" << std::endl;
      return FALSE;
   }
   else
   {
      std::cout << "Still looking" << std::endl;
      return TRUE;
   }
}

所有回调都是静态函数。

java c++ accessibility java-access-bridge
3个回答
13
投票

我也一直在与这个问题作斗争,并且刚刚找到了一个真正有意义的解决方案。 我最终不得不构建 WindowsAccessBridge.dll 的调试版本,并使用调试器进入其中以观察发生了什么。

  • 对“initializeAccessBridge”的调用要求您有一个活动的 Windows 消息泵。

在“initializeAccessBridge”内部,它(最终)创建一个隐藏的对话框窗口(使用CreateDialog)。 创建对话框后,它会使用注册的消息执行 PostMessage。 访问桥的 JavaVM 端响应此消息,并将另一条消息回发到创建的对话框(它似乎是应用程序和 Java VM 之间的“hello”类型握手)。 因此,如果您的应用程序没有活动的消息泵,则您的应用程序永远不会收到来自 JavaVM 的返回消息。

这很重要,因为在收到此消息之前,桥永远不会正确初始化,因此对“IsJavaWindow”的所有调用都会失败(在内部,一旦收到消息,桥就会初始化内部结构 - 因此,没有活动的消息泵,没有初始化)。 我猜这就是为什么您也从未收到回调消息的原因。

不仅如此,您还必须在消息泵可以处理消息的位置调用initializeAccessBridge,然后才能调用IsJavaWindow。

这就是 JavaFerret 和 JavaMonkey 工作的原因——它们在启动时初始化,然后在桥接器通过消息泵接收到初始化消息后枚举对菜单消息的响应。

我能够在 MFC 对话框应用程序(以及我们基于 MFC 的应用程序)中解决此问题的方法是确保您在某个时刻调用“initializeAccessBridge”,以便内置 MFC 消息泵可以推送“hello” ' 在使用之前将消息返回到此隐藏对话框。 在简单的 MFC 对话框情况下,这意味着在 OnInitDialog 中调用initializeAccessBridge,并调用枚举过程来响应按钮调用(例如)。 如果您希望枚举在对话框出现后立即发生,您可以使用计时器在 OnInitDialog 完成后触发(例如 10 毫秒),以允许处理初始化消息。

如果您计划在控制台应用程序中使用它,您将需要编写自己的自定义消息泵来处理初始化消息。

无论如何,我希望这已经足够清楚了! 虽然没有办法知道这是否是“正确”的方法(除了花钱请 Sun 工程师告诉我们),但它确实解决了我的问题。

干杯——达伦。

哦。顺便说一句,我发现了一个不起眼的 Sun 页面,其中提到了有关 AccessBridge 仅适用于基于 awt 的 java 应用程序的信息(但考虑到 Sun 自 2004 年以来没有更新任何文档,这可能已经改变)。 我不是 Java 程序员——为了进行测试,我获取了一些免费的 Java 应用程序(以及 jdk 附带的应用程序)并尝试了我的测试应用程序。 它对我尝试过的所有方法都有效——YMMV。 祝你好运!


0
投票

您确定 OnJavaShutdown() 是静态的吗?我认为声明应该是

static oid JavaAccess::OnJavaShutdown( long vmID )

0
投票

这是我将 Java Access Bridge 与 C# 控制台应用程序集成的实现。不是最优雅的代码,但它给出了一个总体思路。

  1. 运行消息传递泵。
  2. 使用计时器查找所有正在运行的进程并找到一个java应用程序
  3. 调用Application.Run();

我的主程序

class Program
{
    static void Main(string[] args)
    {
        ExecuteProcess();
    }
    static void ExecuteProcess()
    {
        string dllDirectory = Directory.GetCurrentDirectory();
        Environment.SetEnvironmentVariable("PATH", Path.Combine(dllDirectory, "libs-ext"), EnvironmentVariableTarget.Process);
        Environment.SetEnvironmentVariable("JAVA_ACCESSBRIDGE_LOGDIR", @"c:\accessbridgelog", EnvironmentVariableTarget.Process);
        new AccessBridge();
        Application.Run();
    }
}

我的 AccessBridge 课程:

class AccessBridge : IDisposable
{
    private readonly JavaAccessBridgAPI bridgeWrapper;
    private Thread messageLoopThread;
    private object threadState = new object();
    public AccessBridge()
    {
        bridgeWrapper = new JavaAccessBridgAPI();
        RunMessagePump();
        bridgeWrapper.InitializeBridge();
    }
    /// <summary>
    /// Get running processes to find and check witch one is a Java program
    /// </summary>
    private void EnumerateWindows()
    {
        Process[] processes = Process.GetProcesses();
        foreach (Process process in processes)
        {
            IntPtr hwnd = process.MainWindowHandle;
            if (hwnd != IntPtr.Zero)
            {
                try
                {
                    Console.WriteLine($"Process: {process.ProcessName} (ID: {process.Id}) - Main Window Handle: 0x{hwnd.ToInt32():X8}");

                    if (!bridgeWrapper.CheckIfJavaWindow(hwnd))
                    {
                        Console.WriteLine($"\n\t....Not a Java window.");
                    }
                    else
                    {
                        Console.WriteLine($"\n\t....IS A Java window :)");
                    }

                }
                catch (SEHException ex)
                {
                    Console.Error.WriteLine($"{process.ProcessName} - Failed checking. {ex}");
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine($"{process.ProcessName} - Failed checking. {ex}");
                }
            }
        }
    }
    private Timer timer;
    private void RunMessagePump()
    {
        // Start message loop
        // Start message loop in a separate thread
        messageLoopThread = new Thread(RunMessageLoop);
        messageLoopThread.IsBackground = true;  // Ensure it doesn't block program exit
        messageLoopThread.Start();

        timer = new Timer((state) => { 
            EnumerateWindows();
        }, threadState, 5000, Timeout.Infinite );
        
    }
    private void RunMessageLoop()
    {
        MSG msg;
        while (GetMessage(out msg, IntPtr.Zero, 0, 0))
        {
            TranslateMessage(ref msg);
            DispatchMessage(ref msg);
        }
        // Optional: shutdownAccessBridge(); if needed
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MSG
    {
        public IntPtr hwnd;
        public uint message;
        public IntPtr wParam;
        public IntPtr lParam;
        public uint time;
        public Point pt;
        public int lPrivate;
    }

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);

    [DllImport("user32.dll")]
    public static extern bool TranslateMessage([In] ref MSG lpMsg);

    [DllImport("user32.dll")]
    public static extern IntPtr DispatchMessage([In] ref MSG lpmsg);

    public void Dispose()
    {
        timer?.Dispose();
    }
}

我的 JavaAccessBridgAPI 实现:

public class JavaAccessBridgAPI
{
    private const string DllName = "WindowsAccessBridge-64.dll";

    [DllImport(DllName, CallingConvention = CallingConvention.Winapi, EntryPoint = "Windows_run")]
    private static extern void Windows_run();

    [DllImport(DllName, CallingConvention = CallingConvention.Winapi, EntryPoint = "isJavaWindow")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool IsJavaWindow(IntPtr hwnd);

    [DllImport(DllName, CallingConvention = CallingConvention.Winapi, EntryPoint = "getAccessibleContextFromHWND")]
    private static extern bool Windows_GetAccessibleContextFromHWND(IntPtr hwnd, out IntPtr ac);

    [DllImport(DllName, CallingConvention = CallingConvention.Winapi, EntryPoint = "GetAccessibleName")]
    private static extern bool Windows_GetAccessibleName(IntPtr ac, IntPtr nameBuffer, int bufferSize);

    public bool InitializeBridge()
    {
        Windows_run();
        return true;
    }

    public bool CheckIfJavaWindow(IntPtr hwnd) => IsJavaWindow(hwnd);

    public bool RetrieveAccessibleContext(IntPtr hwnd, out IntPtr ac) => Windows_GetAccessibleContextFromHWND(hwnd, out ac);

    public bool RetrieveAccessibleName(IntPtr ac, IntPtr nameBuffer, int bufferSize) => Windows_GetAccessibleName(ac, nameBuffer, bufferSize);
}

windowsaccessbridge-64.dll 必须与主程序位于同一文件夹中或位于“libs-ext”中

Environment.SetEnvironmentVariable("PATH", Path.Combine(dllDirectory, "libs-ext"), EnvironmentVariableTarget.Process);

© www.soinside.com 2019 - 2024. All rights reserved.