在 Processing 中使用 SimpleOpenNI 时,如果尝试将本来能够正常运行的 pde 文件导出成应用,那么在运行时会出现java.lang.UnsatisfiedLinkError
这个错误。详细信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Can't load SimpleOpenNI library (libSimpleOpenNI.jnilib) : java.lang.UnsatisfiedLinkError: Can't load library: /SimpleOpenNI/library/libSimpleOpenNI.jnilib Verify if you installed SimpleOpenNI correctly. http://code.google.com/p/simple-openni/wiki/Installation java.lang.UnsatisfiedLinkError: SimpleOpenNI.SimpleOpenNIJNI.swig_module_init()V at SimpleOpenNI.SimpleOpenNIJNI.swig_module_init(Native Method) at SimpleOpenNI.SimpleOpenNIJNI.<clinit>(SimpleOpenNIJNI.java:290) at SimpleOpenNI.ContextWrapper.<init>(ContextWrapper.java:54) at SimpleOpenNI.SimpleOpenNI.<init>(SimpleOpenNI.java:253) at Sketch.settings(Sketch.java:28) at processing.core.PApplet.handleSettings(PApplet.java:954) at processing.core.PApplet.runSketch(PApplet.java:10786) at processing.core.PApplet.main(PApplet.java:10511) at Main.main(Main.java:7)
根据错误信息,是在读取libSimpleOpenNI.jnilib
这个库文件时失败导致的。奇怪的是,程序尝试读取的路径是:/SimpleOpenNI/library/libSimpleOpenNI.jnilib
。这是一个很奇怪的绝对路径。也有人 尝试直接将库文件复制到这个全局路径的位置,可以让程序运行起来。可是这种方法也太不优雅了。
为什么会出现这种现象?
通过 IntelliJ 可以打开SimpleOpenNI.jar
查看代码细节。可以看到SimpleOpenNI.class
中确定载入库文件路径的方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static { String var0 = System.getProperty("os.name" ).toLowerCase(); String var1 = "SimpleOpenNI" ; String var2 = System.getProperty("os.arch" ).toLowerCase(); if (var0.indexOf("win" ) >= 0 ) { } else if (var0.indexOf("nix" ) < 0 && var0.indexOf("linux" ) < 0 ) { if (var0.indexOf("mac" ) >= 0 ) { var1 = "lib" + var1 + ".jnilib" ; nativLibPath = getLibraryPathLinux() + "/SimpleOpenNI/library/" ; nativDepLibPath = nativLibPath + "osx/" ; } } else { nativLibPath = "/SimpleOpenNI/library/linux" ; if (var2.indexOf("86" ) >= 0 ) { var1 = var1 + "32" ; } else if (var2.indexOf("64" ) >= 0 ) { var1 = "lib" + var1 + "64.so" ; nativLibPath = getLibraryPathLinux() + "/SimpleOpenNI/library/" ; nativDepLibPath = nativLibPath + "linux64/" ; } } try { System.load(nativLibPath + var1); } catch (UnsatisfiedLinkError var5) { System.out.println("Can't load SimpleOpenNI library (" + var1 + ") : " + var5); System.out.println("Verify if you installed SimpleOpenNI correctly.\nhttp://code.google.com/p/simple-openni/wiki/Installation" ); } _initFlag = false ; }
注意到在生成库文件路径时,/SimpleOpenNI/library/libSimpleOpenNI.jnilib
,前面应该会添加getLibraryPathLinux()
的结果。
1 2 3 4 5 6 7 8 9 10 11 12 public static String getLibraryPathLinux () { URL var0 = SimpleOpenNI.class.getResource("SimpleOpenNI.class" ); if (var0 != null ) { String var1 = var0.toString().replace("%20" , " " ); int var2 = var1.indexOf(47 ); boolean var3 = true ; int var4 = var1.indexOf("/SimpleOpenNI/library" ); return -1 < var2 && -1 < var4 ? var1.substring(var2, var4) : "" ; } else { return "" ; } }
我尝试了在不同环境下,SimpleOpenNI.class.getResource("SimpleOpenNI.class")
下运行的结果。发现:
在 pde 运行时,获取到的是独立的SimpleOpenNI.jar
下的路径,例如:/Users/lena/Documents/Processing/libraries/SimpleOpenNI/library/SimpleOpenNI.jar!/SimpleOpenNI/SimpleOpenNI.class
在导出应用中运行时,获取到的是打包后应用内的,例如.../MySketch/application.macosx/MySketch.app/Contents/Java/SimpleOpenNI.jar!/SimpleOpenNI/SimpleOpenNI.class
在函数getLibraryPathLinux
中,程序会定位/SimpleOpenNI/library
这个字符串,然后取出这个子字符串前的内容构成的路径。上述第二种情形内,SimpleOpenNI.jar 被打包到应用内后,不在处于/SimpleOpenNI/library
这个前缀目录下,所以导致定位失败。
如何解决这个问题
在无法直接修改 SimpleOpenNI 的源代码的情况下,要修复这个问题,就要想办法把SimpleOpenNI.jar
放到SimpleOpenNI/library
目录下。我使用的 macOS 系统,下面的方法都是在 Mac 下测试。不过基本思路可以迁移到 Windows 上。
在生成的 App 上右键选择显示包内容。可以查看其内部结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 . ├── Info.plist ├── Java │ ├── Sketch.jar │ ├── NiTE2 │ ├── SimpleOpenNI.jar │ ├── SimpleOpenNI32.dll │ ├── SimpleOpenNI64.dll │ ├── core.jar │ ├── data │ ├── gluegen-rt-natives-macosx-universal.jar │ ├── gluegen-rt.jar │ ├── javamp3-1.0.3.jar │ ├── jogl-all-natives-macosx-universal.jar │ ├── jogl-all.jar │ ├── jsyn-20171016.jar │ ├── libSimpleOpenNI.jnilib │ ├── libSimpleOpenNI64.so │ ├── osx │ ├── sound.jar │ ├── win32 │ └── win64 ├── MacOS │ └── Sketch ├── PkgInfo ├── PlugIns │ └── jdk1.8.0_181.jdk └── Resources ├── en.lproj └── sketch.icns
可以看到SimpleOpenNI.jar
位于Java
目录下。我尝试过直接在此处创建目录SimpleOpenNI/library
并把SimpleOpenNI.jar
放进去。但是运行提示无法找到SimpleOpenNI.jar
。这需要在 APP 运行时进一步指定CLASSPATH
。有一种方法是直接在 Info.plist 文件里面添加-Djava.class.path
运行属性,或者添加ClASSPATH
环境变量,但是这种方法会要求你手动填写所有需要使用的 jar 依赖,甚至是包括 processing 的 jar 文件。这对于后续维护和修改很不利。所以这里我采取了另一种取巧的办法。
进入Contents/MacOS
目录,删除原来的Sketch
文件(你看到的应该是和你的 Processing 程序同名的文件,我这里用 Sketch 来代替)。新建一个同名的空白的文本文件,然后在文件中添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #!/bin/bash cd "$(dirname ${BASH_SOURCE}) " cd ../..APP_ROOT=$(pwd ) cd Contents/JavaJAR_LIBS=$(ls *.jar | tr "\n" ":" ) JAR_LIBS=${JAR_LIBS} ./SimpleOpenNI/library/SimpleOpenNI.jar APP_NAME=$(basename "${BASH_SOURCE} " ) JAVA_BIN=${APP_ROOT} /Contents/PlugIns/jdk1.8.0_181.jdk/Contents/Home/jre/bin/java ${JAVA_BIN} \-Djna.nosys=true \ -Djava.ext.dirs=$APP_ROOT /Contents/PlugIns/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext \ -Xdock:icon=$APP_ROOT /Contents/Resources/sketch.icns \ -Djava.library.path=$APP_ROOT /Contents/Java \ -Dapple.laf.useScreenMenuBar=true \ -Dcom.apple.macos.use-file-dialog-packages=true \ -Dcom.apple.macos.useScreenMenuBar=true \ -Dcom.apple.mrj.application.apple.menu.about.name=${APP_NAME} \ -classpath ${JAR_LIBS} ${APP_NAME}
为这个文件添加可执行权限
将~/Documents/Processing/libraries/SimpleOpenNI
整个文件夹拷贝进导出 APP 的Contents/Java目录下
。然后就可以运行了。