diff --git a/rococoa/rococoa-core/src/main/java/org/rococoa/Foundation.java b/rococoa/rococoa-core/src/main/java/org/rococoa/Foundation.java index c5d6f278..b8f04e20 100644 --- a/rococoa/rococoa-core/src/main/java/org/rococoa/Foundation.java +++ b/rococoa/rococoa-core/src/main/java/org/rococoa/Foundation.java @@ -24,6 +24,7 @@ import org.rococoa.cocoa.CFIndex; import org.rococoa.internal.*; +import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -171,26 +172,39 @@ public static Selector selector(String selectorName) { return result; } - /** - * Send message with selectorName to receiver, passing args, expecting returnType. - *

- * Note that you are responsible for memory management if returnType is ID. - */ + @SuppressWarnings("unchecked") public static T send(ID receiver, String selectorName, Class returnType, Object... args) { - return send(receiver, selector(selectorName), returnType, args); + return send(receiver, selectorName, returnType, null, args); + } + + @SuppressWarnings("unchecked") + public static T send(ID receiver, String selectorName, Class returnType, Method method, Object... args) { + return send(receiver, selector(selectorName), returnType, method, args); + } + + @SuppressWarnings("unchecked") + public static T send(ID receiver, Selector selector, Class returnType, Object... args) { + return send(receiver, selector, returnType, null, args); } /** * Send message with selector to receiver, passing args, expecting returnType. *

* Note that you are responsible for memory management if returnType is ID. + * + * @param returnType Expected return type mapping + * @param method Used to determine if variadic function call is required + * @param args Arguments including ID and selector */ @SuppressWarnings("unchecked") - public static T send(ID receiver, Selector selector, Class returnType, Object... args) { + public static T send(ID receiver, Selector selector, Class returnType, Method method, Object... args) { if (logging.isLoggable(Level.FINEST)) { logging.finest(String.format("sending (%s) %s.%s(%s)", returnType.getSimpleName(), receiver, selector.getName(), new VarArgsUnpacker(args))); } + if (method != null && method.isVarArgs()) { + return (T) messageSendLibrary.syntheticSendVarArgsMessage(returnType, receiver, selector, args); + } return (T) messageSendLibrary.syntheticSendMessage(returnType, receiver, selector, args); } diff --git a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/Pair.java b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MethodFunctionPair.java similarity index 73% rename from rococoa/rococoa-core/src/main/java/org/rococoa/internal/Pair.java rename to rococoa/rococoa-core/src/main/java/org/rococoa/internal/MethodFunctionPair.java index 90c550b8..a2d594fd 100644 --- a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/Pair.java +++ b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MethodFunctionPair.java @@ -19,13 +19,17 @@ package org.rococoa.internal; -public class Pair { +import com.sun.jna.Function; + +import java.lang.reflect.Method; + +public class MethodFunctionPair { - public final T1 a; - public final T2 b; + public final Method method; + public final Function function; - public Pair(T1 a, T2 b) { - this.a = a; - this.b = b; + public MethodFunctionPair(Method method, Function function) { + this.method = method; + this.function = function; } } \ No newline at end of file diff --git a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendHandler.java b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendHandler.java index 8154aff2..a12cbb8e 100644 --- a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendHandler.java +++ b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendHandler.java @@ -19,8 +19,8 @@ package org.rococoa.internal; -import com.sun.jna.Function; import com.sun.jna.Library; +import com.sun.jna.NativeLibrary; import com.sun.jna.NativeLong; import com.sun.jna.Structure; import org.rococoa.ID; @@ -28,10 +28,12 @@ import org.rococoa.Selector; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Optional; /** * Very special case InvocationHandler that invokes the correct message dispatch @@ -63,7 +65,6 @@ class MsgSendHandler implements InvocationHandler { * @see com.sun.jna.Function#OPTION_INVOKING_METHOD */ private final String OPTION_INVOKING_METHOD = "invoking-method"; - // TODO - use JNA string when made public private final static int I386_STRET_CUTOFF = 9; private final static int IA64_STRET_CUTOFF = 17; @@ -73,13 +74,15 @@ class MsgSendHandler implements InvocationHandler { public final static boolean AARCH64 = System.getProperty("os.arch").trim().equalsIgnoreCase("aarch64"); public final static boolean PPC = System.getProperty("os.arch").trim().equalsIgnoreCase("ppc"); - private final static Method OBJC_MSGSEND; + private final static Method OBJC_MSGSEND_FIXED_ARGS; + private final static Method OBJC_MSGSEND_VAR_ARGS; private final static Method OBJC_MSGSEND_STRET; static { try { - OBJC_MSGSEND = MsgSendLibrary.class.getDeclaredMethod("objc_msgSend", - ID.class, Selector.class, Object[].class); + OBJC_MSGSEND_FIXED_ARGS = null; + OBJC_MSGSEND_VAR_ARGS = MsgSendLibrary.class.getDeclaredMethod("objc_msgSend", + ID.class, Selector.class, Object.class, Object[].class); OBJC_MSGSEND_STRET = MsgSendLibrary.class.getDeclaredMethod("objc_msgSend_stret", ID.class, Selector.class, Object[].class); } catch (NoSuchMethodException x) { @@ -87,42 +90,40 @@ class MsgSendHandler implements InvocationHandler { } } - private final Pair objc_msgSend_stret_Pair; - private final Pair objc_msgSend_Pair; + private final MethodFunctionPair objc_msgSend_stret_Pair; + private final MethodFunctionPair objc_msgSend_varArgs_Pair; + private final MethodFunctionPair objc_msgSend_fixedArgs_Pair; private final RococoaTypeMapper rococoaTypeMapper = new RococoaTypeMapper(); - public MsgSendHandler(Optional objc_msgSend_Function, Optional objc_msgSend_stret_Function) { - this.objc_msgSend_Pair = new Pair<>(OBJC_MSGSEND, objc_msgSend_Function.orElse(null)); - this.objc_msgSend_stret_Pair = new Pair<>(OBJC_MSGSEND_STRET, objc_msgSend_stret_Function.orElse(null)); + public MsgSendHandler(final NativeLibrary lib) { + this.objc_msgSend_fixedArgs_Pair = new MethodFunctionPair(OBJC_MSGSEND_FIXED_ARGS, + lib.getFunction("objc_msgSend")); + this.objc_msgSend_varArgs_Pair = new MethodFunctionPair(OBJC_MSGSEND_VAR_ARGS, + lib.getFunction("objc_msgSend")); + this.objc_msgSend_stret_Pair = new MethodFunctionPair(OBJC_MSGSEND_STRET, + AARCH64 ? null : lib.getFunction("objc_msgSend_stret")); } - public Object invoke(Object proxy, Method method, Object[] args) { + public Object invoke(final Object proxy, final Method method, final Object[] args) { Class returnTypeForThisCall = (Class) args[0]; - Object[] argsWithoutReturnType = this.removeReturnTypeFrom(args); - - Map options = new HashMap<>(1); - options.put(Library.OPTION_TYPE_MAPPER, rococoaTypeMapper); - - Pair invocation = this.invocationFor(returnTypeForThisCall); - options.put(OPTION_INVOKING_METHOD, invocation.a); - return invocation.b.invoke(returnTypeForThisCall, argsWithoutReturnType, options); + MethodFunctionPair invocation = this.invocationFor(returnTypeForThisCall, MsgSendInvocationMapper.SYNTHETIC_SEND_VARARGS_MSG.equals(method)); + Map options = new HashMap<>(Collections.singletonMap(Library.OPTION_TYPE_MAPPER, rococoaTypeMapper)); + options.put(OPTION_INVOKING_METHOD, invocation.method); + return invocation.function.invoke(returnTypeForThisCall, Arrays.copyOfRange(args, 1, args.length), options); } - private Object[] removeReturnTypeFrom(Object[] args) { - Object[] result = new Object[args.length - 1]; - System.arraycopy(args, 1, result, 0, args.length - 2); - return result; - } - - private Pair invocationFor(Class returnTypeForThisCall) { + private MethodFunctionPair invocationFor(Class returnTypeForThisCall, boolean varArgs) { if (AARCH64) { - return objc_msgSend_Pair; + if (varArgs) { + return objc_msgSend_varArgs_Pair; + } + return objc_msgSend_fixedArgs_Pair; } boolean isStruct = Structure.class.isAssignableFrom(returnTypeForThisCall); boolean isStructByValue = isStruct && Structure.ByValue.class.isAssignableFrom(returnTypeForThisCall); if (!isStructByValue) { - return objc_msgSend_Pair; + return objc_msgSend_varArgs_Pair; } try { if (PPC) { @@ -130,9 +131,9 @@ private Pair invocationFor(Class returnTypeForThisCall) { return objc_msgSend_stret_Pair; } // on i386 structs with sizeof exactly equal to 1, 2, 4, or 8 return in registers - Structure prototype = (Structure) returnTypeForThisCall.newInstance(); - return prototype.size() < STRET_CUTOFF ? objc_msgSend_Pair : objc_msgSend_stret_Pair; - } catch (InstantiationException | IllegalAccessException e) { + Structure prototype = (Structure) returnTypeForThisCall.getDeclaredConstructor().newInstance(); + return prototype.size() < STRET_CUTOFF ? objc_msgSend_varArgs_Pair : objc_msgSend_stret_Pair; + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new RococoaException(e); } } diff --git a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendInvocationMapper.java b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendInvocationMapper.java index 841331fa..c330537f 100644 --- a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendInvocationMapper.java +++ b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendInvocationMapper.java @@ -17,53 +17,52 @@ * along with Rococoa. If not, see . */ -/** - * - */ package org.rococoa.internal; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.Optional; - +import com.sun.jna.InvocationMapper; +import com.sun.jna.NativeLibrary; import org.rococoa.ID; +import org.rococoa.RococoaException; import org.rococoa.Selector; -import com.sun.jna.InvocationMapper; -import com.sun.jna.NativeLibrary; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; /** * A JNA InvocationMapper that maps calls to syntheticSendMessage to a MsgSendHandler. - * + *

* This allows us to dispatch all calls to syntheticSendMessage and have MsgSendHandler * call objc_msgSend or objc_msgSend_stret as appropriate, casting the return * type appropriately. - * + * * @author duncan */ public class MsgSendInvocationMapper implements InvocationMapper { - private final static Method SYNTHETIC_SEND_MSG; + public final static Method SYNTHETIC_SEND_MSG; + public final static Method SYNTHETIC_SEND_VARARGS_MSG; static { try { - SYNTHETIC_SEND_MSG = MsgSendLibrary.class.getDeclaredMethod("syntheticSendMessage", + SYNTHETIC_SEND_MSG = MsgSendLibrary.class.getDeclaredMethod("syntheticSendMessage", Class.class, ID.class, Selector.class, Object[].class); + } catch (Exception e) { + throw new RococoaException("Error retrieving method"); } - catch (Exception e) { - throw new Error("Error retrieving method"); + try { + SYNTHETIC_SEND_VARARGS_MSG = MsgSendLibrary.class.getDeclaredMethod("syntheticSendVarArgsMessage", + Class.class, ID.class, Selector.class, Object[].class); + } catch (Exception e) { + throw new RococoaException("Error retrieving method"); } } - + public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) { - if (!m.equals(SYNTHETIC_SEND_MSG)) - return null; // default handler - - // Have to late bind this, as it's the only time we get to see lib. - // Not too bad as the results are cached. - return new MsgSendHandler( - Optional.of(lib.getFunction("objc_msgSend")), - MsgSendHandler.AARCH64 ? Optional.ofNullable(null) : Optional.of(lib.getFunction("objc_msgSend_stret"))); + if (m.equals(SYNTHETIC_SEND_MSG) || m.equals(SYNTHETIC_SEND_VARARGS_MSG)) { + // Have to late bind this, as it's the only time we get to see lib. + // Not too bad as the results are cached. + return new MsgSendHandler(lib); + } + return null; // default handler } - } \ No newline at end of file diff --git a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendLibrary.java b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendLibrary.java index 56db174b..80d16f5f 100644 --- a/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendLibrary.java +++ b/rococoa/rococoa-core/src/main/java/org/rococoa/internal/MsgSendLibrary.java @@ -32,8 +32,9 @@ public interface MsgSendLibrary extends Library { // This doesn't exist in the library, but is synthesised by msgSendHandler Object syntheticSendMessage(Class returnType, ID receiver, Selector selector, Object... args); - + Object syntheticSendVarArgsMessage(Class returnType, ID receiver, Selector selector, Object... args); + // We don't call these directly, but through syntheticSendMessage - Object objc_msgSend(ID receiver, Selector selector, Object... args); - Structure objc_msgSend_stret(ID receiver, Selector selector, Object... args); + Object objc_msgSend(ID receiver, Selector selector, Object arg, Object... args); + Structure objc_msgSend_stret(ID receiver, Selector selector, Object... args); } \ No newline at end of file