JavaFX 2 Multiplatform in einer NetBeans Platform App

Post to Twitter

Da auch JavaFX 2 mit einem eigenen Native-Loader kommt, haben wir in einer NetBeans Platform wieder das Problem, die DLL/SO/… Bibliotheken an die richtige Stelle zu bekommen.

Man kann natürlich den Anwender JavaFX installieren und bin-Pfade einrichten lassen. Aber bequemer ist es natürlich, wenn sich die Platform Application darum kümmert.

Hier eine extrem simple (und nicht für alle Plattformen gültige) Lösung.

Es wird davon ausgegangen, dass sich die javafx-2.0.jar in dem Ordner cluster/modules/ext/somename befindet. Cluster ist der suite-Name. Der Pfad modules/ext ist Standard für externe Bibliotheken und somename ist (ggf.) ein Codename-Base-Name der externen Bibliothek oder “lib” (das ist JavaFX aber egal). Ich habe z.B. com.oracle verwendet (die aus der Gruppen-ID meiner Maven-Artifakte kommt).

Will ich nun (nur) Windows in 32bit und 64bit unterstützen, lege ich in dem ext-Order noch bin und darunter amd63 und x86 an. Also: cluster/modules/ext/bin/amd64 und cluster/modules/ext/bin/x86. Der Native Loader von JavaFX sucht immer (von der javafx-2.0.jar) in ../bin. Ich muss also nun die DLL’s kopieren.

Hier ein Beispiel meiner Dateistrukturen:

Da JavaFX fast mit Java7 verheiratet ist, werde ich auch nur mit Java7-nio den Weg beschreiten:

public class Installer extends ModuleInstall {

  @Override
  public void restored() {
    Class c = Installer.class;
    URL u = c.getProtectionDomain().getCodeSource().getLocation();
    String path = u.getPath();
    if ( path.startsWith("file:/") && path.endsWith(".jar!/")) {
      path = path.substring(6);
      path = path.substring(0, path.length()-2);
      int pos = path.lastIndexOf("/");
      if ( pos >= 0 ) {
        path = path.substring(0, pos);
        FileSystem fs = FileSystems.getDefault();
        Path dest = fs.getPath(path, "ext", "bin");
        Path source = fs.getPath(path, "ext", "bin", System.getProperty("os.arch"));
        try {
          for (Path toCopy : Files.newDirectoryStream(source)) {
            Path destFile = fs.getPath(dest.toString(), toCopy.getFileName().toString());
            Files.copy(toCopy, destFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
          }
        } catch (IOException ex) {
          Exceptions.printStackTrace(ex);
        }
      }
    }
  }
}

Am aufwendigsten ist die Ermittlung des Modulpfades per URL u = c.getProtectionDomain().getCodeSource().getLocation(); mit dem Extrahieren des eigentlichen Pfades (man sieht, WebStart wird nicht unterstützt). Den Path “source” ermittle ich ausschließlich über os.arch. Das ist natürlich nicht wirklich nützlich für Plattformen wie Linux. Wer weitere Plattformen unterstützen will, sollte noch zusätzliche Systemparameter ermitteln und weitere Unterordner anlegen. Ich würde aber explizit die Methoden aus org.openide.util.Utilities (isMac, isUnix, isWindows) verwenden. Die System Properties sind da zu geschwätzig.

Nehmen wir an, es würde alles fehl schlagen (weil wir Bibliotheken nicht mitliefern), dann sollte man noch eine eigene Hilfsklasse basteln, die den erfolgreichen (oder nicht erfolgreichen) Kopiervorgang an andere Module liefern kann (z.B. JavaFXHelper.isJavaFXAvailable()). Wenn man das nämlich nicht abprüft und doch JavaFX verwendet, sind die Abstürze so hart, dass häufig nicht mal das Hauptfenster angezeigt wird. Im Log findet ihr dann Fehler wie: java.lang.UnsatisfiedLinkError: Can’t load library: …

Was allerdings schön ist: mehr ist nicht zu machen. Man muss nicht (wie in einem alten Artikel von mir beschrieben) eine Startup-Klasse der Platform unterschieben, um JavaFX zu nutzen.

Einen Mini-WebBrowser fügt man so in eine TopComponent ein:

    final JFXPanel fxPanel = new JFXPanel();

    add(fxPanel);
   
    Platform.runLater(new Runnable() {

      @Override
      public void run() {
        Group group = new Group();
        Scene scene = new Scene(group);
        fxPanel.setScene(scene);
       
        view = new WebView();
        view.setMinSize(1024, 768);
        view.setPrefSize(1024, 768);
        group.getChildren().add (view);
      }
    });

view ist eine Feldvariable der TopComponent. Mit der Methode:

