Eclipse/Gradle 多项目中库的不稳定行为 (UnsatisfiedLinkError)

Posted

技术标签:

【中文标题】Eclipse/Gradle 多项目中库的不稳定行为 (UnsatisfiedLinkError)【英文标题】:Unstable behavior of a library (UnsatisfiedLinkError) in Eclipse/Gradle multi-projects 【发布时间】:2021-09-28 15:09:53 【问题描述】:

最初有 2 个 Gradle 子项目。主要的“app”和“lib”服务。 在“app”中有一个资源目录,其中托管了一个自定义库(库直接构建在此目录中,以便立即集成到项目中)。但有一次,库的扩展导致签名错误,在寻找解决方案时,我尝试了一个提议(在网上找到),其中包括调用 Xcode 的“Clean Build Folder”功能。签名问题尚未解决,但“app”的所有来源已被删除。

在这次冒险之后,我决定创建第三个 Gradle 子项目(“oslib”),它只包含库和承载 JNI 的 Java 代码。至少如果源头移除再次发生,修复成本会比主项目低得多。

一旦签名问题得到解决,库就能够继续发展并正常进行测试,直到发生系统错误(崩溃)。没有什么异常,一个以自己的方式表现出来的库错误。

除了从此刻开始(不更改 Java 环境中的任何内容)之外,任何访问该库的尝试都会在调用的本机 Java 方法上导致 UnsatisfiedLinkError。 库加载没有错误,但行为与缺少库的行为相同。 我不明白为什么现在会有这种行为变化。为了看看会发生什么,我用最简单的代码重现了这个问题,而出现的问题让我更加困惑:

文件«AppLib.h»

#import <Foundation/Foundation.h>
#import <simd/simd.h>
#import <jni.h>

#ifndef _Included_AppLib
#define _Included_AppLib
#ifdef __cplusplus
extern "C" 
#endif

JNIEXPORT void JNICALL Java_test_TestJni_testNative (JNIEnv *env, jobject obj);   

#ifdef __cplusplus

#endif
#endif

文件«AppLib.m»

#import <simd/simd.h>
#import <AppKit/NSApplication.h>
#import <AppKit/NSWindow.h>

#import "AppLib.h"

JNIEXPORT void JNICALL Java_test_TestJni_testNative (JNIEnv *env, jobject obj) 
    NSLog(@"CALL Java_test_TestJni_testNative");

这里是通过“oslib”子项目的 JNI 访问类的代码,该子项目在其资源目录中包含该库:

package test;

public class MainClassProj3 

    public static void main(String[] args) 
        TestJni.test();
    


这里,在“loadLibrary()”方法中,库被复制到一个临时目录中(以便以后从一个 jar 文件中工作),然后从这个目录中加载。在评论中将有可能直接加载库(这对继续很重要)。

package test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class LibraryLoader 

    public static void loadLibrary(final String libPath, final String libName) 
        try 
            final String srcPath = (((libPath == null) || libPath.isEmpty()) ? "" : ("/" + libPath)) + "/" + libName;
            final String outPath = System.getProperty("java.io.tmpdir") + libName;
            final InputStream in = LibraryLoader.class.getResourceAsStream(srcPath);
            final OutputStream out = new FileOutputStream(new File(outPath));
            
            in.transferTo(out);
            in.close();
            out.close();
            
            System.out.println(outPath);
            System.load(outPath);
            
//          System.load("/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib");
            
         catch (Exception e) 
            e.printStackTrace();
        
    


这里是与库建立链接的类。

package test;

public class TestJni 

    static 
        LibraryLoader.loadLibrary("", "libAppLib.dylib");
    

    public native static void testNative();
    
    public static void test() 
        testNative();
    

这里是“app”子项目的主类的代码,它访问“oslib”子项目的服务(对native方法的调用)。

package test;

public class MainClassProj1 

    public static void main(String[] args) 
        TestJni.test();
    


如果我从第一个项目(“app”子项目)的主类调用库,则会收到错误消息。 该库似乎可以正确加载(没有例外),但访问本机方法的行为就像未加载该库一样:

/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
    at test.TestJni.testNative(Native Method)
    at test.TestJni.test(TestJni.java:12)
    at test.MainClassProj1.main(MainClassProj1.java:6)

如果我从第三个项目(“oslib”子项目)的主项目调用库,则操作是正确的:

/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
2021-07-19 19:39:38.788 java[14357:530171] CALL Java_test_TestJni_testNative

如果我再次尝试第一个项目,错误仍然存​​在。

