All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jnr.ffi.LibraryLoader Maven / Gradle / Ivy

There is a newer version: 2.2.16
Show newest version
/*
 * Copyright (C) 2012 Wayne Meissner
 *
 * This file is part of the JNR project.
 *
 * 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.
 */

package jnr.ffi;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import jnr.ffi.mapper.CompositeFunctionMapper;
import jnr.ffi.mapper.CompositeTypeMapper;
import jnr.ffi.mapper.DataConverter;
import jnr.ffi.mapper.FromNativeConverter;
import jnr.ffi.mapper.FunctionMapper;
import jnr.ffi.mapper.SignatureTypeMapper;
import jnr.ffi.mapper.SignatureTypeMapperAdapter;
import jnr.ffi.mapper.ToNativeConverter;
import jnr.ffi.mapper.TypeMapper;
import jnr.ffi.provider.FFIProvider;
import jnr.ffi.provider.LoadedLibrary;

/**
 * Loads a native library and maps it to a java interface.
 *
 * 

Example usage

*
 * {@code
 *
 * public interface LibC {
 *    int puts(String str);
 * }
 *
 * LibC libc = LibraryLoader.create(LibC.class).load("c");
 *
 * libc.puts("Hello, World");
 *
 * }
 * 
*/ public abstract class LibraryLoader { public static final String DEFAULT_LIBRARY = "RTLD_DEFAULT"; private final List searchPaths = new ArrayList(); private final List libraryNames = new ArrayList(); private final List typeMappers = new ArrayList(); private final List functionMappers = new ArrayList(); private final Map optionMap = new EnumMap(LibraryOption.class); private final TypeMapper.Builder typeMapperBuilder = new TypeMapper.Builder(); private final FunctionMapper.Builder functionMapperBuilder = new FunctionMapper.Builder(); private final Class interfaceClass; private boolean failImmediately = false; /** * Creates a new {@code LibraryLoader} instance. * * @param The library type. * @param interfaceClass the interface that describes the native library functions * @return A {@code LibraryLoader} instance. */ public static LibraryLoader create(Class interfaceClass) { return FFIProvider.getSystemProvider().createLibraryLoader(interfaceClass); } protected LibraryLoader(Class interfaceClass) { this.interfaceClass = interfaceClass; } /** * When either the {@link jnr.ffi.annotations.SaveError} or * {@link jnr.ffi.annotations.IgnoreError} annotations are used, the * following matrix applies: * * (SL = save at library level, IM = ignore at method level, etc) * *
     *         | none |  SL  |  IL  | SL+IL|
     * -------------------------------------
     * none    | save | save | ignr | save |
     * SM      | save | save | save | save |
     * IM      | ignr | ignr | ignr | ignr |
     * SM + IM | save | save | save | save |
     * 
* * @param options options * @param methodHasSave whether the method has error-saving enabled * @param methodHasIgnore whether the method ignores errors * @return true if the error was saved, false otherwise */ public static boolean saveError(Map options, boolean methodHasSave, boolean methodHasIgnore) { // default to save boolean saveError = options.containsKey(LibraryOption.SaveError) || !options.containsKey(LibraryOption.IgnoreError); // toggle only according to above matrix if (saveError) { if (methodHasIgnore && !methodHasSave) { saveError = false; } } else { if (methodHasSave) { saveError = true; } } return saveError; } /** * Loads a native library and links the methods defined in {@code interfaceClass} * to native methods in the library. * * @param the interface type. * @param libraryNames the name of the library to load * @param interfaceClass the interface that describes the native library interface * @param searchPaths a map of library names to paths that should be searched * @param libraryOptions options * @return an instance of {@code interfaceclass} that will call the native methods. */ public static T loadLibrary( Class interfaceClass, Map libraryOptions, Map> searchPaths, String... libraryNames) { LibraryLoader loader = FFIProvider.getSystemProvider().createLibraryLoader(interfaceClass); for (String libraryName : libraryNames) { if (libraryName.equals(LibraryLoader.DEFAULT_LIBRARY)) { loader.searchDefault(); continue; } loader.library(libraryName); List paths = searchPaths.get(libraryName); if (paths != null) for (String path : paths) { loader.search(path); } } if (libraryOptions != null) { for (Map.Entry option : libraryOptions.entrySet()) { loader.option(option.getKey(), option.getValue()); } } return loader.failImmediately().load(); } /** * Same as calling {@link #loadLibrary(Class, Map, Map, String...)} with an empty search path map. * * @see #loadLibrary(Class, Map, Map, String...) * * @param interfaceClass the interface to implement * @param libraryOptions options * @param libraryNames names to try when searching for the library * @param the interface type * @return a new object implementing the interface and bound to the library */ public static T loadLibrary( Class interfaceClass, Map libraryOptions, String... libraryNames) { return (T) loadLibrary(interfaceClass, libraryOptions, Collections.EMPTY_MAP, libraryNames); } /** * Adds a library to be loaded. Multiple libraries can be specified using additional calls * to this method, and all libraries will be searched to resolve symbols (e.g. functions, variables). * * @param libraryName The name or path of library to load. * @return The {@code LibraryLoader} instance. */ public LibraryLoader library(String libraryName) { if (libraryName.equals(DEFAULT_LIBRARY)) { return searchDefault(); } this.libraryNames.add(libraryName); return this; } /** * Add the default library to the search order. This will search all loaded libraries in the current process * according to the system's implementation of dlsym(RTLD_DEFAULT). * * @return The {@code LibraryLoader} instance. */ public LibraryLoader searchDefault() { this.libraryNames.add(DEFAULT_LIBRARY); return this; } /** * Adds a path to search for libraries. Multiple paths can be specified using multiple calls * to this method, and all paths will be searched.. * * @param path A directory to search. * @return The {@code LibraryLoader} instance. */ public LibraryLoader search(String path) { searchPaths.add(path); return this; } /** * Sets an option when loading libraries. * * @see LibraryOption * * @param option The option to set. * @param value The value for the option. * @return The {@code LibraryLoader} instance. */ public LibraryLoader option(LibraryOption option, Object value) { switch (option) { case TypeMapper: if (value instanceof SignatureTypeMapper) { mapper((SignatureTypeMapper) value); } else if (value instanceof TypeMapper) { mapper((TypeMapper) value); } else if (value != null) { throw new IllegalArgumentException("invalid TypeMapper: " + value.getClass()); } break; case FunctionMapper: mapper((FunctionMapper) value); break; default: optionMap.put(option, value); } return this; } /** * Adds a type mapper to use when resolving method parameter and result types. * * Multiple type mappers can be specified by additional calls to this method, and * each mapper will be tried in order until one is successful. * * @param typeMapper The type mapper to use. * @return The {@code LibraryLoader} instance. */ public LibraryLoader mapper(TypeMapper typeMapper) { typeMappers.add(new SignatureTypeMapperAdapter(typeMapper)); return this; } /** * Adds a type mapper to use when resolving method parameter and result types. * * Multiple type mappers can be specified by additional calls to this method, and * each mapper will be tried in order until one is successful. * * @param typeMapper The type mapper to use. * @return The {@code LibraryLoader} instance. */ public LibraryLoader mapper(SignatureTypeMapper typeMapper) { typeMappers.add(typeMapper); return this; } /** * Adds a custom java type mapping. * * @param The Java type. * @param javaType The java type to convert to a native type. * @param toNativeConverter A {@link jnr.ffi.mapper.ToNativeConverter} that will convert from the java type to a native type. * @return The {@code LibraryLoader} instance. */ public LibraryLoader map(Class javaType, ToNativeConverter toNativeConverter) { typeMapperBuilder.map(javaType, toNativeConverter); return this; } /** * Adds a custom java type mapping. * * @param The Java type. * @param javaType The java type to convert to a native type. * @param fromNativeConverter A {@link jnr.ffi.mapper.ToNativeConverter} that will convert from the native type to a java type. * @return The {@code LibraryLoader} instance. */ public LibraryLoader map(Class javaType, FromNativeConverter fromNativeConverter) { typeMapperBuilder.map(javaType, fromNativeConverter); return this; } public LibraryLoader map(Class javaType, DataConverter dataConverter) { typeMapperBuilder.map(javaType, dataConverter); return this; } /** * Adds a function mapper to use when resolving symbols in this library. * * Multiple function mappers can be specified by additional calls to this method, and * each mapper will be tried in order, until one is successful. * * @param functionMapper The function mapper to use. * @return The {@code LibraryLoader} instance. */ public LibraryLoader mapper(FunctionMapper functionMapper) { functionMappers.add(functionMapper); return this; } /** * Adds a function name mapping to use when resolving symbols in this library. * * @param javaName The java method name. * @param nativeFunction The native library symbol to map the java method name to. * @return The {@code LibraryLoader} instance. */ public LibraryLoader map(String javaName, String nativeFunction) { functionMapperBuilder.map(javaName, nativeFunction); return this; } /** * Sets the native function calling convention. * *

This is only needed on windows platforms - unless explicitly specified, all platforms assume * {@link CallingConvention#DEFAULT} as the calling convention. * * @param convention The calling convention. * @return The {@code LibraryLoader} instance. */ public LibraryLoader convention(CallingConvention convention) { optionMap.put(LibraryOption.CallingConvention, convention); return this; } /** * Sets the calling convention of the library to the Windows stdcall calling convention * * @return This {@code LibraryLoader} instance. */ public final LibraryLoader stdcall() { return convention(CallingConvention.STDCALL); } /** * Turns off lazy propagation of load failures. By default, {@link jnr.ffi.LibraryLoader#load()} will not fail * immediately if any libraries cannot be loaded - instead, it will create an instance of the library interface * that re-throws any load errors when invoked. * * Calling this method will make {@link jnr.ffi.LibraryLoader#load()} throw errors immediately. * * @return This {@code LibraryLoader} instance. */ public final LibraryLoader failImmediately() { failImmediately = true; return this; } /** * Loads a native library and links the methods defined in {@code interfaceClass} * to native methods in the library. * * @param libraryName The name or path of library to load. * @return an implementation of the interface provided to {@link #create(Class)} that will call the native methods. */ public T load(String libraryName) { return library(libraryName).load(); } /** * Loads a native library and links the methods defined in {@code interfaceClass} * to native methods in the library. * * @return an implementation of the interface provided to {@link #create(Class)} that will call the native methods. */ public T load() { if (libraryNames.isEmpty()) { throw new UnsatisfiedLinkError("no library names specified"); } typeMappers.add(0, new SignatureTypeMapperAdapter(typeMapperBuilder.build())); optionMap.put(LibraryOption.TypeMapper, typeMappers.size() > 1 ? new CompositeTypeMapper(typeMappers) : typeMappers.get(0)); functionMappers.add(0, functionMapperBuilder.build()); optionMap.put(LibraryOption.FunctionMapper, functionMappers.size() > 1 ? new CompositeFunctionMapper(functionMappers) : functionMappers.get(0)); try { return loadLibrary(interfaceClass, Collections.unmodifiableList(libraryNames), getSearchPaths(), Collections.unmodifiableMap(optionMap), failImmediately); } catch (LinkageError error) { if (failImmediately) throw error; return createErrorProxy(error); } catch (Exception ex) { RuntimeException re = ex instanceof RuntimeException ? (RuntimeException) ex : new RuntimeException(ex); if (failImmediately) throw re; return createErrorProxy(re); } } private T createErrorProxy(final Throwable ex) { return interfaceClass.cast(Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass, LoadedLibrary.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { throw ex; } }) ); } private Collection getSearchPaths() { // custom paths before default paths because user may have same name as a system lib List paths = new ArrayList(searchPaths); paths.addAll(DefaultLibPaths.PATHS); return Collections.unmodifiableList(paths); } /** * Implemented by FFI providers to load the actual library. * * @param interfaceClass The java class that describes the functions to be mapped. * @param libraryNames A list of libraries to load and search for symbols. * @param searchPaths The paths to search for libraries to be loaded. * @param options The options to apply when loading the library. * @param failImmediately whether to fast-fail when the library does not implement the requested functions * @return an instance of {@code interfaceClass} that will call the native methods. */ protected abstract T loadLibrary(Class interfaceClass, Collection libraryNames, Collection searchPaths, Map options, boolean failImmediately); static final class DefaultLibPaths { static final List PATHS; private static void addPathsFromFile(Collection paths, File file) { if (!file.isFile() || !file.exists()) return; BufferedReader in = null; try { in = new BufferedReader(new FileReader(file)); String line = in.readLine(); while (line != null) { // ignore empties, comments and include directives if (!line.trim().isEmpty() && !line.startsWith("#") && !line.startsWith("include ")) { // add even if doesn't exist! We're just adding the default paths, we check for validity elsewhere paths.add(line); } line = in.readLine(); } } catch(IOException ignored) { } finally { if (in != null) { try { in.close(); } catch(IOException ignored) {} } } } static { // paths should have no duplicate entries and have insertion order LinkedHashSet paths = new LinkedHashSet(); try { paths.addAll(getPropertyPaths("jnr.ffi.library.path")); paths.addAll(getPropertyPaths("jaffl.library.path")); // Add JNA paths for compatibility paths.addAll(getPropertyPaths("jna.library.path")); // java.library.path should take care of Windows defaults paths.addAll(getPropertyPaths("java.library.path")); } catch (Exception ignored) { } if (Platform.getNativePlatform().isUnix()) { // order is intentional! paths.add("/usr/local/lib"); paths.add("/usr/lib"); paths.add("/lib"); } switch (Platform.getNativePlatform().getOS()) { case FREEBSD: case OPENBSD: case NETBSD: case LINUX: case ZLINUX: case MIDNIGHTBSD: // only for oracle jdk on Linux and non-OSX BSD parse /etc/ld.so.conf and /etc/ld.so.conf.d/* // more details: // https://github.com/jruby/jruby/issues/2913 // https://github.com/jruby/jruby/issues/3145 // https://github.com/elastic/logstash/issues/3127#issuecomment-101068714 File ldSoConf = new File("/etc/ld.so.conf"); File ldSoConfD = new File("/etc/ld.so.conf.d"); if (ldSoConf.exists()) { addPathsFromFile(paths, ldSoConf); } if (ldSoConfD.isDirectory()) { for (File file : ldSoConfD.listFiles()) { addPathsFromFile(paths, file); } } break; } PATHS = Collections.unmodifiableList(new ArrayList(paths)); } } private static List getPropertyPaths(String propName) { String value = System.getProperty(propName); if (value != null) { String[] paths = value.split(File.pathSeparator); return new ArrayList(Arrays.asList(paths)); } return Collections.emptyList(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy