com.sun.jna.CallbackReference Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of platform Show documentation
Show all versions of platform Show documentation
Java Native Access Platform
/* Copyright (c) 2007-2008 Timothy Wall, All Rights Reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package com.sun.jna;
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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/** Provides a reference to an association between a native callback closure
* and a Java {@link Callback} closure.
*/
class CallbackReference extends WeakReference {
static final Map callbackMap = new WeakHashMap();
static final Map allocations = new WeakHashMap();
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 initializers = new WeakHashMap();
static void setCallbackThreadInitializer(Callback cb, CallbackThreadInitializer initializer) {
synchronized(callbackMap) {
if (initializer != null) {
initializers.put(cb, initializer);
}
else {
initializers.remove(cb);
}
}
}
static class AttachOptions extends Structure {
public boolean daemon;
public boolean detach;
public String name;
}
/** 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 = (CallbackThreadInitializer)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.
*/
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 map = callbackMap;
synchronized(map) {
for (Iterator i=map.keySet().iterator();i.hasNext();) {
Callback cb = (Callback)i.next();
if (type.isAssignableFrom(cb.getClass())) {
CallbackReference cbref = (CallbackReference)map.get(cb);
Pointer cbp = cbref != null
? cbref.getTrampoline() : getNativeFunctionPointer(cb);
if (p.equals(cbp)) {
return cb;
}
}
}
int ctype = AltCallingConvention.class.isAssignableFrom(type)
? Function.ALT_CONVENTION : Function.C_CONVENTION;
Map foptions = new HashMap();
Map options = Native.getLibraryOptions(type);
if (options != null) {
foptions.putAll(options);
}
foptions.put(Function.OPTION_INVOKING_METHOD, getCallbackMethod(type));
NativeFunctionHandler h = new NativeFunctionHandler(p, ctype, foptions);
Callback cb = (Callback)Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, h);
map.put(cb, null);
return cb;
}
}
Pointer cbstruct;
// Keep a reference to the proxy to avoid premature GC of it
CallbackProxy proxy;
Method method;
private CallbackReference(Callback callback, int callingConvention, boolean direct) {
super(callback);
TypeMapper mapper = Native.getTypeMapper(callback.getClass());
Class[] nativeParamTypes;
Class returnType;
// Check whether direct mapping may be used, or whether
// we need to fall back to conventional mapping
String arch = System.getProperty("os.arch").toLowerCase();
boolean ppc = "ppc".equals(arch) || "powerpc".equals(arch);
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;
}
// No TypeMapper support in native callback code
if (mapper != null
&& mapper.getFromNativeConverter(ptypes[i]) != null) {
direct = false;
break;
}
}
if (mapper != null
&& mapper.getToNativeConverter(m.getReturnType()) != null) {
direct = false;
}
}
if (direct) {
method = getCallbackMethod(callback);
nativeParamTypes = method.getParameterTypes();
returnType = method.getReturnType();
long peer = Native.createNativeCallback(callback, method,
nativeParamTypes, returnType,
callingConvention, true);
cbstruct = peer != 0 ? new Pointer(peer) : null;
}
else {
if (callback instanceof CallbackProxy) {
proxy = (CallbackProxy)callback;
}
else {
proxy = new DefaultCallbackProxy(getCallbackMethod(callback), mapper);
}
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);
}
long peer = Native.createNativeCallback(proxy, PROXY_CALLBACK_METHOD,
nativeParamTypes, returnType,
callingConvention, false);
cbstruct = peer != 0 ? new Pointer(peer) : null;
}
}
private Class getNativeType(Class cls) {
if (Structure.class.isAssignableFrom(cls)) {
// Make sure we can instantiate an argument of this type
Structure.newInstance(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 pmethods = new HashSet(Arrays.asList(pubMethods));
pmethods.retainAll(Arrays.asList(classMethods));
// Remove Object methods disallowed as callback method names
for (Iterator i=pmethods.iterator();i.hasNext();) {
Method m = (Method)i.next();
if (Callback.FORBIDDEN_NAMES.contains(m.getName())) {
i.remove();
}
}
Method[] methods = (Method[])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() {
return cbstruct.getPointer(0);
}
/** Free native resources associated with this callback when GC'd. */
protected void finalize() {
dispose();
}
/** Free native resources associated with this callback. */
protected synchronized void dispose() {
if (cbstruct != null) {
Native.freeNativeCallback(cbstruct.peer);
cbstruct.peer = 0;
cbstruct = null;
}
}
private Callback getCallback() {
return (Callback)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;
}
int callingConvention = cb instanceof AltCallingConvention
? Function.ALT_CONVENTION : Function.C_CONVENTION;
Map map = callbackMap;
synchronized(map) {
CallbackReference cbref = (CallbackReference)map.get(cb);
if (cbref == null) {
cbref = new CallbackReference(cb, callingConvention, direct);
map.put(cb, cbref);
if (initializers.containsKey(cb)) {
cbref.setCallbackOptions(Native.CB_HAS_INITIALIZER);
}
}
return cbref.getTrampoline();
}
}
private class DefaultCallbackProxy implements CallbackProxy {
private Method callbackMethod;
private ToNativeConverter toNative;
private FromNativeConverter[] fromNative;
public DefaultCallbackProxy(Method callbackMethod, TypeMapper mapper) {
this.callbackMethod = callbackMethod;
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 must not throw exceptions.
*/
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);
}
else if (dstType == WString.class) {
value = new WString(((Pointer)value).getString(0, true));
}
else if (dstType == String[].class
|| dstType == WString[].class) {
value = ((Pointer)value).getStringArray(0, dstType == WString[].class);
}
else if (Callback.class.isAssignableFrom(dstType)) {
value = CallbackReference.this.getCallback(dstType, (Pointer)value);
}
else if (Structure.class.isAssignableFrom(dstType)) {
Structure s = Structure.newInstance(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)) {
byte[] buf = new byte[s.size()];
((Pointer)value).read(0, buf, 0, buf.length);
s.getPointer().write(0, buf, 0, buf.length);
}
else {
s.useMemory((Pointer)value);
}
s.read();
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)
: 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;
}
public Class[] getParameterTypes() {
return callbackMethod.getParameterTypes();
}
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 Function function;
private Map options;
public NativeFunctionHandler(Pointer address, int callingConvention, Map options) {
this.function = new Function(address, callingConvention);
this.options = options;
}
/** Chain invocation to the native function. */
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 new Integer(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;
}
}