  public void browseTo (final String url) {
    Platform.runLater(new Runnable() {
      @Override
      public void run() {
        view.getEngine().load(url);
      }
    });
  }

kann man eine Webseite öffnen (http:// muss aber am Anfang der URL stehen).

Viel Spaß mit JavaFX in der NetBeans Platform.

Dieser Beitrag wurde unter JavaFX, NetBeans Plattform veröffentlicht. Setze ein Lesezeichen auf den Permalink.

4 Antworten auf JavaFX 2 Multiplatform in einer NetBeans Platform App

  1. Jens sagt:

    Du kennst diese Infos zu JNI in NetBeans? http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#jni

    The System.loadLibrary call originating from the module code will try to locate the library file in the following order of directories:

    modules/lib/
    modules/lib/”arch”/
    modules/lib/”arch”/”os”/
    so you may place e.g. 64bit linux version of a foo library in a file modules/lib/amd64/linux/libfoo.so.

  2. Pascal sagt:

    Besten Dank für die Ausführungen. Leider bekomme ich einen Fehler beim Ausführen. Ich habe definitiv die Dateien von einer 64Bit JavaFX Version kopiert.
    Hast du eine Idee?
    Hier der Stack:
    Source: MultiFileObject@66629978[Windows2Local/Components/MainTopComponent.settings]
    Caused: java.lang.UnsatisfiedLinkError: C:\syseca\projects\tmp\netbeans-javafx\application\target\netbeans_javafx\netbeans_javafx\modules\ext\bin\mat.dll: Can’t load this .dll (machine code=0xbd) on a AMD 64-bit platform
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1928)
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1825)
    at java.lang.Runtime.load0(Runtime.java:792)
    at java.lang.System.load(System.java:1059)
    at com.sun.glass.utils.NativeLibLoader.loadLibraryFullPath(NativeLibLoader.java:155)
    at com.sun.glass.utils.NativeLibLoader.loadLibraryInternal(NativeLibLoader.java:85)
    at com.sun.glass.utils.NativeLibLoader.loadLibrary(NativeLibLoader.java:30)
    at com.sun.glass.ui.Application$1.run(Application.java:40)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.glass.ui.Application.loadNativeLibrary(Application.java:38)
    at com.sun.glass.ui.win.WinApplication.(WinApplication.java:33)
    at com.sun.glass.ui.win.WinPlatformFactory.createApplication(WinPlatformFactory.java:20)
    at com.sun.glass.ui.win.WinPlatformFactory.createApplication(WinPlatformFactory.java:17)
    at com.sun.glass.ui.Application.Run(Application.java:63)
    at com.sun.javafx.tk.quantum.QuantumToolkit.startup(QuantumToolkit.java:259)
    Caused: java.lang.RuntimeException
    at com.sun.javafx.tk.quantum.QuantumToolkit.startup(QuantumToolkit.java:269)
    at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:68)
    at javafx.embed.swing.JFXPanel.initFx(JFXPanel.java:127)
    at javafx.embed.swing.JFXPanel.(JFXPanel.java:144)
    at ch.syseca.ebis.netbeansjavafxgui.MainTopComponent.(MainTopComponent.java:45)
    Caused: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
    at org.netbeans.modules.settings.convertors.XMLSettingsSupport$SettingsRecognizer.instanceCreate(XMLSettingsSupport.java:608)
    Caused: java.io.IOException
    at org.netbeans.modules.settings.convertors.XMLSettingsSupport$SettingsRecognizer.instanceCreate(XMLSettingsSupport.java:610)
    at org.netbeans.modules.settings.convertors.SerialDataConvertor$SettingsInstance.instanceCreate(SerialDataConvertor.java:424)
    [catch] at org.netbeans.core.windows.persistence.PersistenceManager.getTopComponentPersistentForID(PersistenceManager.java:571)
    at org.netbeans.core.windows.persistence.PersistenceManager.getTopComponentForID(PersistenceManager.java:681)
    at org.netbeans.core.windows.PersistenceHandler.getTopComponentForID(PersistenceHandler.java:489)
    at org.netbeans.core.windows.WindowManagerImpl.getTopComponentForID(WindowManagerImpl.java:927)
    at org.netbeans.core.windows.WindowManagerImpl.findTopComponent(WindowManagerImpl.java:280)
    at org.openide.windows.OpenComponentAction.getTopComponent(OpenComponentAction.java:76)
    at org.openide.windows.OpenComponentAction.actionPerformed(OpenComponentAction.java:88)
    at org.openide.awt.AlwaysEnabledAction$1.run(AlwaysEnabledAction.java:197)
    at org.openide.util.actions.ActionInvoker$1.run(ActionInvoker.java:95)
    at org.openide.util.actions.ActionInvoker.doPerformAction(ActionInvoker.java:116)
    at org.openide.util.actions.ActionInvoker.invokeAction(ActionInvoker.java:99)
    at org.openide.awt.AlwaysEnabledAction.actionPerformed(AlwaysEnabledAction.java:200)
    at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2018)
    at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2341)
    at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
    at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
    at javax.swing.AbstractButton.doClick(AbstractButton.java:376)
    at javax.swing.plaf.basic.BasicMenuItemUI.doClick(BasicMenuItemUI.java:833)
    at javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(BasicMenuItemUI.java:877)
    at java.awt.Component.processMouseEvent(Component.java:6505)
    at javax.swing.JComponent.processMouseEvent(JComponent.java:3321)
    at java.awt.Component.processEvent(Component.java:6270)
    at java.awt.Container.processEvent(Container.java:2229)
    at java.awt.Component.dispatchEventImpl(Component.java:4861)
    at java.awt.Container.dispatchEventImpl(Container.java:2287)
    at java.awt.Component.dispatchEvent(Component.java:4687)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4832)
    at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4492)
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4422)
    at java.awt.Container.dispatchEventImpl(Container.java:2273)
    at java.awt.Window.dispatchEventImpl(Window.java:2713)
    at java.awt.Component.dispatchEvent(Component.java:4687)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:707)
    at java.awt.EventQueue.access$000(EventQueue.java:101)
    at java.awt.EventQueue$3.run(EventQueue.java:666)
    at java.awt.EventQueue$3.run(EventQueue.java:664)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.awt.EventQueue$4.run(EventQueue.java:680)
    at java.awt.EventQueue$4.run(EventQueue.java:678)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:677)
    at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:162)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:211)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

eMail-Benachrichtigung bei weiteren Kommentaren.
Auch möglich: Abo ohne Kommentar.