Skip to main content
Topic: SDK bindings for Java (soon) (Read 7334 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

SDK bindings for Java (soon)

Hi everybody

These past days I've been working on a solution that enable to write plugins in Java, by mapping the SDK
C++ classes to Java classes.
Initially I didn't know if that was possible without being a headache, and the good news is that I have a working prototype!
The prototype is capable of writing tags fully from java and gets playback notifications (play_callback).
classes metadb_handle, file_info_impl and metadb_io are mapped.
The test function that just add a custom tag to selected items looks like this in java

Code: [Select]
public static void test(Vector<metadb_handle> items) {
        for(metadb_handle item : items) {
            file_info_impl finfo = new file_info_impl();
            item.get_info(finfo);
            finfo.meta_set("JAVA", "java");
            _metadb_io.update_info(item, finfo, (int)_mainwin_handle, false);
        }
}


track changed notification:

Code: [Select]
public static void on_playback_new_track(metadb_handle item) {
        file_info_impl finfo = new file_info_impl();
        item.get_info(finfo);
        label.setText(finfo.meta_get("ARTIST", 0) + " - " + finfo.meta_get("TITLE", 0));
}



Pros:

- way way easier and less error prone to write complex UIs than old win32 common control APIs. Plus
you have access to all Swing fancy widgets.
- easier to write plugins than in C++ (for thoses who don't master C++)

Cons:

- use more memory
- potentially slower for huge calculations so don't write a DSP with it

How does it work ?

- you still have a minimal C++ DLL plugin whose main task is to redirect actions (menu item action, notification) to a java Plugin class. Menu items are still created in C++ and I'm not sure it'll be possible to create them in Java
- SWIG is used to generate JNI code and DLL + Java proxy classes  that contains the JNI bindings to the SDK

I'll expand what I have to see how far I can go but this is promising ! Also with SWIG it should be possible to generate bindings for other languages.


SDK bindings for Java (soon)

Reply #2
I was able to do a lot in binding foobar2000 SDK to .NET technology in foo_title. You can take a look at it for inspiration.

SDK bindings for Java (soon)

Reply #3
I was able to do a lot in binding foobar2000 SDK to .NET technology in foo_title. You can take a look at it for inspiration.


Interesting even if I'm not very familiar with .NET. I went a somewhat different route: In my case, 99% of what you call ManagedWrapper (both C++ and Java proxy classes) is generated using SWIG and slightly modified API header files.

In C#, is the UI living in a different thread than the main thread ? That was a major pbm to solve with Java and Swing.

SDK bindings for Java (soon)

Reply #4
I think it's the same thread. I've had no problems with the GUI so far.

SDK bindings for Java (soon)

Reply #5
The foobar2000 core scans the list of service factories in a component DLL at startup; service factories that are added later on will not be recognized. In theory any service factories that are created before foobar2000_client::get_service_list() returns will be recognized by the core. However to make this work, you cannot use the default implementation of foobar2000_component_client. You would have to make a copy of that library, modify it, and link against the modified library - or just add the modified source file (component_client.cpp) to your project. Unless the dynamically created service factories have member variables that need some clean-up, you don't need to bother about deleting them. Since you component is only unloaded when foobar2000 exits, the memory will be automatically freed by the OS.

This leaves the problem of finding the Java classes for which you need a service factory wrapper. I don't know (yet) how you have currently solved this, but I would try to use a manifest.

SDK bindings for Java (soon)

Reply #6
The foobar2000 core scans the list of service factories in a component DLL at startup; service factories that are added later on will not be recognized. In theory any service factories that are created before foobar2000_client::get_service_list() returns will be recognized by the core. However to make this work, you cannot use the default implementation of foobar2000_component_client. You would have to make a copy of that library, modify it, and link against the modified library - or just add the modified source file (component_client.cpp) to your project. Unless the dynamically created service factories have member variables that need some clean-up, you don't need to bother about deleting them. Since you component is only unloaded when foobar2000 exits, the memory will be automatically freed by the OS.

This leaves the problem of finding the Java classes for which you need a service factory wrapper. I don't know (yet) how you have currently solved this, but I would try to use a manifest.



Interesting info. At what point is get_sevice_list() called ? After all plugins static global variables (containing factories among other) are initialized ? what need to be modified in component_client.cpp so it can see services created dynamically in let's say, initquit_factory::on_load() ?

I'll explain a bit more on how the java bindings work:

The foo_java_plugin_loader's on_init() will:

- Create the JVM with the correct classpath etc

- init the FBAPI static Java class with a few static service pointers (the real C pointers, not the smart pointers) : meta_db_io, playback_control, ... The Java FBAPI class then create Java proxy classes from these pointers which allow to access them as first class java citizens. These proxy classes (generated by SWIG) are almost identical to their C++ counterpart. One difference though: they do not subclass service_base as this is not very useful on a Java point of view. So let's say Java playback_control proxy class map a C++ playback_control *, and the Java proxy code calls the  native JNI implementaton (living in fb2k_java_api.dll) which in turn will call the relevant API C++ code.

Now you may ask why JNI code cannot live in  foo_java_plugin_loader.dll. It is because a DLL can only be loaded once. In that case,  foo_java_plugin_loader.dll is loaded by foobar and any attempt to do a second load using Sytem.loadLibrary() (to solve native API funcs references) will fail.

So we've got a fb2k_java_api.dll that calls and links with SDK code but is not a plugin ! How can this work then ? It works because it lives in the main foobar thread. So if you try to direclty get services in this DLL, they do not exist, but pointers passed from the real plugin loader dll are valid. It's also passed a pointer to the plugin loader dll main struct containg pointer to the JVM, some static api ptr etc.

This is a bit hairy and I was surprised to see it work (after many crashes and hurdles I confess  )

- Once the FBAPI class is initialized, all Java plugins are found and loaded. If they iniherit from play_callback then they are forwarded those events. If they implement the context menu interface then it's taken into account in the dynamic C++ context menu.

SDK bindings for Java (soon)

Reply #7
Quote
Interesting info. At what point is get_sevice_list() called ? After all plugins static global variables (containing factories among other) are initialized ? what need to be modified in component_client.cpp so it can see services created dynamically in let's say, initquit_factory::on_load() ?


Ok, after some testing I understood the modification to make to component_client.cpp to dynamicaly instanciate factories. Now as yo said there's still the issue to find the factories to instanciate but i have some ideas about that.

SDK bindings for Java (soon)

Reply #8
So we've got a fb2k_java_api.dll that calls and links with SDK code but is not a plugin ! How can this work then ? It works because it lives in the main foobar thread. So if you try to direclty get services in this DLL, they do not exist, but pointers passed from the real plugin loader dll are valid. It's also passed a pointer to the plugin loader dll main struct containg pointer to the JVM, some static api ptr etc.

That sounds like a big headache to me.  Here is an idea you could try: Treat fb2k_java_api.dll as a normal foobar2000 component. However you first load it using System.loadLibrary() to satisfy constraints on the Java side. Then you load it from you wrapper component and initialize it like the core would (see below for a rough outline of the component loading process). fb2k_java_api.dll can then create and use foobar2000 services like any other component. Exposing services from fb2k_java_api.dll - compile-time or load-time created ones - is a bit trickier, but should still be feasible. You would have to load it from the wrapper component during the loading phase (which could be a problem if you need access to configuration data) and merge its service factory list into that of the wrapper component. The service factory list is a simple linked list, so that is quite trivial.

Ok, after some testing I understood the modification to make to component_client.cpp to dynamicaly instanciate factories. Now as yo said there's still the issue to find the factories to instanciate but i have some ideas about that.

For future reference, this is how component DLLs are loaded:
  • The core loads the component DLL using LoadLibrary().
  • The core retrieves a pointer to the foobar2000_get_interface() function and calls it to obtain a pointer to the DLL's foobar2000_client object.
  • After checking that the component's version is compatible with the core, the core calls foobar2000_client::get_service_list(). It also sets the component DLL's path and sets the stored configuration data (if any).

    Side note: The cfg_var mechanism is completely implemented on the component's side. For the core, a component's configuration data is just a block (or stream) of binary data. So in theory you could implement a custom configuration mechanism.
  • The main user interface is created. Also the contents of media library and the stored playlists are loaded.
  • The core calls initquit::on_init() for all initquit instances (in undefined order).

SDK bindings for Java (soon)

Reply #9
That sounds like a big headache to me.  Here is an idea you could try: Treat fb2k_java_api.dll as a normal foobar2000 component. However you first load it using System.loadLibrary() to satisfy constraints on the Java side. Then you load it from you wrapper component and initialize it like the core would (see below for a rough outline of the component loading process). fb2k_java_api.dll can then create and use foobar2000 services like any other component.


Good idea. I didn't think of it at the time because I did not know how the core was initializing plugins.
However  99% of fb2k_java_api's code is generated and I only need main_thread_callback_manager which I get from the global plugin struct I'm obligated to pass anyway.

Quote
For future reference, this is how component DLLs are loaded:
  • The core loads the component DLL using LoadLibrary().
  • The core retrieves a pointer to the foobar2000_get_interface() function and calls it to obtain a pointer to the DLL's foobar2000_client object.
  • After checking that the component's version is compatible with the core, the core calls foobar2000_client::get_service_list(). It also sets the component DLL's path and sets the stored configuration data (if any).

    Side note: The cfg_var mechanism is completely implemented on the component's side. For the core, a component's configuration data is just a block (or stream) of binary data. So in theory you could implement a custom configuration mechanism.
  • The main user interface is created. Also the contents of media library and the stored playlists are loaded.
  • The core calls initquit::on_init() for all initquit instances (in undefined order).


Thanks for the explanation, that confirms what I found reading the code and using the debugger.


Cool news is that I just got Java columns ui plugin working , bare a few minor issues, they behave like native panels.

 

SDK bindings for Java (soon)

Reply #10
I have a small issue with main_thread_callback. I need each main thread cycle to invoke some callback on Java plugins so I wrote:

Code: [Select]
class java_main_thread_callback : public main_thread_callback {

public:
    
    void callback_run() {

                callMainJavaThreadCallback();
    
        jvm_ctx->main_thread_cb_manager->add_callback(this);
    }

};


callMainJavaThreadCallback() invoke a java function each cycle to dispatch SWT events. It loks like:

Code: [Select]
public void mainThreadCallback() {
            while(display.readAndDispatch());
            display.sleep();
    };


This works perfectly fine except that now it's impossible to exit foobar (either via the menu or the window's close button, it's like it's a nop, the plugin's on_quit() is never called. If display.sleep() is omitted foobar will  take 100% CPU, but I don't understand what display.sleep() does to make CPU usage low. I did not succeeded having this code not take 100% cpu all the time without calling callMainJavaThreadCallback() [ which I want to do but I still like to understand how to have regular main thread callback not to take 100% CPU]

So my initial problem is that the previous code works fine except that exiting the app is ignored. Any idea ?


EDIT: Ok silliy me I found it : it was inifinite looping in the Java code, on exit.

EDIT2: The inoperant exit is not an infinite loop but some bad interaction between readAndDispatch() and the equivalent dispatching code on the C++ side, as if the java side discarded the WM_DESTROY message directed to foobar's main window or something...

Using this code and I can exit foobar, but I get a foobar freeze when in the menus, proably because menu messages don't get treated.

Code: [Select]
public void mainThreadCallback() {
                if(!display.readAndDispatch())
                           display.sleep();
    };

 
SimplePortal 1.0.0 RC1 © 2008-2020