Not receiving callbacks from the Java Access Bridge
Asked Answered
L

3

7

I'm trying to use the Java Access Bridge to get information about Swing components from inside a C++ app. However, none of the callbacks I register ever get called. I tried enuming the windows an then calling IsJavaWindow() on each handle, but it always returns false. Any ideas on why it apparently is not working?

I presume it's a problem with my app rather than the bridge installation because the demo Monkey and Ferret programs work, initializeAccessBridge() returns true, and the debugger reveals that the WindowsAccessBridge dll is loaded.

I'm using Java 6, update 13 on Windows Vista and I think version 2.0.1 of the access bridge.

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;
   }
}

All of the callbacks are static functions.

Lamellicorn answered 21/7, 2009 at 19:11 Comment(0)
G
13

I have been fighting this one as well, and have just found a solution that actually makes sense. I ended up having to build a debug version of the WindowsAccessBridge.dll and used the debugger to step into it to watch what was happening.

  • The call to 'initializeAccessBridge' REQUIRES you to have an active windows message pump.

Inside 'initializeAccessBridge', it (eventually) creates a hidden dialog window (using CreateDialog). Once the dialog is created, it performs a PostMessage with a registered message. The JavaVM side of the access bridge responds to this message, and posts back another message to the dialog that was created (it appears to be a 'hello' type handshake between your app and the java VM). As such, if your application doesn't have an active message pump, the return message from the JavaVM never gets received by your app.

This is important as until this message is received, the bridge is never properly initialized and as such all calls to 'IsJavaWindow' fail (internally, the bridge initializes an internal structure once the message is received -- as such, no active message pump, no initializing). I'm guessing that this is why you never receive callback messages as well.

Not only that, but you must call initializeAccessBridge at a point where the message pump can process messages before you can call IsJavaWindow.

This is why JavaFerret and JavaMonkey work -- they initialize at startup, and then enumerate on response to a menu message, well after the bridge has received the initialization message via the message pump.

The way I was able to solve this in my MFC dialog app (and our MFC-based application), is to make sure that you call 'initializeAccessBridge' at a point such that the built-in MFC message pump can push the 'hello' message back to this hidden dialog BEFORE you use it. In the simple MFC dialog case, it meant calling initializeAccessBridge in OnInitDialog, and calling the enum procedure in response to a button call (for example). If you want the enum to occur as soon as the dialog appears, you could use a timer to fire (eg. 10ms) after the OnInitDialog completes to allow processing of the initialization message.

If you are planning to use this in a console app, you will need to write your own custom message pump to handle the initialization message.

Anyway, I hope this is clear enough! Whilst there is no way to know whether this is the 'correct' way (other than to pay for a Sun engineer to tell us), it definitely solved my problem.

Cheers -- Darren.

oh. and btw I found an obscure Sun page that mentioned something about the AccessBridge only working for awt-based java apps (but given that Sun hasn't updated any documentation since 2004 this may have changed). I'm not a java programmer -- for testing I grabbed a number of free Java applications (as well as the ones that came with the jdk) and tried out my test application. It worked for all of the ones I tried -- YMMV. Good luck!

Golem answered 28/7, 2009 at 12:2 Comment(4)
I was beginning to suspect that it required a message pump, and I like your explanation why. I just modified my test and it works now. Here are some bounty points!Lamellicorn
Thanks for the point on the message loop. Re: console app. In C# You can use Application.Run() with no-args to create a simple main message loop. If you want to make any calls to the dll then do so from another thread. For C++ the Java Access Bridge download contains the JavaMonkey.cpp source code which has the standard GetMessage, TranslateMessage, DispatchMessage loop.Fukien
This is the most obscure answer ever. Would you please show any code?Buffer
In C#, I needed to call the loop in a separate thread along with the Application.Run(). This answer perfectly addresses the issue I've been grappling with for the past 20 days. I plan to share my C# implementation as an answer for the benefit of others.Brede
B
1

This is my implementation that integrates the Java Access Bridge with a c# console application. Not the most elegant code but it gives a general idea.

  1. run the messaging pump.
  2. Use a timer to look for all running process and find a java application
  3. call Application.Run();

My main program

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();
    }
}

My AccessBridge Class:

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();
    }
}

My JavaAccessBridgAPI implementation:

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);
}

the windowsaccessbridge-64.dll must be in the same folder as the main program or in the "libs-ext" Environment.SetEnvironmentVariable("PATH", Path.Combine(dllDirectory, "libs-ext"), EnvironmentVariableTarget.Process);

Brede answered 25/6, 2024 at 19:20 Comment(0)
P
0

Are you sure the OnJavaShutdown() is static? I believe the declaration should be

static oid JavaAccess::OnJavaShutdown( long vmID )
Pampas answered 28/7, 2009 at 12:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.