All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jnr.ffi.provider.jffi.AsmLibraryLoader Maven / Gradle / Ivy

There is a newer version: 2.2.16
Show newest version
/*
 * Copyright (C) 2008-2010 Wayne Meissner
 *
 * This file is part of the JNR project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package jnr.ffi.provider.jffi;

import com.kenai.jffi.CallingConvention;
import com.kenai.jffi.Function;
import jnr.ffi.*;
import jnr.ffi.annotations.StdCall;
import jnr.ffi.byref.ByReference;
import jnr.ffi.mapper.*;
import jnr.ffi.provider.IdentityFunctionMapper;
import jnr.ffi.provider.NullTypeMapper;
import jnr.ffi.provider.ParameterFlags;
import jnr.ffi.util.EnumMapper;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.Buffer;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import static jnr.ffi.provider.jffi.AsmUtil.*;
import static jnr.ffi.provider.jffi.CodegenUtils.*;
import static org.objectweb.asm.Opcodes.*;

public class AsmLibraryLoader extends LibraryLoader {
    public final static boolean DEBUG = Boolean.getBoolean("jnr.ffi.compile.dump");
    private static final AtomicLong nextClassID = new AtomicLong(0);


    private final NativeRuntime runtime = NativeRuntime.getInstance();


    boolean isInterfaceSupported(Class interfaceClass, Map options) {
        TypeMapper typeMapper = options.containsKey(LibraryOption.TypeMapper)
                ? (TypeMapper) options.get(LibraryOption.TypeMapper) : NullTypeMapper.INSTANCE;

        for (Method m : interfaceClass.getDeclaredMethods()) {
            if (!isReturnTypeSupported(m.getReturnType()) && getResultConverter(m, typeMapper) == null) {
                System.err.println("Unsupported return type: " + m.getReturnType());
                return false;
            }
            for (Class c: m.getParameterTypes()) {
                if (!isParameterTypeSupported(c) && typeMapper.getToNativeConverter(c) == null) {
                    System.err.println("Unsupported parameter type: " + c);
                    return false;
                }
            }
        }

        return true;
    }

    @Override
     T loadLibrary(NativeLibrary library, Class interfaceClass, Map libraryOptions) {
        return generateInterfaceImpl(library, interfaceClass, libraryOptions);
    }

    private final  T generateInterfaceImpl(final NativeLibrary library, Class interfaceClass, Map libraryOptions) {

        boolean debug = DEBUG && interfaceClass.getAnnotation(NoTrace.class) == null;
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassVisitor cv = debug ? AsmUtil.newCheckClassAdapter(cw) : cw;

        String className = p(interfaceClass) + "$jaffl$" + nextClassID.getAndIncrement();
        AsmBuilder builder = new AsmBuilder(className, cv);


        cv.visit(V1_5, ACC_PUBLIC | ACC_FINAL, className, null, p(AbstractAsmLibraryInterface.class),
                new String[] { p(interfaceClass) });

        // Create the constructor to set the 'library' & functions fields
        SkinnyMethodAdapter init = new SkinnyMethodAdapter(cv.visitMethod(ACC_PUBLIC, "",
                sig(void.class, NativeLibrary.class, Object[].class),
                null, null));
        init.start();
        // Invokes the super class constructor as super(Library)

        init.aload(0);
        init.aload(1);

        init.invokespecial(p(AbstractAsmLibraryInterface.class), "", sig(void.class, NativeLibrary.class));
        
        final Method[] methods = interfaceClass.getMethods();
        FromNativeConverter[] resultConverters = new FromNativeConverter[methods.length];
        ToNativeConverter[][] parameterConverters = new ToNativeConverter[methods.length][0];
        
        FunctionMapper functionMapper = libraryOptions.containsKey(LibraryOption.FunctionMapper)
                ? (FunctionMapper) libraryOptions.get(LibraryOption.FunctionMapper) : IdentityFunctionMapper.getInstance();

        TypeMapper typeMapper = libraryOptions.containsKey(LibraryOption.TypeMapper)
                ? (TypeMapper) libraryOptions.get(LibraryOption.TypeMapper) : NullTypeMapper.INSTANCE;
        NativeClosureManager closureManager = new NativeClosureManager(runtime, typeMapper);
        com.kenai.jffi.CallingConvention libraryCallingConvention = getCallingConvention(interfaceClass, libraryOptions);

        BufferMethodGenerator bufgen = new BufferMethodGenerator();
        StubCompiler compiler = StubCompiler.newCompiler();

        final MethodGenerator[] generators = {
                interfaceClass.getAnnotation(NoX86.class) == null
                    ? new X86MethodGenerator(compiler, bufgen) : new NotImplMethodGenerator(),
                new FastIntMethodGenerator(bufgen),
                new FastLongMethodGenerator(bufgen),
                new FastNumericMethodGenerator(bufgen),
                bufgen
        };


        for (int i = 0; i < methods.length; ++i) {
            Method m = methods[i];
            final Class javaReturnType = m.getReturnType();
            final Class[] javaParameterTypes = m.getParameterTypes();
            final Annotation[] resultAnnotations = m.getAnnotations();
            final Annotation[][] parameterAnnotations = m.getParameterAnnotations();

            resultConverters[i] = getResultConverter(m, typeMapper);
            ResultType resultType = InvokerUtil.getResultType(runtime, m.getReturnType(),
                    resultAnnotations, resultConverters[i]);

            parameterConverters[i] = new ToNativeConverter[javaParameterTypes.length];
            ParameterType[] parameterTypes = new ParameterType[javaParameterTypes.length];

            for (int pidx = 0; pidx < javaParameterTypes.length; ++pidx) {
                parameterConverters[i][pidx] = getParameterConverter(m, pidx, typeMapper, closureManager);
                parameterTypes[pidx] = InvokerUtil.getParameterType(runtime, javaParameterTypes[pidx],
                        parameterAnnotations[pidx], parameterConverters[i][pidx]);
            }

            // Stash the name of the function in a static field
            String functionName = functionMapper.mapFunctionName(m.getName(), null);
            cv.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, "name_" + i, ci(String.class), null, functionName);

            // Allow individual methods to set the calling convention to stdcall
            CallingConvention callingConvention = m.getAnnotation(StdCall.class) != null
                    ? CallingConvention.STDCALL : libraryCallingConvention;
            boolean saveErrno = InvokerUtil.requiresErrno(m);
            Function function;
            try {
                function = getFunction(library.findSymbolAddress(functionName),
                    resultType, parameterTypes, saveErrno, callingConvention);
            } catch (SymbolNotFoundError ex) {
                cv.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, "error_" + i, ci(String.class), null, ex.getMessage());
                generateFunctionNotFound(cv, className, i, functionName, javaReturnType, javaParameterTypes);
                continue;
            }

            for (MethodGenerator g : generators) {
                if (g.isSupported(resultType, parameterTypes, callingConvention)) {
                    g.generate(builder, m.getName(), function, resultType, parameterTypes, !saveErrno);
                    break;
                }
            }
        }


        AsmBuilder.ObjectField[] fields = builder.getObjectFieldArray();
        Object[] fieldObjects = new Object[fields.length];
        for (int i = 0; i < fieldObjects.length; i++) {
            fieldObjects[i] = fields[i].value;
            String fieldName = fields[i].name;
            cv.visitField(ACC_PRIVATE | ACC_FINAL, fieldName, ci(fields[i].klass), null, null);
            init.aload(0);
            init.aload(2);
            init.pushInt(i);
            init.aaload();

            if (fields[i].klass.isPrimitive()) {
                Class boxedType = boxedType(fields[i].klass);
                init.checkcast(boxedType);
                unboxNumber(init, boxedType, fields[i].klass);
            } else {
                init.checkcast(fields[i].klass);
            }
            init.putfield(className, fieldName, ci(fields[i].klass));
        }

        init.voidreturn();
        init.visitMaxs(10, 10);
        init.visitEnd();

        cv.visitEnd();

        try {
            byte[] bytes = cw.toByteArray();
            if (debug) {
                ClassVisitor trace = AsmUtil.newTraceClassVisitor(new PrintWriter(System.err));
                new ClassReader(bytes).accept(trace, 0);
            }

            Class implClass = new AsmClassLoader(interfaceClass.getClassLoader()).defineClass(className.replace("/", "."), bytes);
            Constructor cons = implClass.getDeclaredConstructor(NativeLibrary.class, Object[].class);
            T result = cons.newInstance(library, fieldObjects);

            // Attach any native method stubs - we have to delay this until the
            // implementation class is loaded for it to work.
            System.err.flush();
            System.out.flush();
            compiler.attach(implClass);

            return result;
        } catch (Throwable ex) {
            throw new RuntimeException(ex);
        }
    }

    private final ToNativeConverter getParameterConverter(Method m, int parameterIndex,
                                                          TypeMapper typeMapper, NativeClosureManager closureManager) {
        Class parameterType = m.getParameterTypes()[parameterIndex];
        ToNativeConverter conv = typeMapper.getToNativeConverter(parameterType);
        if (conv != null) {
            return new ParameterConverter(conv, new MethodParameterContext(m, parameterIndex));

        } else if (Enum.class.isAssignableFrom(parameterType)) {
            return EnumMapper.getInstance(parameterType.asSubclass(Enum.class));

        } else if (isDelegate(parameterType)) {
            return closureManager.newClosureSite(parameterType);

        } else if (ByReference.class.isAssignableFrom(parameterType)) {
            return new ByReferenceParameterConverter(ParameterFlags.parse(m.getParameterAnnotations()[parameterIndex]));

        } else {
            return null;
        }
    }

    private final FromNativeConverter getResultConverter(Method m, TypeMapper typeMapper) {
        Class returnType = m.getReturnType();
        FromNativeConverter conv = typeMapper.getFromNativeConverter(returnType);
        if (conv != null) {
            return new ResultConverter(conv, new MethodResultContext(m));

        } else if (Enum.class.isAssignableFrom(returnType)) {
            return EnumMapper.getInstance(returnType.asSubclass(Enum.class));

        } else {
            return null;
        }
    }

    private static final com.kenai.jffi.CallingConvention getCallingConvention(Class interfaceClass, Map options) {
        if (interfaceClass.getAnnotation(StdCall.class) != null) {
            return com.kenai.jffi.CallingConvention.STDCALL;
        }
        return InvokerUtil.getCallingConvention(options);
    }

    private final void generateFunctionNotFound(ClassVisitor cv, String className, int idx, String functionName,
            Class returnType, Class[] parameterTypes) {
        SkinnyMethodAdapter mv = new SkinnyMethodAdapter(cv.visitMethod(ACC_PUBLIC | ACC_FINAL, functionName,
                sig(returnType, parameterTypes), null, null));
        mv.start();
        mv.getstatic(className, "error_" + idx, ci(String.class));
        mv.invokestatic(AsmRuntime.class, "newUnsatisifiedLinkError", UnsatisfiedLinkError.class, String.class);
        mv.athrow();
        mv.visitMaxs(10, 10);
        mv.visitEnd();
    }


    private static Function getFunction(long address, ResultType resultType, ParameterType[] parameterTypes,
                                        boolean requiresErrno, CallingConvention convention) {
        return new Function(address, InvokerUtil.getCallContext(resultType, parameterTypes, convention, requiresErrno));
    }

    private static boolean isReturnTypeSupported(Class type) {
        return type.isPrimitive() || Byte.class == type
                || Short.class == type || Integer.class == type
                || Long.class == type || Float.class == type
                || Double.class == type || NativeLong.class == type
                || Enum.class.isAssignableFrom(type)
                || Pointer.class == type || Address.class == type
                || String.class == type
                || Struct.class.isAssignableFrom(type);

    }

    private static boolean isParameterTypeSupported(Class type) {
        return type.isPrimitive() || Byte.class == type
                || Short.class == type || Integer.class == type
                || Long.class == type || Float.class == type
                || Double.class == type || NativeLong.class == type
                || Pointer.class.isAssignableFrom(type) || Address.class.isAssignableFrom(type)
                || Enum.class.isAssignableFrom(type)
                || Buffer.class.isAssignableFrom(type)
                || (type.isArray() && type.getComponentType().isPrimitive())
                || Struct.class.isAssignableFrom(type)
                || (type.isArray() && Struct.class.isAssignableFrom(type.getComponentType()))
                || (type.isArray() && Pointer.class.isAssignableFrom(type.getComponentType()))
                || (type.isArray() && CharSequence.class.isAssignableFrom(type.getComponentType()))
                || CharSequence.class.isAssignableFrom(type)
                || ByReference.class.isAssignableFrom(type)
                || StringBuilder.class.isAssignableFrom(type)
                || StringBuffer.class.isAssignableFrom(type)
                || isDelegate(type)
                ;
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy