diff --git a/rococoa/pom.xml b/rococoa/pom.xml index a4eb2867..043e623c 100644 --- a/rococoa/pom.xml +++ b/rococoa/pom.xml @@ -145,7 +145,7 @@ net.java.dev.jna jna - 5.9.0 + 5.10.0 cglib @@ -167,7 +167,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.2.1 attach-sources @@ -177,25 +177,9 @@ - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - -Xdoclint:none - - - - maven-resources-plugin - 2.6 + 3.2.0 true UTF-8 @@ -305,7 +289,7 @@ maven-dependency-plugin - 3.1.2 + 3.2.0 org.codehaus.mojo 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 3eedacb1..b92521a1 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 @@ -1,13 +1,13 @@ /* * Copyright 2007, 2008 Duncan McGregor - * + * * This file is part of Rococoa, a library to allow Java to talk to Cocoa. - * + * * Rococoa 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 3 of the License, or * (at your option) any later version. - * + * * Rococoa 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 @@ -16,49 +16,48 @@ * You should have received a copy of the GNU Lesser General Public License * along with Rococoa. If not, see . */ - -package org.rococoa.internal; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; +package org.rococoa.internal; +import com.sun.jna.Library; +import com.sun.jna.NativeLibrary; +import com.sun.jna.NativeLong; +import com.sun.jna.Structure; import org.rococoa.ID; import org.rococoa.RococoaException; import org.rococoa.Selector; -import com.sun.jna.Function; -import com.sun.jna.Library; -import com.sun.jna.NativeLong; -import com.sun.jna.Structure; +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; /** * Very special case InvocationHandler that invokes the correct message dispatch * function for different return types. - * + *

* Either objc_msgSend or objc_msgSend_stret should be called, depending on the * return type. The latter is usually for struct by value, but the former is * used for small structs on Intel! Oh and the call has to be mangled in all * cases as the result is returned on the stack, but is different sizes * depending on its type. Luckily jna and libffi take care of the details - * provided they know what the return type is. - * + *

* This InvocationHandler is passed the return type as the first arg to the method call that it * intercepts, it uses it to determine which function to call, and removes it before * calling invoking. - * + * + * @author duncan * @see "http://www.cocoabuilder.com/archive/message/cocoa/2006/6/25/166236" * @see "http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/LowLevelABI/Mac_OS_X_ABI_Function_Calls.pdf" * @see "http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html" - * - * Note also that there is a objc_msgSend_fret that is used supposed to be for + *

+ * Note also that there is a objc_msgSend_fret that is used supposed to be for * floating point return types, but that I haven't (yet) had to use. - * * @see "http://www.sealiesoftware.com/blog/archive/2008/11/16/objc_explain_objc_msgSend_fpret.html" - * - * @author duncan - * */ class MsgSendHandler implements InvocationHandler { @@ -66,75 +65,76 @@ 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; - private final static int stretCutoff = NativeLong.SIZE == 8 ? IA64_STRET_CUTOFF : I386_STRET_CUTOFF; + private final static int STRET_CUTOFF = NativeLong.SIZE == 8 ? IA64_STRET_CUTOFF : I386_STRET_CUTOFF; - private final static boolean ppc = System.getProperty("os.arch").trim().equalsIgnoreCase("ppc"); + 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_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_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) { + } catch (NoSuchMethodException x) { throw new RococoaException(x); } } - 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_Pair; - private RococoaTypeMapper rococoaTypeMapper = new RococoaTypeMapper(); + private final RococoaTypeMapper rococoaTypeMapper = new RococoaTypeMapper(); - public MsgSendHandler(Function objc_msgSend_Function, Function objc_msgSend_stret_Function) { - this.objc_msgSend_Pair = new Pair(OBJC_MSGSEND, objc_msgSend_Function); - this.objc_msgSend_stret_Pair = new Pair(OBJC_MSGSEND_STRET, objc_msgSend_stret_Function); + public MsgSendHandler(final NativeLibrary lib) { + this.objc_msgSend_Pair = new MethodFunctionPair(AARCH64 ? null : OBJC_MSGSEND, + 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) throws Throwable { + + 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) { + if (varArgs) { + return objc_msgSend_varArgs_Pair; + } + return objc_msgSend_Pair; + } boolean isStruct = Structure.class.isAssignableFrom(returnTypeForThisCall); boolean isStructByValue = isStruct && Structure.ByValue.class.isAssignableFrom(returnTypeForThisCall); - if (!isStructByValue) + if (!isStructByValue) { return objc_msgSend_Pair; + } try { - if(ppc) { + if (PPC) { // on ppc32 structs never return in registers 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() < stretCutoff ? objc_msgSend_Pair : objc_msgSend_stret_Pair; - } - catch(InstantiationException e) { - throw new RococoaException(e); - } - catch(IllegalAccessException e) { + Structure prototype = (Structure) returnTypeForThisCall.getDeclaredConstructor().newInstance(); + return prototype.size() < STRET_CUTOFF ? objc_msgSend_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 3ef60683..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,52 +17,52 @@ * along with Rococoa. If not, see . */ -/** - * - */ package org.rococoa.internal; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; - +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( - lib.getFunction("objc_msgSend"), - 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..16ee497b 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,10 @@ 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... 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 diff --git a/rococoa/rococoa-core/src/main/native/Rococoa.h b/rococoa/rococoa-core/src/main/native/Rococoa.h index 427b07f4..c47b12a8 100644 --- a/rococoa/rococoa-core/src/main/native/Rococoa.h +++ b/rococoa/rococoa-core/src/main/native/Rococoa.h @@ -1,7 +1,7 @@ #import #include -void callOnMainThread(void (*fn)(), BOOL waitUntilDone); +void callOnMainThread(void (*fn)(void), BOOL waitUntilDone); @interface RococoaHelper : NSObject + (void) callback: (NSValue*) fn; diff --git a/rococoa/rococoa-core/src/main/native/Rococoa.m b/rococoa/rococoa-core/src/main/native/Rococoa.m index 5caadffd..48481d89 100644 --- a/rococoa/rococoa-core/src/main/native/Rococoa.m +++ b/rococoa/rococoa-core/src/main/native/Rococoa.m @@ -1,6 +1,6 @@ #include "Rococoa.h" -void callOnMainThread(void (*fn)(), BOOL waitUntilDone) { +void callOnMainThread(void (*fn)(void), BOOL waitUntilDone) { // NSLog(@"callOnMainThread function at address %p", fn); // Pool is required as we're being called from Java, which probably doesn't have a pool to // allocate the NSValue from. @@ -13,7 +13,7 @@ void callOnMainThread(void (*fn)(), BOOL waitUntilDone) { @implementation RococoaHelper : NSObject + (void) callback: (NSValue*) fnAsValue { - void (*fn)() = [fnAsValue pointerValue]; + void (*fn)(void) = [fnAsValue pointerValue]; (*fn)(); }