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