Skip to content

Commit

Permalink
Fix #20.
Browse files Browse the repository at this point in the history
  • Loading branch information
dkocher committed Nov 7, 2021
1 parent 44bc0a3 commit c528a5f
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 73 deletions.
28 changes: 21 additions & 7 deletions rococoa/rococoa-core/src/main/java/org/rococoa/Foundation.java
Expand Up @@ -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;
Expand Down Expand Up @@ -171,26 +172,39 @@ public static Selector selector(String selectorName) {
return result;
}

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

@SuppressWarnings("unchecked")
public static <T> T send(ID receiver, String selectorName, Class<T> returnType, Method method, Object... args) {
return send(receiver, selector(selectorName), returnType, method, args);
}

@SuppressWarnings("unchecked")
public static <T> T send(ID receiver, Selector selector, Class<T> returnType, Object... args) {
return send(receiver, selector, returnType, null, args);
}

/**
* Send message with selector to receiver, passing args, expecting returnType.
* <p>
* 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> T send(ID receiver, Selector selector, Class<T> returnType, Object... args) {
public static <T> T send(ID receiver, Selector selector, Class<T> 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);
}

Expand Down
Expand Up @@ -19,13 +19,17 @@

package org.rococoa.internal;

public class Pair<T1, T2> {
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;
}
}
Expand Up @@ -19,19 +19,21 @@

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;
import org.rococoa.RococoaException;
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
Expand Down Expand Up @@ -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;
Expand All @@ -73,66 +74,66 @@ 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) {
throw new RococoaException(x);
}
}

private final Pair<Method, Function> objc_msgSend_stret_Pair;
private final Pair<Method, Function> 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<Function> objc_msgSend_Function, Optional<Function> 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<String, Object> options = new HashMap<>(1);
options.put(Library.OPTION_TYPE_MAPPER, rococoaTypeMapper);

Pair<Method, Function> 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<String, Object> 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<Method, Function> 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) {
// 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() < 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);
}
}
Expand Down
Expand Up @@ -17,53 +17,52 @@
* along with Rococoa. If not, see <http://www.gnu.org/licenses/>.
*/

/**
*
*/
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.
*
* <p>
* 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
}

}
Expand Up @@ -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);
}

0 comments on commit c528a5f

Please sign in to comment.