【问题标题】:java.lang.UnsatisfiedLinkError when using lwjgl with gradlejava.lang.UnsatisfiedLinkError 将 lwjgl 与 gradle 一起使用时
【发布时间】:2015-11-05 02:34:04
【问题描述】:

我正在尝试将 Gradle 与 LWJGL 3 一起使用,但在构建时遇到了问题。 build.gradle 文件包含以下内容:

apply plugin: 'application'
mainClassName = "HelloWorld"
repositories {
    mavenCentral()
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
project.ext.lwjglVersion = "3.0.0a"
dependencies {
    compile "org.lwjgl:lwjgl:${lwjglVersion}"
    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-windows"
    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-linux"
    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-osx"
}

当我运行gradle run 时,我得到以下输出:

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:runjava.lang.UnsatisfiedLinkError: no lwjgl in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1857)
        at java.lang.Runtime.loadLibrary0(Runtime.java:870)
        at java.lang.System.loadLibrary(System.java:1119)
        at org.lwjgl.LWJGLUtil.loadLibrarySystem(LWJGLUtil.java:337)
        at org.lwjgl.Sys$1.run(Sys.java:36)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.lwjgl.Sys.<clinit>(Sys.java:33)
        at org.lwjgl.LWJGLUtil.initialize(LWJGLUtil.java:309)
        at org.lwjgl.system.MemoryUtil.<clinit>(MemoryUtil.java:35)
        at org.lwjgl.Pointer.<clinit>(Pointer.java:22)
        at org.lwjgl.PointerBuffer.<init>(PointerBuffer.java:24)
        at org.lwjgl.PointerBuffer.allocateDirect(PointerBuffer.java:281)
        at org.lwjgl.BufferUtils.createPointerBuffer(BufferUtils.java:190)
        at org.lwjgl.system.libffi.Closure.<clinit>(Closure.java:45)
        at org.lwjgl.glfw.Callbacks.errorCallbackPrint(Callbacks.java:129)
        at HelloWorld.<clinit>(HelloWorld.java:29)
Exception in thread "main"  FAILED

FAILURE: Build failed with an exception.

HelloWorld.java 包含以下内容(教程中的示例代码):

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.Callbacks;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.glfw.GLFWvidmode;

import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.NULL;

public class HelloWorld {
    private static GLFWErrorCallback errorCallback
            = Callbacks.errorCallbackPrint(System.err);

    private static GLFWKeyCallback keyCallback = new GLFWKeyCallback() {

        @Override
        public void invoke(long window, int key, int scancode, int action, int mods) {
            if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
                glfwSetWindowShouldClose(window, GL_TRUE);
            }
        }
    };
    public static void main(String[] args) {
        long window;

        /* Set the error callback */
        glfwSetErrorCallback(errorCallback);

        /* Initialize GLFW */
        if (glfwInit() != GL_TRUE) {
            throw new IllegalStateException("Unable to initialize GLFW");
        }

        /* Create window */
        window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL);
        if (window == NULL) {
            glfwTerminate();
            throw new RuntimeException("Failed to create the GLFW window");
        }

        /* Center the window on screen */
        ByteBuffer vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        glfwSetWindowPos(window,
                (GLFWvidmode.width(vidmode) - 640) / 2,
                (GLFWvidmode.height(vidmode) - 480) / 2
        );

        glfwMakeContextCurrent(window);
        GLContext.createFromCurrent();
        glfwSwapInterval(1);

        glfwSetKeyCallback(window, keyCallback);
        IntBuffer width = BufferUtils.createIntBuffer(1);
        IntBuffer height = BufferUtils.createIntBuffer(1);
        while (glfwWindowShouldClose(window) != GL_TRUE) {
            float ratio;
            glfwGetFramebufferSize(window, width, height);
            ratio = width.get() / (float) height.get();
            width.rewind();
            height.rewind();
            glViewport(0, 0, width.get(), height.get());
            glClear(GL_COLOR_BUFFER_BIT);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glOrtho(-ratio, ratio, -1f, 1f, 1f, -1f);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            glRotatef((float) glfwGetTime() * 50f, 0f, 0f, 1f);
            glBegin(GL_TRIANGLES);
            glColor3f(1f, 0f, 0f);
            glVertex3f(-0.6f, -0.4f, 0f);
            glColor3f(0f, 1f, 0f);
            glVertex3f(0.6f, -0.4f, 0f);
            glColor3f(0f, 0f, 1f);
            glVertex3f(0f, 0.6f, 0f);
            glEnd();
            glfwSwapBuffers(window);
            glfwPollEvents();
            width.flip();
            height.flip();
        }
        glfwDestroyWindow(window);
        keyCallback.release();
        glfwTerminate();
        errorCallback.release();
    }
}

