Optimizations
This commit is contained in:
parent
30f187a636
commit
3d84879ed8
@ -320,11 +320,15 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
public static List<CilInterfaceMethod> CollectAll(TypeDefinition fromType)
|
public static List<CilInterfaceMethod> CollectAll(TypeDefinition fromType)
|
||||||
{
|
{
|
||||||
var list = new List<CilInterfaceMethod>();
|
var map = new Dictionary<string, List<CilInterfaceMethod>>();
|
||||||
Process(fromType, list);
|
Process(fromType, map);
|
||||||
return list;
|
|
||||||
|
|
||||||
void Process(TypeDefinition fromType, List<CilInterfaceMethod> list)
|
var newList = new List<CilInterfaceMethod>();
|
||||||
|
foreach (var oldList in map.Values)
|
||||||
|
newList.AddRange(oldList);
|
||||||
|
return newList;
|
||||||
|
|
||||||
|
void Process(TypeDefinition fromType, Dictionary<string, List<CilInterfaceMethod>> map)
|
||||||
{
|
{
|
||||||
foreach (var fromMethod in fromType.Methods)
|
foreach (var fromMethod in fromType.Methods)
|
||||||
{
|
{
|
||||||
@ -343,19 +347,26 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
var outputMethod = new CilInterfaceMethod(inputMethod);
|
var outputMethod = new CilInterfaceMethod(inputMethod);
|
||||||
|
|
||||||
bool dup = false;
|
if (map.TryGetValue(inputMethod.Name, out var list))
|
||||||
foreach (var oldMethod in list)
|
|
||||||
{
|
{
|
||||||
if ( oldMethod.Method.Name == outputMethod.Method.Name
|
bool dup = false;
|
||||||
&& oldMethod.EqualParameters(outputMethod))
|
foreach (var oldMethod in list)
|
||||||
{
|
{
|
||||||
dup = true;
|
if (oldMethod.EqualParameters(outputMethod))
|
||||||
break;
|
{
|
||||||
|
dup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (! dup)
|
||||||
|
list.Add(outputMethod);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var list2 = new List<CilInterfaceMethod>();
|
||||||
|
list2.Add(outputMethod);
|
||||||
|
map.Add(inputMethod.Name, list2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! dup)
|
|
||||||
list.Add(outputMethod);
|
|
||||||
|
|
||||||
CilMain.GenericStack.Release(genericMark);
|
CilMain.GenericStack.Release(genericMark);
|
||||||
}
|
}
|
||||||
@ -369,7 +380,7 @@ namespace SpaceFlint.CilToJava
|
|||||||
var genericMark = CilMain.GenericStack.Mark();
|
var genericMark = CilMain.GenericStack.Mark();
|
||||||
CilMain.GenericStack.EnterType(fromBaseTypeDef);
|
CilMain.GenericStack.EnterType(fromBaseTypeDef);
|
||||||
|
|
||||||
Process(fromBaseTypeDef, list);
|
Process(fromBaseTypeDef, map);
|
||||||
|
|
||||||
CilMain.GenericStack.Release(genericMark);
|
CilMain.GenericStack.Release(genericMark);
|
||||||
}
|
}
|
||||||
|
@ -708,13 +708,21 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static Dictionary<MethodReference, MethodDefinition> _MethodDefs =
|
||||||
|
new Dictionary<MethodReference, MethodDefinition>();
|
||||||
|
|
||||||
internal static MethodDefinition AsDefinition(MethodReference _ref)
|
internal static MethodDefinition AsDefinition(MethodReference _ref)
|
||||||
{
|
{
|
||||||
if (_ref.IsDefinition)
|
if (_ref.IsDefinition)
|
||||||
return _ref as MethodDefinition;
|
return _ref as MethodDefinition;
|
||||||
var def = _ref.Resolve();
|
if (_MethodDefs.TryGetValue(_ref, out var def))
|
||||||
if (def != null)
|
|
||||||
return def;
|
return def;
|
||||||
|
def = _ref.Resolve();
|
||||||
|
if (def != null)
|
||||||
|
{
|
||||||
|
_MethodDefs.Add(_ref, def);
|
||||||
|
return def;
|
||||||
|
}
|
||||||
throw CilMain.Where.Exception(
|
throw CilMain.Where.Exception(
|
||||||
$"could not resolve method '{_ref.Name}' from assembly '{_ref.DeclaringType.Scope}'");
|
$"could not resolve method '{_ref.Name}' from assembly '{_ref.DeclaringType.Scope}'");
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ namespace SpaceFlint.CilToJava
|
|||||||
{
|
{
|
||||||
if (! _Types.TryGetValue(fromType, out var converted))
|
if (! _Types.TryGetValue(fromType, out var converted))
|
||||||
{
|
{
|
||||||
|
converted = GetCachedPrimitive(fromType);
|
||||||
|
if (converted != null)
|
||||||
|
return converted;
|
||||||
|
|
||||||
CilMain.Where.Push($"type '{fromType.FullName}'");
|
CilMain.Where.Push($"type '{fromType.FullName}'");
|
||||||
|
|
||||||
converted = new CilType();
|
converted = new CilType();
|
||||||
@ -89,11 +93,12 @@ namespace SpaceFlint.CilToJava
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var metadataType = AsDefinition(fromType).MetadataType;
|
var defType = AsDefinition(fromType);
|
||||||
|
var metadataType = defType.MetadataType;
|
||||||
bool isValueType = (metadataType == MetadataType.ValueType);
|
bool isValueType = (metadataType == MetadataType.ValueType);
|
||||||
if (isValueType || (metadataType == MetadataType.Class))
|
if (isValueType || (metadataType == MetadataType.Class))
|
||||||
{
|
{
|
||||||
ImportClass(fromType, isValueType);
|
ImportClass(fromType, defType, isValueType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -143,12 +148,10 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ImportClass(TypeReference fromType, bool isValue)
|
void ImportClass(TypeReference fromType, TypeDefinition defType, bool isValue)
|
||||||
{
|
{
|
||||||
int numGeneric = ImportGenericParameters(fromType);
|
int numGeneric = ImportGenericParameters(fromType);
|
||||||
|
|
||||||
var defType = AsDefinition(fromType);
|
|
||||||
|
|
||||||
if (defType.HasCustomAttribute(
|
if (defType.HasCustomAttribute(
|
||||||
"System.Runtime.Remoting.Contexts.SynchronizationAttribute", true))
|
"System.Runtime.Remoting.Contexts.SynchronizationAttribute", true))
|
||||||
throw CilMain.Where.Exception($"attribute [Synchronization] is not supported");
|
throw CilMain.Where.Exception($"attribute [Synchronization] is not supported");
|
||||||
@ -226,9 +229,7 @@ namespace SpaceFlint.CilToJava
|
|||||||
Flags |= INTERFACE;
|
Flags |= INTERFACE;
|
||||||
|
|
||||||
else if (IsDelegateClass(defType))
|
else if (IsDelegateClass(defType))
|
||||||
{
|
|
||||||
Flags |= DELEGATE;
|
Flags |= DELEGATE;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimitiveType = TypeCode.Empty;
|
PrimitiveType = TypeCode.Empty;
|
||||||
@ -434,7 +435,8 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
for (int i = 0; i <= n; i++)
|
for (int i = 0; i <= n; i++)
|
||||||
{
|
{
|
||||||
if (SuperTypes[i].HasGenericParameters || SuperTypes[i].HasGenericSuperType)
|
//if (SuperTypes[i].HasGenericParameters || SuperTypes[i].HasGenericSuperType)
|
||||||
|
if (SuperTypes[i].IsGenericThisOrSuper)
|
||||||
Flags |= HAS_GEN_SUP;
|
Flags |= HAS_GEN_SUP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -607,21 +609,67 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static Dictionary<TypeReference, TypeDefinition> _TypeDefs =
|
||||||
|
new Dictionary<TypeReference, TypeDefinition>();
|
||||||
|
|
||||||
internal static TypeDefinition AsDefinition(TypeReference _ref)
|
internal static TypeDefinition AsDefinition(TypeReference _ref)
|
||||||
{
|
{
|
||||||
if (_ref.IsDefinition)
|
if (_ref.IsDefinition)
|
||||||
return _ref as TypeDefinition;
|
return _ref as TypeDefinition;
|
||||||
var def = _ref.Resolve();
|
if (_TypeDefs.TryGetValue(_ref, out var def))
|
||||||
if (def != null)
|
|
||||||
return def;
|
return def;
|
||||||
if (_ref.GetElementType() is GenericParameter)
|
def = _ref.Resolve();
|
||||||
return AsDefinition(_ref.Module.TypeSystem.Object);
|
if (def == null && _ref.GetElementType() is GenericParameter)
|
||||||
|
def = AsDefinition(_ref.Module.TypeSystem.Object);
|
||||||
|
if (def != null)
|
||||||
|
{
|
||||||
|
_TypeDefs.Add(_ref, def);
|
||||||
|
return def;
|
||||||
|
}
|
||||||
throw CilMain.Where.Exception(
|
throw CilMain.Where.Exception(
|
||||||
$"could not resolve type '{_ref}' from assembly '{_ref.Scope}'");
|
$"could not resolve type '{_ref}' from assembly '{_ref.Scope}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static CilType[] _Primitives = new CilType[32];
|
||||||
|
|
||||||
|
private static CilType GetCachedPrimitive(TypeReference fromType)
|
||||||
|
{
|
||||||
|
var metadataType = fromType.MetadataType;
|
||||||
|
switch (metadataType)
|
||||||
|
{
|
||||||
|
case MetadataType.Void:
|
||||||
|
case MetadataType.Boolean:
|
||||||
|
case MetadataType.Char:
|
||||||
|
case MetadataType.SByte:
|
||||||
|
case MetadataType.Byte:
|
||||||
|
case MetadataType.Int16:
|
||||||
|
case MetadataType.UInt16:
|
||||||
|
case MetadataType.Int32:
|
||||||
|
case MetadataType.UInt32:
|
||||||
|
case MetadataType.Int64:
|
||||||
|
case MetadataType.UInt64:
|
||||||
|
case MetadataType.Single:
|
||||||
|
case MetadataType.Double:
|
||||||
|
case MetadataType.String:
|
||||||
|
case MetadataType.IntPtr:
|
||||||
|
case MetadataType.UIntPtr:
|
||||||
|
case MetadataType.Object:
|
||||||
|
|
||||||
|
var resultType = _Primitives[(int) metadataType];
|
||||||
|
if (resultType == null)
|
||||||
|
{
|
||||||
|
resultType = new CilType();
|
||||||
|
resultType.Import(AsDefinition(fromType));
|
||||||
|
_Primitives[(int) metadataType] = resultType;
|
||||||
|
}
|
||||||
|
return resultType;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void SetBoxedFlags(bool clonedAtTop)
|
protected void SetBoxedFlags(bool clonedAtTop)
|
||||||
=> Flags |= (VALUE | BYREF) | (clonedAtTop ? CLONED_TOP : 0);
|
=> Flags |= (VALUE | BYREF) | (clonedAtTop ? CLONED_TOP : 0);
|
||||||
|
|
||||||
|
@ -525,6 +525,22 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
int SaveMethodArguments(CilMethod callMethod)
|
int SaveMethodArguments(CilMethod callMethod)
|
||||||
{
|
{
|
||||||
|
// pop method arguments off the stack and into temporary locals.
|
||||||
|
// this is needed when we need to manipulate the object reference,
|
||||||
|
// which was pushed before the arguments, in some way. for example,
|
||||||
|
//
|
||||||
|
// .Net has a 'newobj' which allocates the object and then calls the
|
||||||
|
// constructor, but we have to pop all arguments, insert a jvm 'new'
|
||||||
|
// instruction, then push arguments before the call to constructor.
|
||||||
|
//
|
||||||
|
// another example is a virtual call which must manipulate the pushed
|
||||||
|
// object reference in some way. see calls to this method throughout
|
||||||
|
// this source file for details.
|
||||||
|
//
|
||||||
|
// this creates noise of 'load'/'store' instructions in the generated
|
||||||
|
// code. but disassembly using 'dexdump' shows that the Android 'D8'
|
||||||
|
// compiler generally optimizes all of this away.
|
||||||
|
|
||||||
int localIndex = -1;
|
int localIndex = -1;
|
||||||
int i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
|
int i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
@ -564,6 +580,8 @@ namespace SpaceFlint.CilToJava
|
|||||||
|
|
||||||
void LoadMethodArguments(CilMethod callMethod, int localIndex)
|
void LoadMethodArguments(CilMethod callMethod, int localIndex)
|
||||||
{
|
{
|
||||||
|
// push methods arguments, saved by SaveMethodArguments.
|
||||||
|
|
||||||
int i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
|
int i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
{
|
{
|
||||||
|
@ -631,11 +631,7 @@ namespace SpaceFlint.CilToJava
|
|||||||
else if (! TestForBranch(code, castClass, cilInst.Next))
|
else if (! TestForBranch(code, castClass, cilInst.Next))
|
||||||
{
|
{
|
||||||
ushort nextLabel = (ushort) cilInst.Next.Offset;
|
ushort nextLabel = (ushort) cilInst.Next.Offset;
|
||||||
int localIndex = locals.GetTempIndex(stackTop);
|
TestAndCast(code, castClass, stackTop, nextLabel, locals);
|
||||||
|
|
||||||
TestAndCast(code, castClass, stackTop, nextLabel, localIndex);
|
|
||||||
|
|
||||||
locals.FreeTempIndex(localIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -669,14 +665,41 @@ namespace SpaceFlint.CilToJava
|
|||||||
//
|
//
|
||||||
|
|
||||||
void TestAndCast(JavaCode code, JavaType castClass, JavaType stackTop,
|
void TestAndCast(JavaCode code, JavaType castClass, JavaType stackTop,
|
||||||
ushort nextLabel, int localIndex)
|
ushort nextLabel, CodeLocals locals)
|
||||||
{
|
{
|
||||||
code.NewInstruction(stackTop.StoreOpcode, null, (int) localIndex);
|
int localIndex = -1;
|
||||||
|
if (cilInst?.Previous is Mono.Cecil.Cil.Instruction prevInst)
|
||||||
|
{
|
||||||
|
(_, localIndex) = locals.GetLocalFromLoadInst(
|
||||||
|
prevInst.OpCode.Code, prevInst.Operand);
|
||||||
|
}
|
||||||
|
|
||||||
code.NewInstruction(0x01 /* aconst_null */, null, null);
|
bool usingTempIndex;
|
||||||
|
if (localIndex != -1)
|
||||||
|
{
|
||||||
|
// in the common case, the the preceding instruction loads
|
||||||
|
// a local or an argument with a known index number, so we
|
||||||
|
// don't have a allocate a temporary local variable. but
|
||||||
|
// we will have to swap the values after pushing null
|
||||||
|
usingTempIndex = false;
|
||||||
|
|
||||||
|
code.NewInstruction(0x01 /* aconst_null */, null, null);
|
||||||
|
code.NewInstruction(0x5F /* swap */, null, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// otherwise we need to allocate a temporary variable, save
|
||||||
|
// the top of stack into it, push null, and load the temp
|
||||||
|
usingTempIndex = true;
|
||||||
|
localIndex = locals.GetTempIndex(stackTop);
|
||||||
|
|
||||||
|
code.NewInstruction(stackTop.StoreOpcode, null, localIndex);
|
||||||
|
code.NewInstruction(0x01 /* aconst_null */, null, null);
|
||||||
|
code.NewInstruction(stackTop.LoadOpcode, null, localIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the stack top has two values: null and objref
|
||||||
code.StackMap.PushStack(castClass);
|
code.StackMap.PushStack(castClass);
|
||||||
|
|
||||||
code.NewInstruction(stackTop.LoadOpcode, null, (int) localIndex);
|
|
||||||
code.StackMap.PushStack(stackTop);
|
code.StackMap.PushStack(stackTop);
|
||||||
|
|
||||||
code.NewInstruction(0xC1 /* instanceof */, castClass, null);
|
code.NewInstruction(0xC1 /* instanceof */, castClass, null);
|
||||||
@ -688,10 +711,13 @@ namespace SpaceFlint.CilToJava
|
|||||||
code.NewInstruction(0x57 /* pop */, null, null);
|
code.NewInstruction(0x57 /* pop */, null, null);
|
||||||
code.StackMap.PopStack(CilMain.Where);
|
code.StackMap.PopStack(CilMain.Where);
|
||||||
|
|
||||||
code.NewInstruction(stackTop.LoadOpcode, null, (int) localIndex);
|
code.NewInstruction(stackTop.LoadOpcode, null, localIndex);
|
||||||
code.NewInstruction(0xC0 /* checkcast */, castClass, null);
|
code.NewInstruction(0xC0 /* checkcast */, castClass, null);
|
||||||
|
|
||||||
code.StackMap.PushStack(castClass);
|
code.StackMap.PushStack(castClass);
|
||||||
|
|
||||||
|
if (usingTempIndex)
|
||||||
|
locals.FreeTempIndex(localIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,8 +635,10 @@ namespace SpaceFlint.CilToJava
|
|||||||
public (CilType, int) GetLocalFromLoadInst(Code op, object data)
|
public (CilType, int) GetLocalFromLoadInst(Code op, object data)
|
||||||
{
|
{
|
||||||
int localIndex;
|
int localIndex;
|
||||||
if (op >= Code.Ldloc_0 && op <= Code.Ldloc_3)
|
if (op >= Code.Ldloc_0 && op <= Code.Ldloc_3)
|
||||||
localIndex = VariableIndex(op - Code.Ldloc_0);
|
localIndex = VariableIndex(op - Code.Ldloc_0);
|
||||||
|
else if (op >= Code.Ldarg_0 && op <= Code.Ldarg_3)
|
||||||
|
localIndex = ArgumentIndex(op - Code.Ldarg_0);
|
||||||
else if (data is ParameterDefinition dataArg)
|
else if (data is ParameterDefinition dataArg)
|
||||||
localIndex = ArgumentIndex(dataArg.Sequence);
|
localIndex = ArgumentIndex(dataArg.Sequence);
|
||||||
else if (data is VariableDefinition dataVar)
|
else if (data is VariableDefinition dataVar)
|
||||||
|
@ -12,6 +12,7 @@ namespace SpaceFlint.JavaBinary
|
|||||||
{
|
{
|
||||||
wtr.Where.Push("method body");
|
wtr.Where.Push("method body");
|
||||||
|
|
||||||
|
PerformOptimizations();
|
||||||
EliminateNops();
|
EliminateNops();
|
||||||
|
|
||||||
int codeLength = FillInstructions(wtr);
|
int codeLength = FillInstructions(wtr);
|
||||||
@ -619,6 +620,35 @@ namespace SpaceFlint.JavaBinary
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void PerformOptimizations()
|
||||||
|
{
|
||||||
|
int n = Instructions.Count;
|
||||||
|
var prevInst = Instructions[0];
|
||||||
|
for (int i = 1; i < n; i++)
|
||||||
|
{
|
||||||
|
var currInst = Instructions[i];
|
||||||
|
|
||||||
|
// find occurrences of iconst_0 or iconst_1 followed by i2l
|
||||||
|
// (which must not be a branch target), and convert to lconst_xx
|
||||||
|
|
||||||
|
if (currInst.Opcode == 0x85 /* i2l */)
|
||||||
|
{
|
||||||
|
if ( prevInst.Opcode == 0x12 /* ldc */
|
||||||
|
&& prevInst.Data is int intValue
|
||||||
|
&& (intValue == 0 || intValue == 1)
|
||||||
|
&& (! StackMap.HasBranchFrame(currInst.Label)))
|
||||||
|
{
|
||||||
|
// convert ldc of 0 or 1 into lconst_0 or lconst_1
|
||||||
|
prevInst.Opcode = (byte) (intValue + 0x09);
|
||||||
|
currInst.Opcode = 0x00; // nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevInst = currInst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void EliminateNops()
|
void EliminateNops()
|
||||||
{
|
{
|
||||||
// remove any nop instructions that are not a branch target,
|
// remove any nop instructions that are not a branch target,
|
||||||
|
14
README.md
14
README.md
@ -1,6 +1,6 @@
|
|||||||
# Bluebonnet
|
# Bluebonnet
|
||||||
|
|
||||||
This is an initial release of a partial implementation of the .NET platform on top of the Java Virtual Machine, and compatible with Android runtime. The **Bluebonnet** bytecode compiler translates .NET [CIL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) into [Java bytecode](https://en.wikipedia.org/wiki/Java_bytecode) in Java classes, and additional run-time support is provided by the **Baselib** library.
|
This is a partial implementation of the .NET platform on top of the Java Virtual Machine, and compatible with Android runtime. The **Bluebonnet** bytecode compiler translates .NET [CIL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) into [Java bytecode](https://en.wikipedia.org/wiki/Java_bytecode) in Java classes, and additional run-time support is provided by the **Baselib** library.
|
||||||
|
|
||||||
https://www.spaceflint.com/bluebonnet
|
https://www.spaceflint.com/bluebonnet
|
||||||
|
|
||||||
@ -12,16 +12,18 @@ https://www.spaceflint.com/bluebonnet
|
|||||||
- Simple interoperability with Java APIs.
|
- Simple interoperability with Java APIs.
|
||||||
- Tested with C# and F# programs.
|
- Tested with C# and F# programs.
|
||||||
|
|
||||||
## Requirements
|
## Requirements for Building
|
||||||
|
|
||||||
- Java 8 is required during building, to import Java classes from the `rt.jar` file.
|
- Java 8 is required during building, to import Java classes from the `rt.jar` file.
|
||||||
- Importing from Java 9 modules is not yet supported.
|
- Importing from Java 9 modules is not yet supported.
|
||||||
- The translated code can run on Java 8 or later version.
|
- The translated code can run on Java 8 or later version.
|
||||||
- Alternatively, `(ANDROID_HOME)/platforms/android-XX/android.jar` from Android SDK can be copied as `(JAVA_HOME)\jre\lib\rt.jar` file.
|
- Alternatively, `(ANDROID_HOME)/platforms/android-XX/android.jar` from Android SDK can be copied as `(JAVA_HOME)\jre\lib\rt.jar` file.
|
||||||
- Android build tools with support for `D8`/`R8` desugaring.
|
- Android build tools with support for `D8`/`R8` desugaring.
|
||||||
- Tested with build tools version 30.0.2 and platform API version 28.
|
- Tested with build tools version 30.0.2 and platform API version 30.
|
||||||
- .NET Framework 4.7.2
|
- .NET Framework (4.7 or later)
|
||||||
- Tested only on Windows at this time. Not thoroughly tested with .NET Core.
|
- Tested only on Windows at this time. May not necessarily build with .NET Core.
|
||||||
|
|
||||||
|
[Recent releases](https://github.com/spaceflint7/bluebonnet/releases) include a pre-built `Android.dll` reference assembly, created from the latest Android SDK, as well as all binaries needed for running Bluebonnet. See the Usage section below.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@ -64,4 +66,4 @@ See the [BNA](https://github.com/spaceflint7/bna) and [Unjum](https://github.com
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
For more information about using Bluebonnet, please see the [USAGE.md](USAGE.md) file. That document also records any known differences and deficiencies, compared to a proper .NET implementation.
|
For more information about using Bluebonnet, please see the [USAGE.md](USAGE.md) file. That document also records any known differences and deficiencies, compared to a proper .NET implementation. To use Bluebonnet in Android Studio, see [USAGE-ANDROID.md](USAGE-ANDROID.md).
|
311
USAGE-ANDROID.md
Normal file
311
USAGE-ANDROID.md
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
|
||||||
|
#### Goal
|
||||||
|
|
||||||
|
- Set up development of an Android app using a .NET language
|
||||||
|
|
||||||
|
- Either in Visual Studio or using the command line `dotnet` tool.
|
||||||
|
|
||||||
|
|
||||||
|
- Use Android Studio to build the app.
|
||||||
|
|
||||||
|
- With a Gradle plugin to compile .NET code.
|
||||||
|
|
||||||
|
- And a Gradle build task to convert the .NET code to Java compiled form.
|
||||||
|
|
||||||
|
|
||||||
|
- Most of development should be possible on Windows without requiring an Android device.
|
||||||
|
|
||||||
|
- Platform-specific parts can be qualified with preprocessor constants.
|
||||||
|
|
||||||
|
#### Environment Variables
|
||||||
|
|
||||||
|
- Set the environment variable `MSBUILD_EXE` to point to `MSBuild.exe` program file.
|
||||||
|
- For example, `C:\Program Files (x86)\Microsoft Visual Studio\\2019\Community\MSBuild\Current\Bin\MSBuild.exe`
|
||||||
|
|
||||||
|
|
||||||
|
- Set the environment variable `BLUEBONNET_DIR` to point to a directory containing `Bluebonnet.exe`, `PruneMerge.exe`, `Baselib.jar` and `Android.dll`.
|
||||||
|
|
||||||
|
- These files can be downloaded from the [Bluebonnet releases](https://github.com/spaceflint7/bluebonnet/releases) page.
|
||||||
|
|
||||||
|
#### .NET Project Using DotNet Tool
|
||||||
|
|
||||||
|
- Create a new directory for the project, and change to this directory.
|
||||||
|
|
||||||
|
- Create a .NET Core project called `DotNet`:
|
||||||
|
|
||||||
|
- `dotnet new console -n DotNet`
|
||||||
|
|
||||||
|
- You may use some other project type or language instead of a C# console app.
|
||||||
|
|
||||||
|
- The project must be named `DotNet`.
|
||||||
|
|
||||||
|
|
||||||
|
- Edit `DotNet.csproj` to add a reference to Android DLL created by Bluebonnet:
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Android">
|
||||||
|
<HintPath>$(BLUEBONNET_DIR)\Android.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
- Or, you can create the project using Visual Studio:
|
||||||
|
|
||||||
|
#### .NET Project Using Visual Studio
|
||||||
|
|
||||||
|
- Create a new project.
|
||||||
|
|
||||||
|
- Select a __C# Console Application__ template (.NET Framework or .NET Core).
|
||||||
|
|
||||||
|
- Specify `DotNet` for the project name, and check the box to __Place solution and project in the same directory__.
|
||||||
|
|
||||||
|
- Or specify some other project name and clear that checkbox, but make sure:
|
||||||
|
|
||||||
|
- The solution name is `DotNet`.
|
||||||
|
|
||||||
|
- In __Project Settings__ in Visual Studio, set the __Assembly name__ to `DotNet`.
|
||||||
|
|
||||||
|
|
||||||
|
- Complete the creation of the project and solution.
|
||||||
|
|
||||||
|
- Add a reference to `Android.dll` using Visual Studio, or by editing the project file as shown above.
|
||||||
|
|
||||||
|
#### Activity Class
|
||||||
|
|
||||||
|
- Leave the `Program.cs` file as it is.
|
||||||
|
|
||||||
|
- Otherwise you may get a compilation error about a missing `Main` method.
|
||||||
|
|
||||||
|
- The class in this file will be discarded from the output, unless explicitly referenced.
|
||||||
|
|
||||||
|
|
||||||
|
- Create a new file named `MainActivity.cs` and paste the following into it:
|
||||||
|
|
||||||
|
#if ANDROID
|
||||||
|
|
||||||
|
namespace com.whatever.example
|
||||||
|
{
|
||||||
|
public sealed class MainActivity : android.app.Activity
|
||||||
|
{
|
||||||
|
protected override void onCreate (android.os.Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
android.util.Log.i("EXAMPLE", ">>>>>>>> EXAMPLE ACTIVITY <<<<<<<<");
|
||||||
|
base.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006 // Must begin with uppercase letter
|
||||||
|
#pragma warning disable CA2211 // Non-constant fields should not be visible
|
||||||
|
|
||||||
|
[java.attr.Discard] // discard in output
|
||||||
|
public class R
|
||||||
|
{
|
||||||
|
[java.attr.Discard] // discard in output
|
||||||
|
public class layout
|
||||||
|
{
|
||||||
|
[java.attr.RetainType] public static int activity_main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#### Android Project in Android Studio
|
||||||
|
|
||||||
|
- Create a new project.
|
||||||
|
|
||||||
|
- Select the __Empty Activity__ template.
|
||||||
|
|
||||||
|
- This generates activity definitions in the `AndroidManifest.xml` file.
|
||||||
|
|
||||||
|
|
||||||
|
- For this example, set __Package name__ to `com.whatever.example`.
|
||||||
|
|
||||||
|
- Set the __Save location__ to the project root directory.
|
||||||
|
- Ignore the warning that this directory is not empty.
|
||||||
|
|
||||||
|
|
||||||
|
- Select __Java__ for the language and a reasonable minimum platform API version (e.g. __18__).
|
||||||
|
|
||||||
|
- Complete the creation of the project.
|
||||||
|
|
||||||
|
- Delete the Java class generated by Android Studio for the main activity.
|
||||||
|
|
||||||
|
- It should be located in the project class `app/java/com.whatever.example/MainActivity`
|
||||||
|
|
||||||
|
- Alternatively, delete the entire directory of Java source files - `app/src/main/java`
|
||||||
|
|
||||||
|
#### Gradle Build Script
|
||||||
|
|
||||||
|
- Open the __module__-specific build script.
|
||||||
|
|
||||||
|
- In Android Studio, this is the `build.gradle` file annotated with __Module__ (rather than Project).
|
||||||
|
|
||||||
|
- In the directory structure, this is `app/build.gradle` (rather than the top-level file with the same name).
|
||||||
|
|
||||||
|
- Note that the right file begins with a `plugins` section followed by an `android` section.
|
||||||
|
|
||||||
|
|
||||||
|
- Optional: Between the `android` section, and the `dependencies` section, insert:
|
||||||
|
|
||||||
|
buildDir = "${project.rootDir}/build/${project.name}"
|
||||||
|
|
||||||
|
- This sets the output build directory just below the top-level of the project.
|
||||||
|
|
||||||
|
- Otherwise the default is the `app/build` directory.
|
||||||
|
|
||||||
|
|
||||||
|
- At the top of the `dependencies` section, insert:
|
||||||
|
|
||||||
|
implementation files("$buildDir/dotnet/dotnet.jar")
|
||||||
|
|
||||||
|
- After the `dependencies` section, append:
|
||||||
|
|
||||||
|
task buildDotNet {
|
||||||
|
doLast {
|
||||||
|
delete("${buildDir}/dotnet/DotNet.jar")
|
||||||
|
exec {
|
||||||
|
workingDir "${project.rootDir}"
|
||||||
|
commandLine System.env.MSBUILD_EXE ?: 'msbuild.exe',
|
||||||
|
'DotNet', '-r',
|
||||||
|
'-p:OutputType=Library',
|
||||||
|
'-p:Configuration=Release',
|
||||||
|
'-p:DefineConstants=ANDROID',
|
||||||
|
"-p:OutputPath=$buildDir/dotnet",
|
||||||
|
"-p:IntermediateOutputPath=$buildDir/dotnet/intermediate/"
|
||||||
|
}
|
||||||
|
exec {
|
||||||
|
commandLine "${System.env.BLUEBONNET_DIR}/Bluebonnet.exe",
|
||||||
|
"${buildDir}/dotnet/DotNet.dll",
|
||||||
|
"${buildDir}/dotnet/DotNet0.jar"
|
||||||
|
}
|
||||||
|
def manifest = new XmlSlurper().parse(android.sourceSets.main.manifest.srcFile)
|
||||||
|
exec {
|
||||||
|
commandLine "${System.env.BLUEBONNET_DIR}/PruneMerge.exe",
|
||||||
|
"${buildDir}/dotnet/DotNet0.jar",
|
||||||
|
"${System.env.BLUEBONNET_DIR}/Baselib.jar",
|
||||||
|
"${buildDir}/dotnet/DotNet.jar",
|
||||||
|
":${manifest.@package}${manifest.application.activity.'@android:name'}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preBuild.dependsOn buildDotNet
|
||||||
|
|
||||||
|
|
||||||
|
- Note the use of environment variables in the build script:
|
||||||
|
|
||||||
|
- `MSBUILD_EXE` should specify the path to the `MSBuild.exe` program.
|
||||||
|
|
||||||
|
- `BLUEBONNET_DIR` should specify the directory containing __Bluebonnet__ binaries.
|
||||||
|
|
||||||
|
- These environment variables should be made visible to Android Studio.
|
||||||
|
|
||||||
|
#### Gradle Build Script - Sample
|
||||||
|
|
||||||
|
- After updating your `app/build.gradle` file, it should look (more or less) like this:
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.whatever.example"
|
||||||
|
minSdkVersion 18
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDir = "${project.rootDir}/build/${project.name}"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation files("$buildDir/dotnet/dotnet.jar")
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||||
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
|
testImplementation 'junit:junit:4.+'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
task buildDotNet {
|
||||||
|
doLast {
|
||||||
|
delete("${buildDir}/dotnet/DotNet.jar")
|
||||||
|
|
||||||
|
exec {
|
||||||
|
workingDir "${project.rootDir}"
|
||||||
|
commandLine System.env.MSBUILD_EXE ?: 'msbuild.exe',
|
||||||
|
'DotNet', '-r',
|
||||||
|
'-p:OutputType=Library',
|
||||||
|
'-p:Configuration=Release',
|
||||||
|
'-p:DefineConstants=ANDROID',
|
||||||
|
"-p:OutputPath=$buildDir/dotnet",
|
||||||
|
"-p:IntermediateOutputPath=$buildDir/dotnet/intermediate/"
|
||||||
|
}
|
||||||
|
exec {
|
||||||
|
commandLine "${System.env.BLUEBONNET_DIR}/Bluebonnet.exe",
|
||||||
|
"${buildDir}/dotnet/DotNet.dll",
|
||||||
|
"${buildDir}/dotnet/DotNet0.jar"
|
||||||
|
}
|
||||||
|
def manifest = new XmlSlurper().parse(android.sourceSets.main.manifest.srcFile)
|
||||||
|
exec {
|
||||||
|
commandLine "${System.env.BLUEBONNET_DIR}/PruneMerge.exe",
|
||||||
|
"${buildDir}/dotnet/DotNet0.jar",
|
||||||
|
"${System.env.BLUEBONNET_DIR}/Baselib.jar",
|
||||||
|
"${buildDir}/dotnet/DotNet.jar",
|
||||||
|
":${manifest.@package}${manifest.application.activity.'@android:name'}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preBuild.dependsOn buildDotNet
|
||||||
|
|
||||||
|
#### Gradle Build Script - Overview
|
||||||
|
|
||||||
|
- In place of Java source files, a new dependency was added on a JAR file - `dotnet.jar`.
|
||||||
|
|
||||||
|
- The `buildDotNet` task was defined to perform the following build commands:
|
||||||
|
|
||||||
|
- Run `MSBuild` on the .NET project, in `Release` configuration, with the preprocessor define `ANDROID`
|
||||||
|
|
||||||
|
- Run `Bluebonnet` on the resulting `dotnet.dll` to create `dotnet0.jar`
|
||||||
|
|
||||||
|
- Extract the class name for the main activity from the file `AndroidManifest.xml`
|
||||||
|
|
||||||
|
- Run `PruneMerge` to merge `Baselib.jar` (from Bluebonnet) and `dotnet0.jar` into the final `dotnet.jar`
|
||||||
|
|
||||||
|
- All unreferenced classes are discard from the output.
|
||||||
|
|
||||||
|
|
||||||
|
- The `buildDotNet` task was set to execute before the Gradle `preBuild` task.
|
||||||
|
|
||||||
|
#### Test It
|
||||||
|
|
||||||
|
- Compile and run the project in Android Studio.
|
||||||
|
|
||||||
|
- If all goes well, the example app should start in the emulator, and display Hello World!
|
||||||
|
|
||||||
|
- This layout comes from the `activity_main.xml` layout file, generated by Android Studio.
|
||||||
|
|
||||||
|
- `onCreate()` in the C# `MainActivity` class calls `setContentView` to inflate this layout.
|
||||||
|
|
||||||
|
|
||||||
|
- The __Logcat__ tab should show the debug log message printed by `MainActivity.onCreate()`
|
4
USAGE.md
4
USAGE.md
@ -12,7 +12,7 @@ By default, all types/classes in the input are processed; one or more wildcard `
|
|||||||
|
|
||||||
If `input_file` is a Java archive, then the output written to `output_file` is a [reference assembly](https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies) declaring all types in the input. For example,
|
If `input_file` is a Java archive, then the output written to `output_file` is a [reference assembly](https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies) declaring all types in the input. For example,
|
||||||
|
|
||||||
Bluebonnet (java_home)\jre\lib\rt.jar .obj\Javalib.dll
|
Bluebonnet $JAVA_HOME\jre\lib\rt.jar .obj\Javalib.dll
|
||||||
|
|
||||||
#### .NET Assembly to Java Code
|
#### .NET Assembly to Java Code
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ Bluebonnet recognizes the following attributes:
|
|||||||
|
|
||||||
- `[java.attr.AsInterfaceAttribute]` on a class causes it to be written in output as an interface. This allows the creation of default method implementations even with versions of C# that do not support this feature. See for example [Baselib/`IDisposable.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/IDisposable.cs).
|
- `[java.attr.AsInterfaceAttribute]` on a class causes it to be written in output as an interface. This allows the creation of default method implementations even with versions of C# that do not support this feature. See for example [Baselib/`IDisposable.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/IDisposable.cs).
|
||||||
|
|
||||||
- `[java.attr.RetainNameAttribute]` on a type or method indicates that renaming should be inhibited. For example, an interface method would be renamed to "<interfaceName><methodName>", to allow a class to implement multiple interfaces. However, this is not appropriate for implementing a Java interface whose methods already have a pre-set method name. See for example [Baselib/`IDisposable.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/IDisposable.cs).
|
- `[java.attr.RetainNameAttribute]` on a type or method indicates that renaming should be inhibited. For example, an interface method would be renamed to the concatenation of interface name and method name, to allow a class to implement multiple interfaces. However, this is not appropriate for implementing a Java interface whose methods already have a pre-set method name. See for example [Baselib/`IDisposable.cs`](https://github.com/spaceflint7/bluebonnet/blob/master/Baselib/src/System/IDisposable.cs).
|
||||||
|
|
||||||
These attributes are emitted when Bluebonnet exports Java declarations to a .NET assembly, so they are available when such an assembly is referenced during compilation.
|
These attributes are emitted when Bluebonnet exports Java declarations to a .NET assembly, so they are available when such an assembly is referenced during compilation.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user