/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
    at test.TestJni.testNative(Native Method)
    at test.TestJni.test(TestJni.java:12)
    at test.MainClassProj1.main(MainClassProj1.java:6)

每次调用 LibraryLoader.loadLibrary(String, String) 时,库都会被复制到临时目录中,并且永远不会被删除。因此,自从第一次测试以来,它就出现在“/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T”中。 所以通过替换LibraryLoader.loadLibrary(String, String)方法的内容:

public class LibraryLoader 

    public static void loadLibrary(final String libPath, final String libName) 
        try 
//          final String srcPath = (((libPath == null) || libPath.isEmpty()) ? "" : ("/" + libPath)) + "/" + libName;
//          final String outPath = System.getProperty("java.io.tmpdir") + libName;
//          final InputStream in = LibraryLoader.class.getResourceAsStream(srcPath);
//          final OutputStream out = new FileOutputStream(new File(outPath));
//          
//          in.transferTo(out);
//          in.close();
//          out.close();
//          
//          System.out.println(outPath);
//          System.load(outPath);
            
            System.load("/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib");
            
         catch (Exception e) 
            e.printStackTrace();
        
    


如果我再次运行第一个项目,错误仍然存​​在。

Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
    at test.TestJni.testNative(Native Method)
    at test.TestJni.test(TestJni.java:12)
    at test.MainClassProj1.main(MainClassProj1.java:6)

如果我在第三个项目之后立即运行,则会发生错误(新行为)。

Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
    at test.TestJni.testNative(Native Method)
    at test.TestJni.test(TestJni.java:12)
    at test.MainClassProj3.main(MainClassProj3.java:6)

现在我再次更改 LibraryLoader.loadLibrary(String, String) 的内容以返回其初始版本(在加载之前将库复制到临时目录中)然后我只运行第三个项目。一切都很好:

/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
2021-07-19 20:03:30.308 java[19075:553720] CALL Java_test_TestJni_testNative

我再次更改 LibraryLoader.loadLibrary (String, String) 的内容以返回其没有库副本的版本,然后我只运行第三个项目。 这次没有报错:

2021-07-19 20:06:55.747 java[19711:557089] CALL Java_test_TestJni_testNative

现在我再次运行第一个项目,这次也没有错误。

2021-07-19 20:10:05.934 java[20337:560000] CALL Java_test_TestJni_testNative

如果我按顺序重复刚才描述的内容,我会得到完全相同的结果。 请注意,从 jar 文件运行此代码可以完美运行。

有人对这种行为有解释吗?

访问只读文件(“oslib”子项目的资源目录中的库在任何时候都没有修改的库)的一系列独立执行如何导致或解决不稳定?

Mac OS 版本为 10.15.2 Java 版本为 OpenJDK 15.0.3 XCode 版本为 11.7 Eclipse 版本为 2021-06 (4.20.0)

【问题讨论】:

【参考方案1】:

我有一个我不太喜欢的解决方法,但它解除了我的障碍,其中包括识别我的开发环境并直接在其资源目录中查找库。希望它保持稳定。

在“LibraryLoader.loadLibrary()”方法中:

public class LibraryLoader 
    
    public static void loadLibrary(final String libPath, final String libName) 
        try 
            final String appPath = new File(System.getProperty("user.dir")).toString();
            final String srcPath = (((libPath == null) || libPath.isEmpty()) ? "" : ("/" + libPath)) + "/" + libName;
            final String outPath = System.getProperty("java.io.tmpdir") + libName;
            final InputStream in = LibraryLoader.class.getResourceAsStream(srcPath);
            final OutputStream out = new FileOutputStream(new File(outPath));
            final String devPath;
            
            if (appPath.startsWith("/Users/ ... absolute path of the main project ... /app")) 
                devPath = appPath.replaceAll("app", "oslib/src/main/ ... path where the library is located ... /" + srcPath);
                System.load(devPath);
            
            else 
                
                in.transferTo(out);
                in.close();
                out.close();
                
                System.out.println(outPath);
                System.load(outPath);
            
         catch (Exception e) 
            ...
        
    


【讨论】:

以上是关于Eclipse/Gradle 多项目中库的不稳定行为 (UnsatisfiedLinkError)的主要内容,如果未能解决你的问题,请参考以下文章

Android Studio 中库的 NoClassDefFoundError [重复]

怎么百变android studio 中库的arr文件的输出目录呢?

Unix中库的使用

Eclipse Gradle 向导使用 SubFolder -lib 创建项目

导入eclipse gradle spring boot项目后,Lombok无法在intellij idea 18.1版本中工作

数据库命名规范(命名规则)