org.jruby.ext.ffi.jffi.CallbackManager Maven / Gradle / Ivy
package org.jruby.ext.ffi.jffi;
import com.kenai.jffi.CallingConvention;
import com.kenai.jffi.Closure;
import com.kenai.jffi.ClosureManager;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubyProc;
import org.jruby.anno.JRubyClass;
import org.jruby.ext.ffi.AbstractInvoker;
import org.jruby.ext.ffi.AllocatedDirectMemoryIO;
import org.jruby.ext.ffi.ArrayMemoryIO;
import org.jruby.ext.ffi.CallbackInfo;
import org.jruby.ext.ffi.DirectMemoryIO;
import org.jruby.ext.ffi.InvalidMemoryIO;
import org.jruby.ext.ffi.MappedType;
import org.jruby.ext.ffi.MemoryIO;
import org.jruby.ext.ffi.NullMemoryIO;
import org.jruby.ext.ffi.Platform;
import org.jruby.ext.ffi.Pointer;
import org.jruby.ext.ffi.Struct;
import org.jruby.ext.ffi.StructByValue;
import org.jruby.ext.ffi.Type;
import org.jruby.ext.ffi.Util;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
/**
* Manages Callback instances for the low level FFI backend.
*/
public class CallbackManager extends org.jruby.ext.ffi.CallbackManager {
private static final int LONG_SIZE = Platform.getPlatform().longSize();
private static final String CALLBACK_ID = "ffi_callback";
/** Holder for the single instance of CallbackManager */
private static final class SingletonHolder {
static final CallbackManager INSTANCE = new CallbackManager();
}
/**
* Gets the singleton instance of CallbackManager
*/
public static final CallbackManager getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* Creates a Callback class for a ruby runtime
*
* @param runtime The runtime to create the class for
* @param module The module to place the class in
*
* @return The newly created ruby class
*/
public static RubyClass createCallbackClass(Ruby runtime, RubyModule module) {
RubyClass cbClass = module.defineClassUnder("Callback", module.fastGetClass("Pointer"),
ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
cbClass.defineAnnotatedMethods(Callback.class);
cbClass.defineAnnotatedConstants(Callback.class);
return cbClass;
}
public final org.jruby.ext.ffi.Pointer getCallback(Ruby runtime, CallbackInfo cbInfo, Object proc) {
return proc instanceof RubyObject
? getCallback(runtime, cbInfo, (RubyObject) proc)
: newCallback(runtime, cbInfo, proc);
}
/**
* Gets a Callback object conforming to the signature contained in the
* CallbackInfo for the ruby Proc or Block instance.
*
* @param runtime The ruby runtime the callback is attached to
* @param cbInfo The signature of the native callback
* @param proc The ruby object to call when the callback is invoked.
* @return A native value returned to the native caller.
*/
public final org.jruby.ext.ffi.Pointer getCallback(Ruby runtime, CallbackInfo cbInfo, RubyObject proc) {
if (proc instanceof Function) {
return (Function) proc;
}
synchronized (proc) {
Object existing = proc.fastGetInternalVariable(CALLBACK_ID);
if (existing instanceof Callback && ((Callback) existing).cbInfo == cbInfo) {
return (Callback) existing;
} else if (existing instanceof Map) {
Map m = (Map) existing;
Callback cb = (Callback) m.get(proc);
if (cb != null) {
return cb;
}
}
Callback cb = newCallback(runtime, cbInfo, proc);
if (existing == null) {
((RubyObject) proc).fastSetInternalVariable(CALLBACK_ID, cb);
} else {
Map m = existing instanceof Map
? (Map) existing
: Collections.synchronizedMap(new WeakHashMap());
m.put(cbInfo, cb);
m.put(((Callback) existing).cbInfo, (Callback) existing);
((RubyObject) proc).fastSetInternalVariable(CALLBACK_ID, m);
}
return cb;
}
}
/**
* Gets a Callback object conforming to the signature contained in the
* CallbackInfo for the ruby Proc or Block instance.
*
* @param runtime The ruby runtime the callback is attached to
* @param cbInfo The signature of the native callback
* @param proc The ruby Block object to call when the callback is invoked.
* @return A native value returned to the native caller.
*/
final Callback getCallback(Ruby runtime, CallbackInfo cbInfo, Block proc) {
return newCallback(runtime, cbInfo, proc);
}
private final Callback newCallback(Ruby runtime, CallbackInfo cbInfo, Object proc) {
ClosureInfo info = getClosureInfo(runtime, cbInfo);
WeakRefCallbackProxy cbProxy = new WeakRefCallbackProxy(runtime, info, proc);
Closure.Handle handle = ClosureManager.getInstance().newClosure(cbProxy, info.callContext);
return new Callback(runtime, handle, cbInfo, info);
}
private final ClosureInfo getClosureInfo(Ruby runtime, CallbackInfo cbInfo) {
Object info = cbInfo.getProviderCallbackInfo();
if (info != null && info instanceof ClosureInfo) {
return (ClosureInfo) info;
}
cbInfo.setProviderCallbackInfo(info = newClosureInfo(runtime, cbInfo));
return (ClosureInfo) info;
}
private final ClosureInfo newClosureInfo(Ruby runtime, CallbackInfo cbInfo) {
return new ClosureInfo(runtime, cbInfo.getReturnType(), cbInfo.getParameterTypes(),
cbInfo.isStdcall() ? CallingConvention.STDCALL : CallingConvention.DEFAULT);
}
/**
*/
final CallbackMemoryIO newClosure(Ruby runtime, Type returnType, Type[] parameterTypes,
Object proc, CallingConvention convention) {
ClosureInfo info = new ClosureInfo(runtime, returnType, parameterTypes, convention);
final CallbackProxy cbProxy = new CallbackProxy(runtime, info, proc);
final Closure.Handle handle = ClosureManager.getInstance().newClosure(cbProxy, info.callContext);
return new CallbackMemoryIO(runtime, handle);
}
/**
* Holds the JFFI return type and parameter types to avoid
*/
private static class ClosureInfo {
final CallingConvention convention;
final Type returnType;
final Type[] parameterTypes;
final com.kenai.jffi.Type ffiReturnType;
final com.kenai.jffi.Type[] ffiParameterTypes;
final com.kenai.jffi.CallContext callContext;
public ClosureInfo(Ruby runtime, Type returnType, Type[] paramTypes, CallingConvention convention) {
this.convention = convention;
this.ffiParameterTypes = new com.kenai.jffi.Type[paramTypes.length];
for (int i = 0; i < paramTypes.length; ++i) {
if (!isParameterTypeValid(paramTypes[i]) || (ffiParameterTypes[i] = FFIUtil.getFFIType(paramTypes[i])) == null) {
throw runtime.newTypeError("invalid callback parameter type: " + paramTypes[i]);
}
}
ffiReturnType = FFIUtil.getFFIType(returnType);
if (!isReturnTypeValid(returnType) || ffiReturnType == null) {
runtime.newTypeError("invalid callback return type: " + returnType);
}
this.callContext = new com.kenai.jffi.CallContext(ffiReturnType, ffiParameterTypes, convention);
this.returnType = returnType;
this.parameterTypes = (Type[]) paramTypes.clone();
}
}
/**
* Wrapper around the native callback, to represent it as a ruby object
*/
@JRubyClass(name = "FFI::Callback", parent = "FFI::Pointer")
static class Callback extends AbstractInvoker {
private final CallbackInfo cbInfo;
private final ClosureInfo closureInfo;
Callback(Ruby runtime, Closure.Handle handle, CallbackInfo cbInfo, ClosureInfo closureInfo) {
super(runtime, runtime.fastGetModule("FFI").fastGetClass("Callback"),
cbInfo.getParameterTypes().length, new CallbackMemoryIO(runtime, handle));
this.cbInfo = cbInfo;
this.closureInfo = closureInfo;
}
void dispose() {
MemoryIO mem = getMemoryIO();
if (mem instanceof CallbackMemoryIO) {
((CallbackMemoryIO) mem).free();
}
}
@Override
public DynamicMethod createDynamicMethod(RubyModule module) {
com.kenai.jffi.Function function = new com.kenai.jffi.Function(((DirectMemoryIO) getMemoryIO()).getAddress(),
closureInfo.ffiReturnType, closureInfo.ffiParameterTypes);
return MethodFactory.createDynamicMethod(getRuntime(), module, function,
closureInfo.returnType, closureInfo.parameterTypes, closureInfo.convention, getRuntime().getNil());
}
}
/**
* Wraps a ruby proc in a JFFI Closure
*/
private static abstract class AbstractCallbackProxy implements Closure {
protected final Ruby runtime;
protected final ClosureInfo closureInfo;
AbstractCallbackProxy(Ruby runtime, ClosureInfo closureInfo) {
this.runtime = runtime;
this.closureInfo = closureInfo;
}
protected final void invoke(Closure.Buffer buffer, Object recv) {
ThreadContext context = runtime.getCurrentContext();
IRubyObject[] params = new IRubyObject[closureInfo.parameterTypes.length];
for (int i = 0; i < params.length; ++i) {
params[i] = fromNative(runtime, closureInfo.parameterTypes[i], buffer, i);
}
IRubyObject retVal;
if (recv instanceof RubyProc) {
retVal = ((RubyProc) recv).call(context, params);
} else if (recv instanceof Block) {
retVal = ((Block) recv).call(context, params);
} else {
retVal = ((IRubyObject) recv).callMethod(context, "call", params);
}
setReturnValue(runtime, closureInfo.returnType, buffer, retVal);
}
}
/**
* Wraps a ruby proc in a JFFI Closure
*/
private static final class WeakRefCallbackProxy extends AbstractCallbackProxy implements Closure {
private final WeakReference
© 2015 - 2025 Weber Informatics LLC | Privacy Policy