是什么导致了错误,我该如何解决?

【问题讨论】:

    标签: java gradle lwjgl


    【解决方案1】:

    您可能已经注意到,在您下载的 lwjgl 文件夹(带有 jar 文件的文件夹)中,应该有一个名为“native”的目录。在这个目录中应该有三个带有系统名称的子文件夹(windows、macos...)。这个本机文件夹应该在您项目内的一个文件夹中(我创建了一个名为 lwjgl 的文件夹)。然后,在程序的第一行,你写System.setProperty("java.library.path", "./lwjgl")。这一行告诉 java 搜索那里的所有本机文件。

    【讨论】:

    • 这不行,你必须指定你要设置的属性名称:System.setProperty("java.library.path", "./lwjgl/native/")
    • 我没有下载 LWJGL 文件夹。我正在使用 Gradle。
    【解决方案2】:

    您必须在运行时将 Lwjgl 的本机库链接到您的应用程序。感谢 gradle,本机库位于类路径中……某处。

    您可以继续找到它们,将它们提取到一个临时位置,然后链接它们,但已经有一个 example project 为您做这件事。

    您需要做的就是将他们的类SharedLibraryLoader 包含到您的项目中并调用load() 方法。

    如果此链接失效,这里是您需要的课程的全部内容:

    /*******************************************************************************
     * Copyright 2011 See AUTHORS file.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     * 
     *   http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     ******************************************************************************/
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.util.HashSet;
    import java.util.UUID;
    import java.util.zip.CRC32;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipFile;
    
    /** Loads shared libraries from JAR files. Call {@link SharedLibraryLoader#load() to load the
     * required LWJGL 3 native shared libraries.
     * @author mzechner
     * @author Nathan Sweet */
    public class SharedLibraryLoader {
        static public boolean isWindows = System.getProperty("os.name").contains("Windows");
        static public boolean isLinux = System.getProperty("os.name").contains("Linux");
        static public boolean isMac = System.getProperty("os.name").contains("Mac");
        static public boolean isIos = false;
        static public boolean isAndroid = false;
        static public boolean isARM = System.getProperty("os.arch").startsWith("arm");
        static public boolean is64Bit = System.getProperty("os.arch").equals("amd64")
            || System.getProperty("os.arch").equals("x86_64");
    
        // JDK 8 only.
        static public String abi = (System.getProperty("sun.arch.abi") != null ? System.getProperty("sun.arch.abi") : "");
    
        static {
            String vm = System.getProperty("java.runtime.name");
            if (vm != null && vm.contains("Android Runtime")) {
                isAndroid = true;
                isWindows = false;
                isLinux = false;
                isMac = false;
                is64Bit = false;
            }
            if (!isAndroid && !isWindows && !isLinux && !isMac) {
                isIos = true;
                is64Bit = false;
            }
        }
    
        static boolean load = true;
    
        static {
            // Don't extract natives if using JWS.
            try {
                Method method = Class.forName("javax.jnlp.ServiceManager").getDeclaredMethod("lookup", new Class[] {String.class});
                method.invoke(null, "javax.jnlp.PersistenceService");
                load = false;
            } catch (Throwable ex) {
                load = true;
            }   
        }
    
        /** Extracts the LWJGL native libraries from the classpath and sets the "org.lwjgl.librarypath" system property. */
        static public synchronized void load () {
            load(false);
        }
    
        /** Extracts the LWJGL native libraries from the classpath and sets the "org.lwjgl.librarypath" system property. */
        static public synchronized void load (boolean disableOpenAL) {      
            if (!load) return;
    
            SharedLibraryLoader loader = new SharedLibraryLoader();
            File nativesDir = null;
            try {
                if (SharedLibraryLoader.isWindows) {
                    nativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? "lwjgl.dll" : "lwjgl32.dll", null).getParentFile();
                    if (!disableOpenAL)
                        loader.extractFile(SharedLibraryLoader.is64Bit ? "OpenAL.dll" : "OpenAL32.dll", nativesDir.getName());
                } else if (SharedLibraryLoader.isMac) {
                    nativesDir = loader.extractFile("liblwjgl.dylib", null).getParentFile();
                    if (!disableOpenAL) loader.extractFile("libopenal.dylib", nativesDir.getName());
                } else if (SharedLibraryLoader.isLinux) {
                    nativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? "liblwjgl.so" : "liblwjgl32.so", null).getParentFile();
                    if (!disableOpenAL)
                        loader.extractFile(SharedLibraryLoader.is64Bit ? "libopenal.so" : "libopenal32.so", nativesDir.getName());
                }
            } catch (Throwable ex) {
                throw new RuntimeException("Unable to extract LWJGL natives.", ex);
            }
            System.setProperty("org.lwjgl.librarypath", nativesDir.getAbsolutePath());
            load = false;
        }
    
        static private final HashSet<String> loadedLibraries = new HashSet<String>();
    
        private String nativesJar;
    
        public SharedLibraryLoader () {
        }
    
        /** Fetches the natives from the given natives jar file. Used for testing a shared lib on the fly.
         * @param nativesJar */
        public SharedLibraryLoader (String nativesJar) {
            this.nativesJar = nativesJar;
        }
    
        /** Returns a CRC of the remaining bytes in the stream. */
        public String crc (InputStream input) {
            if (input == null) throw new IllegalArgumentException("input cannot be null.");
            CRC32 crc = new CRC32();
            byte[] buffer = new byte[4096];
            try {
                while (true) {
                    int length = input.read(buffer);
                    if (length == -1) break;
                    crc.update(buffer, 0, length);
                }
            } catch (Exception ex) {
                if(input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                    }
                }
            }
            return Long.toString(crc.getValue(), 16);
        }
    
        /** Maps a platform independent library name to a platform dependent name. */
        public String mapLibraryName (String libraryName) {
            if (isWindows) return libraryName + (is64Bit ? "64.dll" : ".dll");
            if (isLinux) return "lib" + libraryName + (isARM ? "arm" + abi : "") + (is64Bit ? "64.so" : ".so");
            if (isMac) return "lib" + libraryName + (is64Bit ? "64.dylib" : ".dylib");
            return libraryName;
        }
    
        /** Loads a shared library for the platform the application is running on.
         * @param libraryName The platform independent library name. If not contain a prefix (eg lib) or suffix (eg .dll). */
        public synchronized void load (String libraryName) {
            // in case of iOS, things have been linked statically to the executable, bail out.
            if (isIos) return;
    
            libraryName = mapLibraryName(libraryName);
            if (loadedLibraries.contains(libraryName)) return;
    
            try {
                if (isAndroid)
                    System.loadLibrary(libraryName);
                else
                    loadFile(libraryName);
            } catch (Throwable ex) {
                throw new RuntimeException("Couldn't load shared library '" + libraryName + "' for target: "
                    + System.getProperty("os.name") + (is64Bit ? ", 64-bit" : ", 32-bit"), ex);
            }
            loadedLibraries.add(libraryName);
        }
    
        private InputStream readFile (String path) {
            if (nativesJar == null) {
                InputStream input = SharedLibraryLoader.class.getResourceAsStream("/" + path);
                if (input == null) throw new RuntimeException("Unable to read file for extraction: " + path);
                return input;
            }
    
            // Read from JAR.
            ZipFile file = null;
            try {
                file = new ZipFile(nativesJar);
                ZipEntry entry = file.getEntry(path);
                if (entry == null) throw new RuntimeException("Couldn't find '" + path + "' in JAR: " + nativesJar);
                return file.getInputStream(entry);
            } catch (IOException ex) {
                throw new RuntimeException("Error reading '" + path + "' in JAR: " + nativesJar, ex);
            } finally {
                if(file != null) {
                    try {
                        file.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
    
        /** Extracts the specified file into the temp directory if it does not already exist or the CRC does not match. If file
         * extraction fails and the file exists at java.library.path, that file is returned.
         * @param sourcePath The file to extract from the classpath or JAR.
         * @param dirName The name of the subdirectory where the file will be extracted. If null, the file's CRC will be used.
         * @return The extracted file. */
        public File extractFile (String sourcePath, String dirName) throws IOException {
            try {
                String sourceCrc = crc(readFile(sourcePath));
                if (dirName == null) dirName = sourceCrc;
    
                File extractedFile = getExtractedFile(dirName, new File(sourcePath).getName());
                return extractFile(sourcePath, sourceCrc, extractedFile);
            } catch (RuntimeException ex) {
                // Fallback to file at java.library.path location, eg for applets.
                File file = new File(System.getProperty("java.library.path"), sourcePath);
                if (file.exists()) return file;
                throw ex;
            }
        }
    
        /** Returns a path to a file that can be written. Tries multiple locations and verifies writing succeeds. */
        private File getExtractedFile (String dirName, String fileName) {
            // Temp directory with username in path.
            File idealFile = new File(System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name") + "/"
                + dirName, fileName);
            if (canWrite(idealFile)) return idealFile;
    
            // System provided temp directory.
            try {
                File file = File.createTempFile(dirName, null);
                if (file.delete()) {
                    file = new File(file, fileName);
                    if (canWrite(file)) return file;
                }
            } catch (IOException ignored) {
            }
    
            // User home.
            File file = new File(System.getProperty("user.home") + "/.libgdx/" + dirName, fileName);
            if (canWrite(file)) return file;
    
            // Relative directory.
            file = new File(".temp/" + dirName, fileName);
            if (canWrite(file)) return file;
    
            return idealFile; // Will likely fail, but we did our best.
        }
    
        /** Returns true if the parent directories of the file can be created and the file can be written. */
        private boolean canWrite (File file) {
            File parent = file.getParentFile();
            File testFile;
            if (file.exists()) {
                if (!file.canWrite() || !canExecute(file)) return false;
                // Don't overwrite existing file just to check if we can write to directory.
                testFile = new File(parent, UUID.randomUUID().toString());
            } else {
                parent.mkdirs();
                if (!parent.isDirectory()) return false;
                testFile = file;
            }
            try {
                new FileOutputStream(testFile).close();
                if (!canExecute(testFile)) return false;
                return true;
            } catch (Throwable ex) {
                return false;
            } finally {
                testFile.delete();
            }
        }
    
        private boolean canExecute (File file) {
            try {
                Method canExecute = File.class.getMethod("canExecute");
                if ((Boolean)canExecute.invoke(file)) return true;
    
                Method setExecutable = File.class.getMethod("setExecutable", boolean.class, boolean.class);
                setExecutable.invoke(file, true, false);
    
                return (Boolean)canExecute.invoke(file);
            } catch (Exception ignored) {
            }
            return false;
        }
    
        private File extractFile (String sourcePath, String sourceCrc, File extractedFile) throws IOException {
            String extractedCrc = null;
            if (extractedFile.exists()) {
                try {
                    extractedCrc = crc(new FileInputStream(extractedFile));
                } catch (FileNotFoundException ignored) {
                }
            }
    
            // If file doesn't exist or the CRC doesn't match, extract it to the temp dir.
            if (extractedCrc == null || !extractedCrc.equals(sourceCrc)) {
                try {
                    InputStream input = readFile(sourcePath);
                    extractedFile.getParentFile().mkdirs();
                    FileOutputStream output = new FileOutputStream(extractedFile);
                    byte[] buffer = new byte[4096];
                    while (true) {
                        int length = input.read(buffer);
                        if (length == -1) break;
                        output.write(buffer, 0, length);
                    }
                    input.close();
                    output.close();
                } catch (IOException ex) {
                    throw new RuntimeException("Error extracting file: " + sourcePath + "\nTo: " + extractedFile.getAbsolutePath(), ex);
                }
            }
    
            return extractedFile;
        }
    
        /** Extracts the source file and calls System.load. Attemps to extract and load from multiple locations. Throws runtime
         * exception if all fail. */
        private void loadFile (String sourcePath) {
            String sourceCrc = crc(readFile(sourcePath));
    
            String fileName = new File(sourcePath).getName();
    
            // Temp directory with username in path.
            File file = new File(System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name") + "/" + sourceCrc,
                fileName);
            Throwable ex = loadFile(sourcePath, sourceCrc, file);
            if (ex == null) return;
    
            // System provided temp directory.
            try {
                file = File.createTempFile(sourceCrc, null);
                if (file.delete() && loadFile(sourcePath, sourceCrc, file) == null) return;
            } catch (Throwable ignored) {
            }
    
            // User home.
            file = new File(System.getProperty("user.home") + "/.libgdx/" + sourceCrc, fileName);
            if (loadFile(sourcePath, sourceCrc, file) == null) return;
    
            // Relative directory.
            file = new File(".temp/" + sourceCrc, fileName);
            if (loadFile(sourcePath, sourceCrc, file) == null) return;
    
            // Fallback to java.library.path location, eg for applets.
            file = new File(System.getProperty("java.library.path"), sourcePath);
            if (file.exists()) {
                System.load(file.getAbsolutePath());
                return;
            }
    
            throw new RuntimeException(ex);
        }
    
        /** @return null if the file was extracted and loaded. */
        private Throwable loadFile (String sourcePath, String sourceCrc, File extractedFile) {
            try {
                System.load(extractFile(sourcePath, sourceCrc, extractedFile).getAbsolutePath());
                return null;
            } catch (Throwable ex) {
                ex.printStackTrace();
                return ex;
            }
        }
    }
    

    此示例项目适用于 Lwjgl 3(按照 OP 的要求)。但是,如果您使用的是 Lwjgl 2.9.x,它仍然可以工作,您只需在 load 方法中更改库的名称即可。

    【讨论】:

      【解决方案3】:

      来自documentation

      如果 Java 虚拟机找不到声明为本地的方法的适当本地语言定义,则抛出此异常。

      所以,给定no lwjgl in java.library.path,由于某种原因它找不到本机库。抱歉,没有 gradle 经验可以帮助您...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多