package sharpen.xobotos.api.interop; import static sharpen.core.framework.Environments.my; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.VariableDeclaration; import sharpen.core.Configuration; import sharpen.core.NamingStrategy; import sharpen.core.Sharpen; import sharpen.core.csharp.ast.*; import sharpen.core.framework.BindingUtils; import sharpen.core.framework.ByRef; import sharpen.xobotos.api.interop.NativeMethod.Kind; import sharpen.xobotos.api.interop.Signature.Flags; import sharpen.xobotos.api.interop.Signature.Mode; import sharpen.xobotos.api.interop.Signature.ParameterInfo; import sharpen.xobotos.api.interop.Signature.ReturnInfo; import sharpen.xobotos.api.interop.glue.AbstractTypeReference; import sharpen.xobotos.api.interop.glue.CompilationUnit; import sharpen.xobotos.api.interop.glue.Expression; import sharpen.xobotos.api.interop.glue.IncludeSection; import sharpen.xobotos.api.interop.glue.Method; import sharpen.xobotos.api.interop.marshal.MarshalAsClass; import sharpen.xobotos.api.interop.marshal.MarshalAsNativeType; import sharpen.xobotos.api.interop.marshal.MarshalInfo; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; public class NativeMethodBuilder { private final NativeBuilder _builder; private final MethodDeclaration _node; private final NativeMethod _native; private final String _nativeName; private final String _nativeFunctionName; private boolean _resolved; private boolean _resolveFailed; private ElementInfo _returnInfo; private ElementInfo[] _paramInfo; private IVariableBinding _implicitInstance; private NativeHandleBuilder _nativeHandle; private IMethodBinding _binding; private String _methodName; public static class ElementInfo { public final ITypeBinding type; public final MarshalInfo marshal; public final Mode mode; public final Flags flags; public boolean isClass() { return marshal instanceof MarshalAsNativeType; } public ITypeBinding getType() { return marshal.getType(); } public AbstractNativeTypeBuilder getTypeBuilder() { return ((MarshalAsNativeType) marshal).getTypeBuilder(); } public CSTypeReferenceExpression getManagedType() { return marshal.getManagedType(mode, flags); } public CSTypeReferenceExpression getPInvokeType() { return marshal.getPInvokeType(mode, flags); } public CSTypeReferenceExpression getPInvokeReturnType() { return marshal.getPInvokeReturnType(); } public AbstractTypeReference getNativeType() { return marshal.getNativeType(mode, flags); } public ElementInfo(ITypeBinding type, MarshalInfo marshal, Mode mode, Flags flags) { this.type = type; this.marshal = marshal; this.mode = mode; this.flags = flags; } public CSExpression marshalIn(CSExpression expr) { return marshal.marshalIn(expr, mode, flags); } public Expression marshalNativeArg(Expression expr) { return marshal.marshalNativeArg(expr, mode, flags); } } public NativeMethodBuilder(NativeBuilder builder, MethodDeclaration method, NativeMethod template, String name, String funcName, NativeHandleBuilder handleBuilder) { this._builder = builder; this._nativeHandle = handleBuilder; this._node = method; this._native = template; this._nativeName = name; this._nativeFunctionName = funcName; } public NativeBuilder getNativeBuilder() { return _builder; } public NativeConfiguration _getConfig() { return _builder.getConfig(); } public MethodDeclaration getMethod() { return _node; } public NativeMethod getNativeMethod() { return _native; } public NativeHandleBuilder getNativeHandle() { return _nativeHandle; } protected static boolean isVoid(ITypeBinding type) { return (type == null) || BindingUtils.qualifiedName(type).equals("void"); } public boolean resolve(IMarshalContext context) { if (_resolveFailed) return false; if (_resolved) return true; _binding = _node.resolveBinding(); ITypeBinding returnType = _binding.getReturnType(); ITypeBinding[] paramTypes = _binding.getParameterTypes(); _methodName = BindingUtils.qualifiedSignature(_binding); final Signature signature = getNativeMethod().getSignature(); final Kind kind = _native.getKind(); MarshalInfo returnInfo = null; if (isVoid(returnType) && (kind == Kind.CONSTRUCTOR)) { ReturnInfo info = signature.getReturnInfo(); if ((info != null) && (info.marshal != null)) returnInfo = info.marshal.resolve(null); if (returnInfo == null) { Sharpen.Log(Level.SEVERE, "Missing marshal info for return type of constructor '%s'", _methodName); _resolveFailed = true; } } else if (!isVoid(returnType) && !getNativeMethod().returnVoid()) { ReturnInfo info = signature.getReturnInfo(); if (info != null) { if (info.marshal != null) returnInfo = info.marshal.resolve(returnType); else returnInfo = context.getMarshalInfo(returnType); } else { returnInfo = context.getMarshalInfo(returnType); } if (returnInfo == null) { Sharpen.Log(Level.SEVERE, "Missing marshal info for return type '%s' of method '%s'", BindingUtils.qualifiedName(returnType), _methodName); _resolveFailed = true; } } if (kind == Kind.CONSTRUCTOR) { if (returnInfo == null) { Sharpen.Log(Level.SEVERE, "Missing marshal info for return type '%s' of constructor '%s'", BindingUtils.qualifiedName(returnType), _methodName); _resolveFailed = true; } else if (!(returnInfo instanceof MarshalAsClass)) { Sharpen.Log(Level.SEVERE, "Return type '%s' of constructor '%s' must have MarshalAsClass marshal info", BindingUtils.qualifiedName(returnType), _methodName); _resolveFailed = true; } } if (kind == Kind.DESTRUCTOR) { if (_returnInfo != null) { Sharpen.Log(Level.SEVERE, "Destructor '%s' cannot return a value.", _methodName); _resolveFailed = true; } else if (paramTypes.length != 1) { Sharpen.Log(Level.SEVERE, "Destructor '%s' must take one single argument.", _methodName); _resolveFailed = true; } else if (_nativeHandle == null) { Sharpen.Log(Level.SEVERE, "Destructor '%s' must have a native handle", _methodName); _resolveFailed = true; } } if (_resolveFailed) return false; if (returnInfo != null) { if (!returnInfo.resolve(context, returnType)) { Sharpen.Log(Level.SEVERE, "Cannot resolve return value in '%s'.", _methodName); _resolveFailed = true; } else { _returnInfo = new ElementInfo(returnType, returnInfo, Mode.OUT, null); } } if (kind == Kind.DESTRUCTOR) { _paramInfo = new ElementInfo[1]; if (_nativeHandle == null) { Sharpen.Log(Level.SEVERE, "Missing 'native-handle' for destructor '%s'.", _methodName); _resolveFailed = true; } else { MarshalInfo info = new MarshalAsClass(paramTypes[0], null, _nativeHandle.getTemplate()); VariableDeclaration vdecl = (VariableDeclaration) _node.parameters().get(0); IVariableBinding vbinding = vdecl.resolveBinding(); ITypeBinding vtype = vbinding.getType(); if (!info.resolve(context, vtype)) { Sharpen.Log(Level.SEVERE, "Cannot resolve marshal info for destructor '%s'.", _methodName); _resolveFailed = true; } else { _paramInfo[0] = new ElementInfo(vtype, info, null, null); } } } else { _paramInfo = new ElementInfo[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { final ParameterInfo info = signature.getParameterInfo(i); if ((info == null) || (info.mode == Mode.REMOVE)) continue; MarshalInfo marshal; if (info.marshal != null) marshal = info.marshal.resolve(paramTypes[i]); else marshal = context.getMarshalInfo(paramTypes[i]); if (marshal == null) { Sharpen.Log(Level.SEVERE, "Missing marshal info for type '%s' in method '%s'", BindingUtils.qualifiedName(paramTypes[i]), _methodName); _resolveFailed = true; continue; } VariableDeclaration vdecl = (VariableDeclaration) _node.parameters().get(i); IVariableBinding vbinding = vdecl.resolveBinding(); ITypeBinding vtype = vbinding.getType(); boolean isInstanceArg; if ((kind == Kind.INSTANCE) || (kind == Kind.PROXY)) isInstanceArg = (i == 0) && !signature.implicitInstance(); else isInstanceArg = false; if (isInstanceArg) { _paramInfo[0] = new ElementInfo(vtype, marshal, Mode.REF, null); if (!marshal.resolve(context, vtype)) { Sharpen.Log(Level.SEVERE, "Cannot resolve instance parameter of method '%s'.", _methodName); _resolveFailed = true; } } else { _paramInfo[i] = new ElementInfo(vtype, marshal, info.mode, info.flags); if (!marshal.resolve(context, vtype)) { Sharpen.Log(Level.SEVERE, "Cannot resolve parameter %d of method '%s'.", i, _methodName); _resolveFailed = true; } } } } if (signature.implicitInstance()) { if (_nativeHandle == null) { Sharpen.Log(Level.SEVERE, "Cannot use <add-instance> without <native-handle> in '%s'", _methodName); _resolveFailed = true; return false; } String fieldName = _nativeHandle.getTemplate().getField(); _implicitInstance = findField(_binding, fieldName); if (_implicitInstance == null) { Sharpen.Log(Level.SEVERE, "No such field '%s' in type '%s'", fieldName, BindingUtils.qualifiedName(_binding.getDeclaringClass())); _resolveFailed = true; return false; } } if (_resolveFailed) return false; _resolved = true; return true; } private IVariableBinding findField(IMethodBinding binding, String name) { for (final IVariableBinding field : binding.getDeclaringClass().getDeclaredFields()) { if (field.getName().equals(name)) return field; } return null; } public String getNativeName() { return _nativeName; } public String getNativeFunction() { return _nativeFunctionName; } public ElementInfo getReturnInfo() { return _returnInfo; } public ElementInfo[] getParameterInfo() { return _paramInfo; } public IVariableBinding getImplicitInstance() { return _implicitInstance; } public void collectIncludes(IncludeSection section) { if (_resolveFailed) return; section.addIncludes(_native); for (final ElementInfo param : _paramInfo) { collectIncludes(section, param); } collectIncludes(section, _returnInfo); } private void collectIncludes(IncludeSection section, ElementInfo info) { if (info == null) return; if (info.isClass()) { if (info.getTypeBuilder() instanceof HelperClassBuilder) { } NativeBuilder builder = info.getTypeBuilder().getNativeBuilder(); if (builder != _builder) section.addInclude(builder.getHeaderInclude()); } else if (info.marshal != null) { section.addIncludes(info.marshal); } } public boolean createPInvokeWrapper(CSTypeDeclaration parent, final CSMethod method) { if (_resolveFailed) return false; final NamingStrategy ns = my(Configuration.class).getNamingStrategy(); final CSTypeReferenceExpression pinvokeReturnType; final CSTypeReferenceExpression mappedReturnType; if (_returnInfo != null) { mappedReturnType = _returnInfo.getManagedType(); pinvokeReturnType = _returnInfo.getPInvokeReturnType(); method.returnType(mappedReturnType); } else { pinvokeReturnType = null; mappedReturnType = null; } CSTypeReferenceExpression[] mappedParamTypes = new CSTypeReferenceExpression[_paramInfo.length]; for (int i = 0; i < _paramInfo.length; i++) { if (_paramInfo[i] == null) continue; mappedParamTypes[i] = _paramInfo[i].getManagedType(); method.parameters().get(i).type(mappedParamTypes[i]); } final CSDllImport dllImport = _builder.getConfig().getDllImportAttribute(); final List<CSExpression> args = new ArrayList<CSExpression>(); final CSMethod pinvoke = new CSMethod(_nativeFunctionName); pinvoke.dllImport(dllImport); pinvoke.modifier(CSMethodModifier.Extern); pinvoke.visibility(CSVisibility.Private); if (_returnInfo != null) { pinvoke.returnType(pinvokeReturnType); } else { pinvoke.returnType(new CSTypeReference("void")); } if (_native.returnVoid()) method.returnType(new CSTypeReference("void")); if (_native.getKind() == Kind.DESTRUCTOR) { final String name = method.parameters().get(0).name(); CSExpression pref = new CSReferenceExpression(name); CSExpression mr = new CSMemberReferenceExpression(pref, "Dispose"); method.body().addStatement(new CSMethodInvocationExpression(mr)); return true; } if (_implicitInstance != null) { CSExpression fref = new CSReferenceExpression(_implicitInstance.getName()); CSTypeReferenceExpression ftype = _nativeHandle.getManagedType(); pinvoke.addParameter(new CSVariableDeclaration("_instance", ftype)); args.add(fref); } final List<CSVariableDeclaration> decls = new ArrayList<CSVariableDeclaration>(); final List<CSStatement> preStatements = new ArrayList<CSStatement>(); final List<CSStatement> postStatements = new ArrayList<CSStatement>(); final List<CSStatement> cleanupStatements = new ArrayList<CSStatement>(); for (int i = 0, pos = 0; i < _paramInfo.length; i++) { if (_paramInfo[i] == null) { method.removeParameter(pos); continue; } final String name = ns.identifier(method.parameters().get(pos).name()); CSExpression pref = new CSReferenceExpression(name); IManagedMarshalContext context = new IManagedMarshalContext() { @Override public void addDeclaration(CSVariableDeclaration decl, CSStatement cleanup) { decls.add(decl); if (cleanup != null) cleanupStatements.add(cleanup); } @Override public void addPreStatement(CSStatement statement) { preStatements.add(statement); } @Override public void addPostStatement(CSStatement statement) { postStatements.add(statement); } @Override public ManagedVariable addParameter(String suffix, CSTypeReferenceExpression type, CSExpression arg, CSAttribute... attrs) { final String pname = suffix != null ? name + suffix : name; CSVariableDeclaration vdecl = pinvoke.addParameter(pname, type, attrs); args.add(arg); return new ManagedVariable(vdecl); } @Override public String getVariableName(String suffix) { return suffix != null ? name + "_" + suffix : name; } }; _paramInfo[i].marshal.marshal(context, pref, _paramInfo[i].mode, _paramInfo[i].flags); pos++; } final CSExpression[] arglist = args.toArray(new CSExpression[0]); CSExpression mie = new CSMethodInvocationExpression(new CSReferenceExpression(pinvoke.name()), arglist); final CSExpression outExpr; final ByRef<ManagedVariable> retval = new ByRef<ManagedVariable>(); if (_returnInfo != null) { IManagedReturnContext context = new IManagedReturnContext() { @Override public void addStatement(CSStatement statement) { postStatements.add(statement); } @Override public ManagedVariable createVariable(String name, CSTypeReferenceExpression type, CSExpression init) { final String vname = "_retval_" + name; ManagedVariable var = new ManagedVariable(vname, type); if (init != null) var.getDeclaration().initializer(init); postStatements.add(var.getDeclarationStatement()); return var; } @Override public ManagedVariable createRetval(CSExpression init) { retval.value = new ManagedVariable("_retval", mappedReturnType); retval.value.getDeclaration().initializer(init); postStatements.add(retval.value.getDeclarationStatement()); return retval.value; } }; outExpr = _returnInfo.marshal.marshalRetval(context, mie); if (retval.value != null) { if (outExpr != null) throw new IllegalStateException(); } else if (outExpr == null) { throw new IllegalStateException(); } } else { outExpr = null; } for (CSVariableDeclaration decl : decls) method.body().addStatement(new CSDeclarationStatement(-1, decl)); final CSBlock block; if (cleanupStatements.size() > 0) { CSTryStatement tryBlock = new CSTryStatement(-1); CSBlock finallyBlock = new CSBlock(); for (CSStatement stm : cleanupStatements) { finallyBlock.addStatement(stm); } tryBlock.finallyBlock(finallyBlock); method.body().addStatement(tryBlock); block = tryBlock.body(); } else { block = method.body(); } for (CSStatement stm : preStatements) block.addStatement(stm); if (_returnInfo != null) { if (postStatements.size() > 0) { if (retval.value == null) { retval.value = new ManagedVariable("_retval", mappedReturnType); retval.value.getDeclaration().initializer(outExpr); block.addStatement(retval.value.getDeclarationStatement()); } for (CSStatement stmt : postStatements) block.addStatement(stmt); block.addStatement(new CSReturnStatement(-1, retval.value.getReference())); } else { block.addStatement(new CSReturnStatement(-1, outExpr)); } } else { block.addStatement(mie); for (CSStatement stmt : postStatements) block.addStatement(stmt); } parent.addMember(pinvoke); return true; } public boolean createNativeMethod(CompilationUnit unit) { if(_resolveFailed) return false; if (_native.getKind() == Kind.DESTRUCTOR) return true; NativeGlueGenerator generator = new NativeGlueGenerator(this); if (!generator.resolve()) return false; Method method = generator.generate(); if (method == null) return false; unit.addMethod(method); return true; } }