/* Copyright (c) 2007-2013 Timothy Wall, All Rights Reserved * * The contents of this file is dual-licensed under 2 * alternative Open Source/Free licenses: LGPL 2.1 or later and * Apache License 2.0. (starting with JNA version 4.0.0). * * You can freely decide which license you want to apply to * the project. * * You may obtain a copy of the LGPL License at: * * http://www.gnu.org/licenses/licenses.html * * A copy is also included in the downloadable source code package * containing JNA, in file "LGPL2.1". * * You may obtain a copy of the Apache License at: * * http://www.apache.org/licenses/ * * A copy is also included in the downloadable source code package * containing JNA, in file "AL2.0". */ package com.sun.jna; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import com.sun.jna.win32.DLLCallback; /** Provides a reference to an association between a native callback closure * and a Java {@link Callback} closure. */ public class CallbackReference extends WeakReference<Callback> { static final Map<Callback, CallbackReference> callbackMap = new WeakHashMap<Callback, CallbackReference>(); static final Map<Callback, CallbackReference> directCallbackMap = new WeakHashMap<Callback, CallbackReference>(); static final Map<Pointer, Reference<Callback>> pointerCallbackMap = new WeakHashMap<Pointer, Reference<Callback>>(); // Track memory allocations associated with this closure (usually String args) static final Map<Object, Object> allocations = new WeakHashMap<Object, Object>(); // Global map of allocated closures to facilitate centralized cleanup private static final Map<CallbackReference, Reference<CallbackReference>> allocatedMemory = Collections.synchronizedMap(new WeakHashMap<CallbackReference, Reference<CallbackReference>>()); private static final Method PROXY_CALLBACK_METHOD; static { try { PROXY_CALLBACK_METHOD = CallbackProxy.class.getMethod("callback", new Class[] { Object[].class }); } catch(Exception e) { throw new Error("Error looking up CallbackProxy.callback() method"); } } private static final Map<Callback, CallbackThreadInitializer> initializers = new WeakHashMap<Callback, CallbackThreadInitializer>(); /** * @param cb The {@link Callback} instance * @param initializer The {@link CallbackThreadInitializer} - if {@code null} then the * associated initializer instance is removed * @return The previous initializer instance (may be {@code null}) */ static CallbackThreadInitializer setCallbackThreadInitializer(Callback cb, CallbackThreadInitializer initializer) { synchronized(initializers) { if (initializer != null) { return initializers.put(cb, initializer); } else { return initializers.remove(cb); } } } static class AttachOptions extends Structure { public static final List<String> FIELDS = createFieldsOrder("daemon", "detach", "name"); public boolean daemon; public boolean detach; public String name; // Thread name must be UTF8-encoded { setStringEncoding("utf8"); } @Override protected List<String> getFieldOrder() { return FIELDS; } } /* Called from native code to initialize a callback thread. */ private static ThreadGroup initializeThread(Callback cb, AttachOptions args) { CallbackThreadInitializer init = null; if (cb instanceof DefaultCallbackProxy) { cb = ((DefaultCallbackProxy)cb).getCallback(); } synchronized(initializers) { init = initializers.get(cb); } ThreadGroup group = null; if (init != null) { group = init.getThreadGroup(cb); args.name = init.getName(cb); args.daemon = init.isDaemon(cb); args.detach = init.detach(cb); args.write(); } return group; } /** Return a Callback associated with the given function pointer. * If the pointer refers to a Java callback trampoline, return the original * Java Callback. Otherwise, return a proxy to the native function * pointer. * @throws IllegalStateException if the given pointer has already been * mapped to a callback of a different type. */ public static Callback getCallback(Class<?> type, Pointer p) { return getCallback(type, p, false); } private static Callback getCallback(Class<?> type, Pointer p, boolean direct) { if (p == null) { return null; } if (!type.isInterface()) throw new IllegalArgumentException("Callback type must be an interface"); Map<Callback, CallbackReference> map = direct ? directCallbackMap : callbackMap; synchronized(pointerCallbackMap) { Callback cb = null; Reference<Callback> ref = pointerCallbackMap.get(p); if (ref != null) { cb = ref.get(); if (cb != null && !type.isAssignableFrom(cb.getClass())) { throw new IllegalStateException("Pointer " + p + " already mapped to " + cb + ".\nNative code may be re-using a default function pointer" + ", in which case you may need to use a common Callback class" + " wherever the function pointer is reused."); } return cb; } int ctype = AltCallingConvention.class.isAssignableFrom(type) ? Function.ALT_CONVENTION : Function.C_CONVENTION; Map<String, Object> foptions = new HashMap<String, Object>(Native.getLibraryOptions(type)); foptions.put(Function.OPTION_INVOKING_METHOD, getCallbackMethod(type)); NativeFunctionHandler h = new NativeFunctionHandler(p, ctype, foptions); cb = (Callback)Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, h); // No CallbackReference for this callback map.remove(cb); pointerCallbackMap.put(p, new WeakReference<Callback>(cb)); return cb; } } Pointer cbstruct; Pointer trampoline; // Keep a reference to the proxy to avoid premature GC of it CallbackProxy proxy; Method method; int callingConvention; private CallbackReference(Callback callback, int callingConvention, boolean direct) { super(callback); TypeMapper mapper = Native.getTypeMapper(callback.getClass()); this.callingConvention = callingConvention; Class<?>[] nativeParamTypes; Class<?> returnType; // Check whether direct mapping may be used, or whether // we need to fall back to conventional mapping boolean ppc = Platform.isPPC(); if (direct) { Method m = getCallbackMethod(callback); Class<?>[] ptypes = m.getParameterTypes(); for (int i=0;i < ptypes.length;i++) { // varargs w/FP args via ffi_call fails on ppc (darwin) if (ppc && (ptypes[i] == float.class || ptypes[i] == double.class)) { direct = false; break; } // Direct mode callbacks do not support TypeMapper if (mapper != null && mapper.getFromNativeConverter(ptypes[i]) != null) { direct = false; break; } } // Direct mode callbacks do not support TypeMapper if (mapper != null && mapper.getToNativeConverter(m.getReturnType()) != null) { direct = false; } } String encoding = Native.getStringEncoding(callback.getClass()); long peer = 0; if (direct) { method = getCallbackMethod(callback); nativeParamTypes = method.getParameterTypes(); returnType = method.getReturnType(); int flags = Native.CB_OPTION_DIRECT; if (callback instanceof DLLCallback) { flags |= Native.CB_OPTION_IN_DLL; } peer = Native.createNativeCallback(callback, method, nativeParamTypes, returnType, callingConvention, flags, encoding); } else { if (callback instanceof CallbackProxy) { proxy = (CallbackProxy)callback; } else { proxy = new DefaultCallbackProxy(getCallbackMethod(callback), mapper, encoding); } nativeParamTypes = proxy.getParameterTypes(); returnType = proxy.getReturnType(); // Generate a list of parameter types that the native code can // handle. Let the CallbackProxy do any further conversion // to match the true Java callback method signature if (mapper != null) { for (int i=0;i < nativeParamTypes.length;i++) { FromNativeConverter rc = mapper.getFromNativeConverter(nativeParamTypes[i]); if (rc != null) { nativeParamTypes[i] = rc.nativeType(); } } ToNativeConverter tn = mapper.getToNativeConverter(returnType); if (tn != null) { returnType = tn.nativeType(); } } for (int i=0;i < nativeParamTypes.length;i++) { nativeParamTypes[i] = getNativeType(nativeParamTypes[i]); if (!isAllowableNativeType(nativeParamTypes[i])) { String msg = "Callback argument " + nativeParamTypes[i] + " requires custom type conversion"; throw new IllegalArgumentException(msg); } } returnType = getNativeType(returnType); if (!isAllowableNativeType(returnType)) { String msg = "Callback return type " + returnType + " requires custom type conversion"; throw new IllegalArgumentException(msg); } int flags = callback instanceof DLLCallback ? Native.CB_OPTION_IN_DLL : 0; peer = Native.createNativeCallback(proxy, PROXY_CALLBACK_METHOD, nativeParamTypes, returnType, callingConvention, flags, encoding); } cbstruct = peer != 0 ? new Pointer(peer) : null; allocatedMemory.put(this, new WeakReference<CallbackReference>(this)); } private Class<?> getNativeType(Class<?> cls) { if (Structure.class.isAssignableFrom(cls)) { // Make sure we can instantiate an argument of this type Structure.validate(cls); if (!Structure.ByValue.class.isAssignableFrom(cls)) return Pointer.class; } else if (NativeMapped.class.isAssignableFrom(cls)) { return NativeMappedConverter.getInstance(cls).nativeType(); } else if (cls == String.class || cls == WString.class || cls == String[].class || cls == WString[].class || Callback.class.isAssignableFrom(cls)) { return Pointer.class; } return cls; } private static Method checkMethod(Method m) { if (m.getParameterTypes().length > Function.MAX_NARGS) { String msg = "Method signature exceeds the maximum " + "parameter count: " + m; throw new UnsupportedOperationException(msg); } return m; } /* * Find the first instance of an interface which implements the Callback * interface or an interface derived from Callback, which defines an * appropriate callback method. */ static Class<?> findCallbackClass(Class<?> type) { if (!Callback.class.isAssignableFrom(type)) { throw new IllegalArgumentException(type.getName() + " is not derived from com.sun.jna.Callback"); } if (type.isInterface()) { return type; } Class<?>[] ifaces = type.getInterfaces(); for (int i=0;i < ifaces.length;i++) { if (Callback.class.isAssignableFrom(ifaces[i])) { try { // Make sure it's got a recognizable callback method getCallbackMethod(ifaces[i]); return ifaces[i]; } catch(IllegalArgumentException e) { break; } } } if (Callback.class.isAssignableFrom(type.getSuperclass())) { return findCallbackClass(type.getSuperclass()); } return type; } private static Method getCallbackMethod(Callback callback) { return getCallbackMethod(findCallbackClass(callback.getClass())); } private static Method getCallbackMethod(Class<?> cls) { // Look at only public methods defined by the Callback class Method[] pubMethods = cls.getDeclaredMethods(); Method[] classMethods = cls.getMethods(); Set<Method> pmethods = new HashSet<Method>(Arrays.asList(pubMethods)); pmethods.retainAll(Arrays.asList(classMethods)); // Remove Object methods disallowed as callback method names for (Iterator<Method> i=pmethods.iterator();i.hasNext();) { Method m = i.next(); if (Callback.FORBIDDEN_NAMES.contains(m.getName())) { i.remove(); } } Method[] methods = pmethods.toArray(new Method[pmethods.size()]); if (methods.length == 1) { return checkMethod(methods[0]); } for (int i=0;i < methods.length;i++) { Method m = methods[i]; if (Callback.METHOD_NAME.equals(m.getName())) { return checkMethod(m); } } String msg = "Callback must implement a single public method, " + "or one public method named '" + Callback.METHOD_NAME + "'"; throw new IllegalArgumentException(msg); } /** Set the behavioral options for this callback. */ private void setCallbackOptions(int options) { cbstruct.setInt(Pointer.SIZE, options); } /** Obtain a pointer to the native glue code for this callback. */ public Pointer getTrampoline() { if (trampoline == null) { trampoline = cbstruct.getPointer(0); } return trampoline; } /** Free native resources associated with this callback when GC'd. */ @Override protected void finalize() { dispose(); } /** Free native resources associated with this callback. */ protected synchronized void dispose() { if (cbstruct != null) { try { Native.freeNativeCallback(cbstruct.peer); } finally { cbstruct.peer = 0; cbstruct = null; allocatedMemory.remove(this); } } } /** Dispose of all memory allocated for callbacks. */ static void disposeAll() { // use a copy since dispose() modifes the map Collection<CallbackReference> refs = new LinkedList<CallbackReference>(allocatedMemory.keySet()); for (CallbackReference r : refs) { r.dispose(); } } private Callback getCallback() { return get(); } /** If the callback is one we generated to wrap a native function pointer, return that. Otherwise return null. */ private static Pointer getNativeFunctionPointer(Callback cb) { if (Proxy.isProxyClass(cb.getClass())) { Object handler = Proxy.getInvocationHandler(cb); if (handler instanceof NativeFunctionHandler) { return ((NativeFunctionHandler)handler).getPointer(); } } return null; } /** Return a {@link Pointer} to the native function address for the * given callback. */ public static Pointer getFunctionPointer(Callback cb) { return getFunctionPointer(cb, false); } /** Native code may call this method with direct=true. */ private static Pointer getFunctionPointer(Callback cb, boolean direct) { Pointer fp = null; if (cb == null) { return null; } if ((fp = getNativeFunctionPointer(cb)) != null) { return fp; } Map<String, ?> options = Native.getLibraryOptions(cb.getClass()); int callingConvention = cb instanceof AltCallingConvention ? Function.ALT_CONVENTION : (options != null && options.containsKey(Library.OPTION_CALLING_CONVENTION) ? ((Integer)options.get(Library.OPTION_CALLING_CONVENTION)).intValue() : Function.C_CONVENTION); Map<Callback, CallbackReference> map = direct ? directCallbackMap : callbackMap; synchronized(pointerCallbackMap) { CallbackReference cbref = map.get(cb); if (cbref == null) { cbref = new CallbackReference(cb, callingConvention, direct); map.put(cb, cbref); pointerCallbackMap.put(cbref.getTrampoline(), new WeakReference<Callback>(cb)); if (initializers.containsKey(cb)) { cbref.setCallbackOptions(Native.CB_HAS_INITIALIZER); } } return cbref.getTrampoline(); } } private class DefaultCallbackProxy implements CallbackProxy { private final Method callbackMethod; private ToNativeConverter toNative; private final FromNativeConverter[] fromNative; private final String encoding; public DefaultCallbackProxy(Method callbackMethod, TypeMapper mapper, String encoding) { this.callbackMethod = callbackMethod; this.encoding = encoding; Class<?>[] argTypes = callbackMethod.getParameterTypes(); Class<?> returnType = callbackMethod.getReturnType(); fromNative = new FromNativeConverter[argTypes.length]; if (NativeMapped.class.isAssignableFrom(returnType)) { toNative = NativeMappedConverter.getInstance(returnType); } else if (mapper != null) { toNative = mapper.getToNativeConverter(returnType); } for (int i=0;i < fromNative.length;i++) { if (NativeMapped.class.isAssignableFrom(argTypes[i])) { fromNative[i] = new NativeMappedConverter(argTypes[i]); } else if (mapper != null) { fromNative[i] = mapper.getFromNativeConverter(argTypes[i]); } } if (!callbackMethod.isAccessible()) { try { callbackMethod.setAccessible(true); } catch(SecurityException e) { throw new IllegalArgumentException("Callback method is inaccessible, make sure the interface is public: " + callbackMethod); } } } public Callback getCallback() { return CallbackReference.this.getCallback(); } private Object invokeCallback(Object[] args) { Class<?>[] paramTypes = callbackMethod.getParameterTypes(); Object[] callbackArgs = new Object[args.length]; // convert basic supported types to appropriate Java parameter types for (int i=0;i < args.length;i++) { Class<?> type = paramTypes[i]; Object arg = args[i]; if (fromNative[i] != null) { FromNativeContext context = new CallbackParameterContext(type, callbackMethod, args, i); callbackArgs[i] = fromNative[i].fromNative(arg, context); } else { callbackArgs[i] = convertArgument(arg, type); } } Object result = null; Callback cb = DefaultCallbackProxy.this.getCallback(); if (cb != null) { try { result = convertResult(callbackMethod.invoke(cb, callbackArgs)); } catch (IllegalArgumentException e) { Native.getCallbackExceptionHandler().uncaughtException(cb, e); } catch (IllegalAccessException e) { Native.getCallbackExceptionHandler().uncaughtException(cb, e); } catch (InvocationTargetException e) { Native.getCallbackExceptionHandler().uncaughtException(cb, e.getTargetException()); } } // Synch any structure arguments back to native memory for (int i=0;i < callbackArgs.length;i++) { if (callbackArgs[i] instanceof Structure && !(callbackArgs[i] instanceof Structure.ByValue)) { ((Structure)callbackArgs[i]).autoWrite(); } } return result; } /** Called from native code. All arguments are in an array of * Object as the first argument. Converts all arguments to types * required by the actual callback method signature, and converts * the result back into an appropriate native type. * This method <em>must not</em> throw exceptions. */ @Override public Object callback(Object[] args) { try { return invokeCallback(args); } catch (Throwable t) { Native.getCallbackExceptionHandler().uncaughtException(getCallback(), t); return null; } } /** Convert argument from its basic native type to the given * Java parameter type. */ private Object convertArgument(Object value, Class<?> dstType) { if (value instanceof Pointer) { if (dstType == String.class) { value = ((Pointer)value).getString(0, encoding); } else if (dstType == WString.class) { value = new WString(((Pointer)value).getWideString(0)); } else if (dstType == String[].class) { value = ((Pointer)value).getStringArray(0, encoding); } else if (dstType == WString[].class) { value = ((Pointer)value).getWideStringArray(0); } else if (Callback.class.isAssignableFrom(dstType)) { value = CallbackReference.getCallback(dstType, (Pointer)value); } else if (Structure.class.isAssignableFrom(dstType)) { // If passed by value, don't hold onto the pointer, which // is only valid for the duration of the callback call if (Structure.ByValue.class.isAssignableFrom(dstType)) { Structure s = Structure.newInstance(dstType); byte[] buf = new byte[s.size()]; ((Pointer)value).read(0, buf, 0, buf.length); s.getPointer().write(0, buf, 0, buf.length); s.read(); value = s; } else { Structure s = Structure.newInstance(dstType, (Pointer)value); s.conditionalAutoRead(); value = s; } } } else if ((boolean.class == dstType || Boolean.class == dstType) && value instanceof Number) { value = Function.valueOf(((Number)value).intValue() != 0); } return value; } private Object convertResult(Object value) { if (toNative != null) { value = toNative.toNative(value, new CallbackResultContext(callbackMethod)); } if (value == null) { return null; } Class<?> cls = value.getClass(); if (Structure.class.isAssignableFrom(cls)) { if (Structure.ByValue.class.isAssignableFrom(cls)) { return value; } return ((Structure)value).getPointer(); } else if (cls == boolean.class || cls == Boolean.class) { return Boolean.TRUE.equals(value) ? Function.INTEGER_TRUE : Function.INTEGER_FALSE; } else if (cls == String.class || cls == WString.class) { return getNativeString(value, cls == WString.class); } else if (cls == String[].class || cls == WString.class) { StringArray sa = cls == String[].class ? new StringArray((String[])value, encoding) : new StringArray((WString[])value); // Delay GC until array itself is GC'd. allocations.put(value, sa); return sa; } else if (Callback.class.isAssignableFrom(cls)) { return getFunctionPointer((Callback)value); } return value; } @Override public Class<?>[] getParameterTypes() { return callbackMethod.getParameterTypes(); } @Override public Class<?> getReturnType() { return callbackMethod.getReturnType(); } } /** Provide invocation handling for an auto-generated Java interface proxy * for a native function pointer. * Cf. Library.Handler */ private static class NativeFunctionHandler implements InvocationHandler { private final Function function; private final Map<String, ?> options; public NativeFunctionHandler(Pointer address, int callingConvention, Map<String, ?> options) { this.options = options; this.function = new Function(address, callingConvention, (String) options.get(Library.OPTION_STRING_ENCODING)); } /** Chain invocation to the native function. */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Library.Handler.OBJECT_TOSTRING.equals(method)) { String str = "Proxy interface to " + function; Method m = (Method)options.get(Function.OPTION_INVOKING_METHOD); Class<?> cls = findCallbackClass(m.getDeclaringClass()); str += " (" + cls.getName() + ")"; return str; } else if (Library.Handler.OBJECT_HASHCODE.equals(method)) { return Integer.valueOf(hashCode()); } else if (Library.Handler.OBJECT_EQUALS.equals(method)) { Object o = args[0]; if (o != null && Proxy.isProxyClass(o.getClass())) { return Function.valueOf(Proxy.getInvocationHandler(o) == this); } return Boolean.FALSE; } if (Function.isVarArgs(method)) { args = Function.concatenateVarArgs(args); } return function.invoke(method.getReturnType(), args, options); } public Pointer getPointer() { return function; } } /** Returns whether the given class is supported in native code. * Other types (String, WString, Structure, arrays, NativeMapped, * etc) are supported in the Java library. */ private static boolean isAllowableNativeType(Class<?> cls) { return cls == void.class || cls == Void.class || cls == boolean.class || cls == Boolean.class || cls == byte.class || cls == Byte.class || cls == short.class || cls == Short.class || cls == char.class || cls == Character.class || cls == int.class || cls == Integer.class || cls == long.class || cls == Long.class || cls == float.class || cls == Float.class || cls == double.class || cls == Double.class || (Structure.ByValue.class.isAssignableFrom(cls) && Structure.class.isAssignableFrom(cls)) || Pointer.class.isAssignableFrom(cls); } private static Pointer getNativeString(Object value, boolean wide) { if (value != null) { NativeString ns = new NativeString(value.toString(), wide); // Delay GC until string itself is GC'd. allocations.put(value, ns); return ns.getPointer(); } return null; } }