1 /* 2 * The contents of this file is dual-licensed under 2 3 * alternative Open Source/Free licenses: LGPL 2.1 or later and 4 * Apache License 2.0. (starting with JNA version 4.0.0). 5 * 6 * You can freely decide which license you want to apply to 7 * the project. 8 * 9 * You may obtain a copy of the LGPL License at: 10 * 11 * http://www.gnu.org/licenses/licenses.html 12 * 13 * A copy is also included in the downloadable source code package 14 * containing JNA, in file "LGPL2.1". 15 * 16 * You may obtain a copy of the Apache License at: 17 * 18 * http://www.apache.org/licenses/ 19 * 20 * A copy is also included in the downloadable source code package 21 * containing JNA, in file "AL2.0". 22 */ 23 package com.sun.jna; 24 25 import com.sun.jna.internal.ReflectionUtils; 26 import java.lang.reflect.InvocationHandler; 27 import java.lang.reflect.Method; 28 import java.lang.reflect.Proxy; 29 import java.util.HashMap; 30 import java.util.Map; 31 import java.util.WeakHashMap; 32 33 /** Derive from this interface for all native library definitions. 34 * 35 * Define an instance of your library like this: 36 * <pre><code> 37 * MyNativeLibrary INSTANCE = (MyNativeLibrary) 38 * Native.load("mylib", MyNativeLibrary.class); 39 * </code></pre> 40 * <p> 41 * By convention, method names are identical to the native names, although you 42 * can map java names to different native names by providing a 43 * {@link FunctionMapper} as a value for key {@link #OPTION_FUNCTION_MAPPER} 44 * in the options map passed to the 45 * {@link Native#load(String, Class, Map)} call. 46 * <p> 47 * Although the names for structures and structure fields may be chosen 48 * arbitrarily, they should correspond as closely as possible to the native 49 * definitions. The same is true for parameter names. 50 * <p> 51 * This interface supports multiple, concurrent invocations of any library 52 * methods on the Java side. Check your library documentation for its 53 * multi-threading requirements on the native side. If a library is not safe 54 * for simultaneous multi-threaded access, consider using 55 * {@link Native#synchronizedLibrary} to prevent simultaneous multi-threaded 56 * access to the native code. 57 * <p> 58 * <b>Optional fields</b><br> 59 * Interface options will be automatically propagated to structures defined 60 * within the library provided a call to 61 * {@link Native#load(String,Class,Map)} is made prior to instantiating 62 * any of those structures. One common way of ensuring this is to declare 63 * an <b>INSTANCE</b> field in the interface which holds the 64 * <code>load</code> result. 65 * <p> 66 * <b>OPTIONS</b> (an instance of {@link Map}), 67 * <b>TYPE_MAPPER</b> (an instance of {@link TypeMapper}), 68 * <b>STRUCTURE_ALIGNMENT</b> (one of the alignment types defined in 69 * {@link Structure}), and <b>STRING_ENCODING</b> (a {@link String}) may also 70 * be defined. If no instance of the interface has been instantiated, these 71 * fields will be used to determine customization settings for structures and 72 * methods defined within the interface. 73 * <p> 74 * 75 * @author Todd Fast, todd.fast@sun.com 76 * @author Timothy Wall, twalljava@dev.java.net 77 */ 78 public interface Library { 79 /** Option key for a {@link TypeMapper} for the library. */ 80 String OPTION_TYPE_MAPPER = "type-mapper"; 81 /** Option key for a {@link FunctionMapper} for the library. */ 82 String OPTION_FUNCTION_MAPPER = "function-mapper"; 83 /** Option key for an {@link InvocationMapper} for the library. */ 84 String OPTION_INVOCATION_MAPPER = "invocation-mapper"; 85 /** Option key for structure alignment type ({@link Integer}), which should 86 * be one of the predefined alignment types in {@link Structure}. 87 */ 88 String OPTION_STRUCTURE_ALIGNMENT = "structure-alignment"; 89 /** <p>Option key for per-library String encoding. This affects conversions 90 * between Java unicode and native (<code>const char*</code>) strings (as 91 * arguments or Structure fields). 92 * </p> 93 * Defaults to {@link Native#getDefaultStringEncoding()}. 94 */ 95 String OPTION_STRING_ENCODING = "string-encoding"; 96 /** Option key for a boolean flag to allow any Java class instance as a 97 parameter. If no type mapper is found, the object is passed as a 98 pointer. 99 <em>NOTE:</em> This is for use with raw JNI interactions via the 100 JNIEnv data structure. 101 */ 102 String OPTION_ALLOW_OBJECTS = "allow-objects"; 103 /** Calling convention for the entire library. */ 104 String OPTION_CALLING_CONVENTION = "calling-convention"; 105 /** Flags to use when opening the native library (see {@link Native#open(String,int)}) */ 106 String OPTION_OPEN_FLAGS = "open-flags"; 107 /** <p>Class loader to use when searching for native libraries on the 108 * resource path (classpath). If not provided the current thread's 109 * context class loader is used.</p> 110 * If extracted from the resource path (i.e. bundled in a jar file), the 111 * loaded library's lifespan will mirror that of the class loader, which 112 * means you can use the same library in isolated contexts without 113 * conflict. 114 */ 115 String OPTION_CLASSLOADER = "classloader"; 116 117 static class Handler implements InvocationHandler { 118 119 static final Method OBJECT_TOSTRING; 120 static final Method OBJECT_HASHCODE; 121 static final Method OBJECT_EQUALS; 122 123 static { 124 try { 125 OBJECT_TOSTRING = Object.class.getMethod("toString"); 126 OBJECT_HASHCODE= Object.class.getMethod("hashCode"); 127 OBJECT_EQUALS = Object.class.getMethod("equals", Object.class); 128 } catch (Exception e) { 129 throw new Error("Error retrieving Object.toString() method"); 130 } 131 } 132 133 /** 134 * FunctionInfo has to be immutable to to make the object visible 135 * to other threads fully initialized. This is a prerequisite for 136 * using the class in the double checked locking scenario of {@link Handler#invoke(Object, Method, Object[])} 137 */ 138 private static final class FunctionInfo { 139 final InvocationHandler handler; 140 final Function function; 141 final boolean isVarArgs; 142 final Object methodHandle; 143 final Map<String, ?> options; 144 final Class<?>[] parameterTypes; 145 FunctionInfo(Object mh)146 FunctionInfo(Object mh) { 147 this.handler = null; 148 this.function = null; 149 this.isVarArgs = false; 150 this.options = null; 151 this.parameterTypes = null; 152 this.methodHandle = mh; 153 } 154 FunctionInfo(InvocationHandler handler, Function function, Class<?>[] parameterTypes, boolean isVarArgs, Map<String, ?> options)155 FunctionInfo(InvocationHandler handler, Function function, Class<?>[] parameterTypes, boolean isVarArgs, Map<String, ?> options) { 156 this.handler = handler; 157 this.function = function; 158 this.isVarArgs = isVarArgs; 159 this.options = options; 160 this.parameterTypes = parameterTypes; 161 this.methodHandle = null; 162 } 163 } 164 165 private final NativeLibrary nativeLibrary; 166 private final Class<?> interfaceClass; 167 // Library invocation options 168 private final Map<String, Object> options; 169 private final InvocationMapper invocationMapper; 170 private final Map<Method, FunctionInfo> functions = new WeakHashMap<Method, FunctionInfo>(); Handler(String libname, Class<?> interfaceClass, Map<String, ?> options)171 public Handler(String libname, Class<?> interfaceClass, Map<String, ?> options) { 172 173 if (libname != null && "".equals(libname.trim())) { 174 throw new IllegalArgumentException("Invalid library name \"" + libname + "\""); 175 } 176 177 if (!interfaceClass.isInterface()) { 178 throw new IllegalArgumentException(libname + " does not implement an interface: " + interfaceClass.getName()); 179 } 180 181 this.interfaceClass = interfaceClass; 182 this.options = new HashMap<String, Object>(options); 183 int callingConvention = AltCallingConvention.class.isAssignableFrom(interfaceClass) 184 ? Function.ALT_CONVENTION 185 : Function.C_CONVENTION; 186 if (this.options.get(OPTION_CALLING_CONVENTION) == null) { 187 this.options.put(OPTION_CALLING_CONVENTION, Integer.valueOf(callingConvention)); 188 } 189 if (this.options.get(OPTION_CLASSLOADER) == null) { 190 this.options.put(OPTION_CLASSLOADER, interfaceClass.getClassLoader()); 191 } 192 this.nativeLibrary = NativeLibrary.getInstance(libname, this.options); 193 invocationMapper = (InvocationMapper)this.options.get(OPTION_INVOCATION_MAPPER); 194 } 195 getNativeLibrary()196 public NativeLibrary getNativeLibrary() { 197 return nativeLibrary; 198 } 199 getLibraryName()200 public String getLibraryName() { 201 return nativeLibrary.getName(); 202 } 203 getInterfaceClass()204 public Class<?> getInterfaceClass() { 205 return interfaceClass; 206 } 207 208 @Override invoke(Object proxy, Method method, Object[] inArgs)209 public Object invoke(Object proxy, Method method, Object[] inArgs) 210 throws Throwable { 211 212 // Intercept Object methods 213 if (OBJECT_TOSTRING.equals(method)) { 214 return "Proxy interface to " + nativeLibrary; 215 } else if (OBJECT_HASHCODE.equals(method)) { 216 return Integer.valueOf(hashCode()); 217 } else if (OBJECT_EQUALS.equals(method)) { 218 Object o = inArgs[0]; 219 if (o != null && Proxy.isProxyClass(o.getClass())) { 220 return Function.valueOf(Proxy.getInvocationHandler(o) == this); 221 } 222 return Boolean.FALSE; 223 } 224 225 // Using the double-checked locking pattern to speed up function calls 226 FunctionInfo f = functions.get(method); 227 if(f == null) { 228 synchronized(functions) { 229 f = functions.get(method); 230 if (f == null) { 231 boolean isDefault = ReflectionUtils.isDefault(method); 232 if(! isDefault) { 233 boolean isVarArgs = Function.isVarArgs(method); 234 InvocationHandler handler = null; 235 if (invocationMapper != null) { 236 handler = invocationMapper.getInvocationHandler(nativeLibrary, method); 237 } 238 Function function = null; 239 Class<?>[] parameterTypes = null; 240 Map<String, Object> options = null; 241 if (handler == null) { 242 // Find the function to invoke 243 function = nativeLibrary.getFunction(method.getName(), method); 244 parameterTypes = method.getParameterTypes(); 245 options = new HashMap<String, Object>(this.options); 246 options.put(Function.OPTION_INVOKING_METHOD, method); 247 } 248 f = new FunctionInfo(handler, function, parameterTypes, isVarArgs, options); 249 } else { 250 f = new FunctionInfo(ReflectionUtils.getMethodHandle(method)); 251 } 252 functions.put(method, f); 253 } 254 } 255 } 256 if (f.methodHandle != null) { 257 return ReflectionUtils.invokeDefaultMethod(proxy, f.methodHandle, inArgs); 258 } else { 259 if (f.isVarArgs) { 260 inArgs = Function.concatenateVarArgs(inArgs); 261 } 262 if (f.handler != null) { 263 return f.handler.invoke(proxy, method, inArgs); 264 } 265 return f.function.invoke(method, f.parameterTypes, method.getReturnType(), inArgs, f.options); 266 } 267 } 268 } 269 } 270