在使用 JPMS Java 模块时,如何解决 JavaCPP 本机库的 UnsatisfiedLinkError?

发布于 2025-01-12 14:41:00 字数 3946 浏览 2 评论 0原文

我有一个使用 Gradle 和大量 JavaCPP 库的 Java 17 项目。我从克隆的 简单演示项目开始来自 JavaCPP Github 存储库。这个示例项目包含了几个本机库,例如 OpenCV、ffmpeg 等,所以我认为这是一个很好的测试。而且,毫不奇怪,它运行得很好。它调出了我的相机,进行了面部检测等。一切都非常酷。

我的目标 - 我想模块化我的 JavaCPP 项目以使其符合 JPMS。

这并不容易。因此,为了排除故障,我认为我应该从良好的测试代码开始,这就是我使用官方 JavaCPP Gradle 演示程序的原因。

因此,我执行了以下操作将其转换为符合 JPMS:

  1. 创建了一个放置在 src/main/java 下的 module-info.java。我添加了适当的 requires 语句(见下文)。
  2. 修改了 build.gradle 以添加多个 *-platform 依赖项和一些其他插件,包括 JavaFX。

TL;DR - 我让它工作了(尽管相机在应用程序窗口中以一定角度出现,这很奇怪,但我假设我仍然缺少 module-info.java< 中的库) /代码>)。问题是,它只有在我不仅在 build.gradle 中指定了许多额外的 *-platform 依赖项,而且还需要在 中列出实际的本机平台库之后才起作用。 >模块信息.java。因此,例如,我需要添加以下语句:

requires org.bytedeco.opencv.macosx.x86_64;

如果我不这样做,则会收到以下错误:

线程“main”中出现异常java.lang.UnsatisfiedLinkError:java.library.path中没有jniopencv_core:/Users/brk009/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions :/System/Library/Java/Extensions:/usr/lib/java:.

我的主要问题 - 如何在不对 module-info.java 中的平台相关本机库进行硬编码的情况下正确构建和执行模块化 JavaCPP 项目? strong> 我认为只需在 module-info.java 中指定 *-platform 库就可以了,但事实并非如此。

如果这只是我自己系统的一个项目,那么很好 - 我会接受它。但是,我想将一些示例代码传递给我的学生。如果他们都运行 Mac 就好了。然而,我的学生拥有相当异构的平台基础(即 Mac、Windows 和 Linux 用户的混合体)。理想情况下,拥有一个独立于平台的代码库并让我的程序构建和运行而不管平台如何,那就太好了。哎呀,如果我只需要将平台指定为 gradlew 的参数作为命令行参数,我什至会很高兴,例如所示的 此处,我可以在其中指定 -PjavacppPlatform=linux-x86_64。但这也不起作用。

我确实验证了 Loader.Detector.getPlatform() 返回正确的平台字符串,并且 Loader.getCacheDir() 返回 ~/.javacpp/cache 正如您所期望的。

任何帮助/指导将不胜感激!谢谢您。


module-info.java

module HelloJavaCPP {
    requires java.base;
    requires java.desktop;
    requires org.bytedeco.javacpp;
    requires org.bytedeco.javacpp.macosx.x86_64;  // I do NOT WANT to hard code any platform!
    requires org.bytedeco.javacv;
    requires org.bytedeco.opencv;
    requires org.bytedeco.opencv.macosx.x86_64;
    requires org.bytedeco.ffmpeg;
    requires org.bytedeco.ffmpeg.macosx.x86_64;
    requires org.bytedeco.openblas;
    requires org.bytedeco.openblas.macosx.x86_64;
}

build.gradle

plugins {
    id 'application'
    id 'java'
    id 'java-library'
    id 'org.openjfx.javafxplugin' version '0.0.12'
    id 'org.javamodularity.moduleplugin' version '1.8.10'
    id 'org.bytedeco.gradle-javacpp-platform' version '1.5.7'
}

group = 'org.hello'
version = '1.5.7'

repositories {
    mavenLocal()
    mavenCentral()
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

javafx {
    version = "17.0.2"
    modules = [ 'javafx.graphics','javafx.controls', 'javafx.fxml' ]
}

dependencies {
    api "org.bytedeco:javacv-platform:1.5.7"
    api 'org.bytedeco:opencv-platform:4.5.5-1.5.7'
//    api "org.bytedeco:opencv-platform-gpu:4.5.5-$version"
    api "org.bytedeco:ffmpeg-platform-gpl:5.0-$version"
    api 'org.bytedeco:openblas-platform:0.3.19-1.5.7'
    testImplementation 'junit:junit:4.13.2'
}

application {
    mainModule = "$moduleName"
    mainClass = "org.hello.Demo"
}


settings.gradle 我把这个包括在内只是为了以防万一。

pluginManagement {
    repositories {
        mavenLocal()
        mavenCentral()
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
        gradlePluginPortal()
    }
}

rootProject.name = 'HelloJavaCPP'

gradle.rootProject { ext.javacppVersion = '1.5.7' }

I have a Java 17 project using Gradle with a multitude of JavaCPP libraries. I started with a simple demo project that I cloned from the JavaCPP Github repo. This sample project incorporates several native libs such as OpenCV, ffmpeg, etc., so I thought it'd be a good test. And, no surprise, it worked just fine. It brought up my camera, did the face detection, etc. All very cool.

My aim - I want to modularize my JavaCPP projects to be JPMS compliant.

Not easy. So, to troubleshoot, I figure that I would start with good test code, which is why I'm working with the official JavaCPP Gradle demo program.

So, I did the following to convert it to be JPMS compliant:

  1. Created a module-info.java placed down src/main/java. I added the appropriate requires statements (see below).
  2. Modified build.gradle to add several *-platform dependencies and a few other plugins, including JavaFX.

The TL;DR - I got it to work (though the camera appears at an angle in the app window, which is just weird, but I'm assuming I'm still missing a library in module-info.java). The problem is that it only worked after I not only specified numerous additional *-platform dependencies in build.gradle, but also needed to list the actual native platform libraries in module-info.java. So, for instance, I need to add the following statement:

requires org.bytedeco.opencv.macosx.x86_64;

If I do not do that, then I get the following error:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no jniopencv_core in java.library.path: /Users/brk009/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.

My main question - How can I make a modular JavaCPP project build and execute properly without hard coding the platform dependent native libraries in module-info.java? I thought that just specifying the *-platform libraries in module-info.java would do it, but nope.

If this was just a project for my own system, then fine - I'd live with it. However, I want to pass some of my example code off to my students. It'd be fine if they all ran Macs. However, my students have quite a heterogeneous platform base (i.e. a mix of Mac, Windows, and Linux users.) Ideally, it'd be great to have a platform-independent codebase and let my program build and run regardless of the platform. Heck, I'd even be happy if I only needed to specify the platform as a parameter for gradlew as a command-line argument, such as indicated here, where I could just specify -PjavacppPlatform=linux-x86_64. But that did not work either.

I did verify that Loader.Detector.getPlatform() returns the correct platform string, and Loader.getCacheDir() returns ~/.javacpp/cache as you would expect.

Any help/guidance would be immensely appreciated! Thank you kindly.


module-info.java

module HelloJavaCPP {
    requires java.base;
    requires java.desktop;
    requires org.bytedeco.javacpp;
    requires org.bytedeco.javacpp.macosx.x86_64;  // I do NOT WANT to hard code any platform!
    requires org.bytedeco.javacv;
    requires org.bytedeco.opencv;
    requires org.bytedeco.opencv.macosx.x86_64;
    requires org.bytedeco.ffmpeg;
    requires org.bytedeco.ffmpeg.macosx.x86_64;
    requires org.bytedeco.openblas;
    requires org.bytedeco.openblas.macosx.x86_64;
}

build.gradle

plugins {
    id 'application'
    id 'java'
    id 'java-library'
    id 'org.openjfx.javafxplugin' version '0.0.12'
    id 'org.javamodularity.moduleplugin' version '1.8.10'
    id 'org.bytedeco.gradle-javacpp-platform' version '1.5.7'
}

group = 'org.hello'
version = '1.5.7'

repositories {
    mavenLocal()
    mavenCentral()
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

javafx {
    version = "17.0.2"
    modules = [ 'javafx.graphics','javafx.controls', 'javafx.fxml' ]
}

dependencies {
    api "org.bytedeco:javacv-platform:1.5.7"
    api 'org.bytedeco:opencv-platform:4.5.5-1.5.7'
//    api "org.bytedeco:opencv-platform-gpu:4.5.5-$version"
    api "org.bytedeco:ffmpeg-platform-gpl:5.0-$version"
    api 'org.bytedeco:openblas-platform:0.3.19-1.5.7'
    testImplementation 'junit:junit:4.13.2'
}

application {
    mainModule = "$moduleName"
    mainClass = "org.hello.Demo"
}


settings.gradle
I'm including this just incase.

pluginManagement {
    repositories {
        mavenLocal()
        mavenCentral()
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
        gradlePluginPortal()
    }
}

rootProject.name = 'HelloJavaCPP'

gradle.rootProject { ext.javacppVersion = '1.5.7' }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

魂牵梦绕锁你心扉 2025-01-19 14:41:00

塞缪尔上面发布的那些链接非常有帮助。事实证明,JavaFX 存在一些模块化特性,当 JavaFX 与非 JavaFX 模块(例如 JavaCPP 中的模块)一起使用时,这些特性可能会造成严重破坏。请参阅此处

重要的关键部分:

我们可以使用 JavaFX Maven 插件的 run 目标、Exec Maven 插件的 java 目标,或者使用从 Maven 依赖项和选项 --add-modules ALL-MODULE 计算的模块路径手动启动 java -路径

一旦我弄清楚如何在 Gradle 中添加 JVM 参数,我就能够删除所有硬编码架构 requires 语句,并且我现在可以使用 gradlew run,让 JavaCPP 的 Loader 类来完成发现架构本身并加载适当的本机库的所有工作!

需要更改的两个最重要的文件:


module-info.java

请注意,这变得多么简单,并且它没有硬编码的平台系统架构信息,这正是我们想要的:

module HelloJavaCPP {
    requires java.base;
    requires java.desktop;
    requires org.bytedeco.javacpp;
    requires org.bytedeco.javacv;
    requires org.bytedeco.opencv;
    requires org.bytedeco.ffmpeg;
    requires org.bytedeco.openblas;
}

build.gradle

我需要做出的最重要的更改(我从发布的信息 这里是添加一个run配置,并指定JVM参数`--add-modules

plugins {
    id 'application'
    id 'java'
    id 'java-library'
    id 'org.openjfx.javafxplugin' version '0.0.12'
    id 'org.javamodularity.moduleplugin' version '1.8.10'
    id 'org.bytedeco.gradle-javacpp-platform' version "$javacppVersion"
}

group = 'org.hello'
version = '1.5.7'

repositories {
    mavenCentral()
}

ext {
    // javacppPlatform - should be autodetected, but can also specify on cmd line
    // as -PjavacppPlatform=macosx-x86_64
//    javacppPlatform = 'linux-x86_64,macosx-x86_64,windows-x86_64,etc' // defaults to Loader.getPlatform()
    javacppPlatform = 'macosx-x86_64' // defaults to Loader.getPlatform()
}

javafx {
    version = "17.0.2"
    modules = [ 'javafx.graphics','javafx.controls', 'javafx.fxml' ]
}

dependencies {
    api "org.bytedeco:javacpp-platform:$javacppVersion"
    api "org.bytedeco:javacv-platform:$javacppVersion"
    api "org.bytedeco:opencv-platform:4.5.5-1.5.7"
    api "org.bytedeco:ffmpeg-platform-gpl:5.0-1.5.7"
    api "org.bytedeco:openblas-platform:0.3.19-1.5.7"
    testImplementation 'junit:junit:4.13.2'
}

application {
    mainModule = "$moduleName"
    mainClass = "org.hello.Demo"
}

// THIS WAS THE PRIMARY CHANGE: 
run {
    jvmArgs = ['--add-modules', 'ALL-MODULE-PATH']
}

值得注意的是,我能够删除extbuild.gradle 中进行配置,它允许 Loader.getPlatform() 在运行时完成确定平台的工作,而且效果很好(我把它留在了!仅供参考。)

我希望这有帮助从我读到的内容来看,我没有测试构建图像,这相当复杂,我们会再次解决这个问题

Those links posted by Samuel above were immensely helpful. It turns out there are some modularity peculiarities with JavaFX that can wreck havoc when using JavaFX with non-JavaFX modules such as those in JavaCPP. See here.

The key part that was important:

We can either use the run goal of the JavaFX Maven plugin, the java goal of the Exec Maven plugin, or manually launch java with a module path computed from Maven dependencies and option --add-modules ALL-MODULE-PATH.

Once I figured out how to add a JVM argument in Gradle, I was able to remove all hard-coded architecture requires statements, and I can now use gradlew run, let JavaCPP's Loader class do all the work of discovering the architecture itself and loading the appropriate native libraries!

The two most important files that need to change:


module-info.java

Notice how much simpler this becomes, and it has NO hard-coded platform system architecture information, which is exactly what we want:

module HelloJavaCPP {
    requires java.base;
    requires java.desktop;
    requires org.bytedeco.javacpp;
    requires org.bytedeco.javacv;
    requires org.bytedeco.opencv;
    requires org.bytedeco.ffmpeg;
    requires org.bytedeco.openblas;
}

build.gradle

The most important change I needed to make (which I got from information posted here was to add a run configuration, and specify the JVM argument `--add-modules

plugins {
    id 'application'
    id 'java'
    id 'java-library'
    id 'org.openjfx.javafxplugin' version '0.0.12'
    id 'org.javamodularity.moduleplugin' version '1.8.10'
    id 'org.bytedeco.gradle-javacpp-platform' version "$javacppVersion"
}

group = 'org.hello'
version = '1.5.7'

repositories {
    mavenCentral()
}

ext {
    // javacppPlatform - should be autodetected, but can also specify on cmd line
    // as -PjavacppPlatform=macosx-x86_64
//    javacppPlatform = 'linux-x86_64,macosx-x86_64,windows-x86_64,etc' // defaults to Loader.getPlatform()
    javacppPlatform = 'macosx-x86_64' // defaults to Loader.getPlatform()
}

javafx {
    version = "17.0.2"
    modules = [ 'javafx.graphics','javafx.controls', 'javafx.fxml' ]
}

dependencies {
    api "org.bytedeco:javacpp-platform:$javacppVersion"
    api "org.bytedeco:javacv-platform:$javacppVersion"
    api "org.bytedeco:opencv-platform:4.5.5-1.5.7"
    api "org.bytedeco:ffmpeg-platform-gpl:5.0-1.5.7"
    api "org.bytedeco:openblas-platform:0.3.19-1.5.7"
    testImplementation 'junit:junit:4.13.2'
}

application {
    mainModule = "$moduleName"
    mainClass = "org.hello.Demo"
}

// THIS WAS THE PRIMARY CHANGE: 
run {
    jvmArgs = ['--add-modules', 'ALL-MODULE-PATH']
}

It's worth noting that I was able to remove the ext configuration in build.gradle, which allows Loader.getPlatform() to do the work of determining the platform at runtime, and it worked just fine! (I left it in place just for reference purposes.)

I hope this helps others. I did NOT test out building an image, as judging from what I read, that is quite an additional level of complexity. We'll tackle that another time.

Thank you again.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文