Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues/20 #24

Merged
merged 4 commits into from Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 4 additions & 20 deletions rococoa/pom.xml
Expand Up @@ -145,7 +145,7 @@
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.9.0</version>
<version>5.10.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
Expand All @@ -167,7 +167,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -177,25 +177,9 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<version>3.2.0</version>
<inherited>true</inherited>
<configuration>
<encoding>UTF-8</encoding>
Expand Down Expand Up @@ -305,7 +289,7 @@
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<version>3.2.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
Expand Down
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;
}
}
@@ -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
Expand All @@ -16,125 +16,125 @@
* You should have received a copy of the GNU Lesser General Public License
* 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.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.
*
* <p>
* 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.
*
* <p>
* 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
* <p>
* 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 {

/**
* @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<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_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<Method, Function>(OBJC_MSGSEND, objc_msgSend_Function);
this.objc_msgSend_stret_Pair = new Pair<Method, Function>(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<String, Object> options = new HashMap<String, Object>(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) {
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);
}
}
Expand Down