Optimizations
This commit is contained in:
parent
30f187a636
commit
3d84879ed8
@ -320,11 +320,15 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
public static List<CilInterfaceMethod> CollectAll(TypeDefinition fromType)
|
||||
{
|
||||
var list = new List<CilInterfaceMethod>();
|
||||
Process(fromType, list);
|
||||
return list;
|
||||
var map = new Dictionary<string, List<CilInterfaceMethod>>();
|
||||
Process(fromType, map);
|
||||
|
||||
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)
|
||||
{
|
||||
@ -343,19 +347,26 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
var outputMethod = new CilInterfaceMethod(inputMethod);
|
||||
|
||||
bool dup = false;
|
||||
foreach (var oldMethod in list)
|
||||
if (map.TryGetValue(inputMethod.Name, out var list))
|
||||
{
|
||||
if ( oldMethod.Method.Name == outputMethod.Method.Name
|
||||
&& oldMethod.EqualParameters(outputMethod))
|
||||
bool dup = false;
|
||||
foreach (var oldMethod in list)
|
||||
{
|
||||
dup = true;
|
||||
break;
|
||||
if (oldMethod.EqualParameters(outputMethod))
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -369,7 +380,7 @@ namespace SpaceFlint.CilToJava
|
||||
var genericMark = CilMain.GenericStack.Mark();
|
||||
CilMain.GenericStack.EnterType(fromBaseTypeDef);
|
||||
|
||||
Process(fromBaseTypeDef, list);
|
||||
Process(fromBaseTypeDef, map);
|
||||
|
||||
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)
|
||||
{
|
||||
if (_ref.IsDefinition)
|
||||
return _ref as MethodDefinition;
|
||||
var def = _ref.Resolve();
|
||||
if (def != null)
|
||||
if (_MethodDefs.TryGetValue(_ref, out var def))
|
||||
return def;
|
||||
def = _ref.Resolve();
|
||||
if (def != null)
|
||||
{
|
||||
_MethodDefs.Add(_ref, def);
|
||||
return def;
|
||||
}
|
||||
throw CilMain.Where.Exception(
|
||||
$"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))
|
||||
{
|
||||
converted = GetCachedPrimitive(fromType);
|
||||
if (converted != null)
|
||||
return converted;
|
||||
|
||||
CilMain.Where.Push($"type '{fromType.FullName}'");
|
||||
|
||||
converted = new CilType();
|
||||
@ -89,11 +93,12 @@ namespace SpaceFlint.CilToJava
|
||||
}
|
||||
else
|
||||
{
|
||||
var metadataType = AsDefinition(fromType).MetadataType;
|
||||
var defType = AsDefinition(fromType);
|
||||
var metadataType = defType.MetadataType;
|
||||
bool isValueType = (metadataType == MetadataType.ValueType);
|
||||
if (isValueType || (metadataType == MetadataType.Class))
|
||||
{
|
||||
ImportClass(fromType, isValueType);
|
||||
ImportClass(fromType, defType, isValueType);
|
||||
}
|
||||
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);
|
||||
|
||||
var defType = AsDefinition(fromType);
|
||||
|
||||
if (defType.HasCustomAttribute(
|
||||
"System.Runtime.Remoting.Contexts.SynchronizationAttribute", true))
|
||||
throw CilMain.Where.Exception($"attribute [Synchronization] is not supported");
|
||||
@ -226,9 +229,7 @@ namespace SpaceFlint.CilToJava
|
||||
Flags |= INTERFACE;
|
||||
|
||||
else if (IsDelegateClass(defType))
|
||||
{
|
||||
Flags |= DELEGATE;
|
||||
}
|
||||
}
|
||||
|
||||
PrimitiveType = TypeCode.Empty;
|
||||
@ -434,7 +435,8 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -607,21 +609,67 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
|
||||
|
||||
static Dictionary<TypeReference, TypeDefinition> _TypeDefs =
|
||||
new Dictionary<TypeReference, TypeDefinition>();
|
||||
|
||||
internal static TypeDefinition AsDefinition(TypeReference _ref)
|
||||
{
|
||||
if (_ref.IsDefinition)
|
||||
return _ref as TypeDefinition;
|
||||
var def = _ref.Resolve();
|
||||
if (def != null)
|
||||
if (_TypeDefs.TryGetValue(_ref, out var def))
|
||||
return def;
|
||||
if (_ref.GetElementType() is GenericParameter)
|
||||
return AsDefinition(_ref.Module.TypeSystem.Object);
|
||||
def = _ref.Resolve();
|
||||
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(
|
||||
$"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)
|
||||
=> Flags |= (VALUE | BYREF) | (clonedAtTop ? CLONED_TOP : 0);
|
||||
|
||||
|
@ -525,6 +525,22 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
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 i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
|
||||
while (i-- > 0)
|
||||
@ -564,6 +580,8 @@ namespace SpaceFlint.CilToJava
|
||||
|
||||
void LoadMethodArguments(CilMethod callMethod, int localIndex)
|
||||
{
|
||||
// push methods arguments, saved by SaveMethodArguments.
|
||||
|
||||
int i = callMethod.Parameters.Count - (callMethod.HasDummyClassArg ? 1 : 0);
|
||||
while (i-- > 0)
|
||||
{
|
||||
|
@ -631,11 +631,7 @@ namespace SpaceFlint.CilToJava
|
||||
else if (! TestForBranch(code, castClass, cilInst.Next))
|
||||
{
|
||||
ushort nextLabel = (ushort) cilInst.Next.Offset;
|
||||
int localIndex = locals.GetTempIndex(stackTop);
|
||||
|
||||
TestAndCast(code, castClass, stackTop, nextLabel, localIndex);
|
||||
|
||||
locals.FreeTempIndex(localIndex);
|
||||
TestAndCast(code, castClass, stackTop, nextLabel, locals);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -669,14 +665,41 @@ namespace SpaceFlint.CilToJava
|
||||
//
|
||||
|
||||
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.NewInstruction(stackTop.LoadOpcode, null, (int) localIndex);
|
||||
code.StackMap.PushStack(stackTop);
|
||||
|
||||
code.NewInstruction(0xC1 /* instanceof */, castClass, null);
|
||||
@ -688,10 +711,13 @@ namespace SpaceFlint.CilToJava
|
||||
code.NewInstruction(0x57 /* pop */, null, null);
|
||||
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.StackMap.PushStack(castClass);
|
||||
|
||||
if (usingTempIndex)
|
||||
locals.FreeTempIndex(localIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,8 +635,10 @@ namespace SpaceFlint.CilToJava
|
||||
public (CilType, int) GetLocalFromLoadInst(Code op, object data)
|
||||
{
|
||||
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);
|
||||
else if (op >= Code.Ldarg_0 && op <= Code.Ldarg_3)
|
||||
localIndex = ArgumentIndex(op - Code.Ldarg_0);
|
||||
else if (data is ParameterDefinition dataArg)
|
||||
localIndex = ArgumentIndex(dataArg.Sequence);
|
||||
else if (data is VariableDefinition dataVar)
|
||||
|
@ -12,6 +12,7 @@ namespace SpaceFlint.JavaBinary
|
||||
{
|
||||
wtr.Where.Push("method body");
|
||||
|
||||
PerformOptimizations();
|
||||
EliminateNops();
|
||||
|
||||
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()
|
||||
{
|
||||
// remove any nop instructions that are not a branch target,
|
||||
|
14
README.md
14
README.md
@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@ -12,16 +12,18 @@ https://www.spaceflint.com/bluebonnet
|
||||
- Simple interoperability with Java APIs.
|
||||
- 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.
|
||||
- Importing from Java 9 modules is not yet supported.
|
||||
- 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.
|
||||
- Android build tools with support for `D8`/`R8` desugaring.
|
||||
- Tested with build tools version 30.0.2 and platform API version 28.
|
||||
- .NET Framework 4.7.2
|
||||
- Tested only on Windows at this time. Not thoroughly tested with .NET Core.
|
||||
- Tested with build tools version 30.0.2 and platform API version 30.
|
||||
- .NET Framework (4.7 or later)
|
||||
- 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
|
||||
|
||||
@ -64,4 +66,4 @@ See the [BNA](https://github.com/spaceflint7/bna) and [Unjum](https://github.com
|
||||
|
||||
## 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,
|
||||
|
||||
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
|
||||
|
||||
@ -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.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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user