Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Documentation/release-notes/4260.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
### Linker Behavior Change

Xamarin.Android has to consolidate changes to the Android APIs and how
they affect C# types. If you consider an example such as:

* API 28 has an `IFoo` interface in `Mono.Android.dll` for `v9.0`.
* C# code implements `IFoo` in class `Bar`.
* API 29 adds another method to `IFoo` in `Mono.Android.dll` for
`v10.0`.
* Apps built targeting API 30 / `v10.0` will fix up `Bar` to implement
the new method, but throw `Java.Lang.AbstractMethodError` if it was
called.

If the linker didn't make this change, you would instead hit a crash
when trying to create an instance of type `Bar` such as:

System.TypeLoadException: VTable setup of type 'Bar' failed.

Xamarin.Android has some performance improvements to the linker in
this area. Existing apps *should* not be affected.

Submit a [bug report][bug] if you find an issue in this area.

[bug]: https://github.com/xamarin/xamarin-android/wiki/Submitting-Bugs,-Feature-Requests,-and-Pull-Requests
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ bool HaveSameSignature (TypeReference iface, MethodDefinition iMethod, MethodDef
if (IsInOverrides (iMethod, tMethod))
return true;

if (iMethod.Name != tMethod.Name && (iMethod.DeclaringType == null || (iMethod.DeclaringType.DeclaringType == null ? (string.Format ("{0}.{1}", iface.FullName, iMethod.Name) != tMethod.Name) : (string.Format ("{0}.{1}.{2}", iMethod.DeclaringType.DeclaringType, iface.Name, iMethod.Name) != tMethod.Name))))
if (iMethod.Name != tMethod.Name)
return false;

if (!CompareTypes (iMethod.MethodReturnType.ReturnType, tMethod.MethodReturnType.ReturnType))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,69 @@ static void CreateAbstractIfaceImplementation (string assemblyPath, AssemblyDefi
}
}

[Test]
public void FixAbstractMethodsStep_Explicit ()
{
var path = Path.Combine (Path.GetFullPath (XABuildPaths.TestOutputDirectory), "temp", TestName);
var step = new FixAbstractMethodsStep ();
var pipeline = new Pipeline ();

Directory.CreateDirectory (path);

using (var context = new LinkContext (pipeline)) {

context.Resolver.AddSearchDirectory (path);

var myAssemblyPath = Path.Combine (path, "MyAssembly.dll");

using (var android = CreateFauxMonoAndroidAssembly ()) {
android.Write (Path.Combine (path, "Mono.Android.dll"));
CreateExplicitInterface (myAssemblyPath, android);
}

using (var assm = context.Resolve (myAssemblyPath)) {
step.Process (context);

var impl = assm.MainModule.GetType ("MyNamespace.MyClass");
Assert.AreEqual (2, impl.Methods.Count, "MyClass should contain 2 methods");
var method = impl.Methods.FirstOrDefault (m => m.Name == "MyNamespace.IMyInterface.MyMethod");
Assert.IsNotNull (method, "MyNamespace.IMyInterface.MyMethod should exist");
method = impl.Methods.FirstOrDefault (m => m.Name == "MyMissingMethod");
Assert.IsNotNull (method, "MyMissingMethod should exist");
}
}

Directory.Delete (path, true);
}

static void CreateExplicitInterface (string assemblyPath, AssemblyDefinition android)
{
using (var assm = AssemblyDefinition.CreateAssembly (new AssemblyNameDefinition ("NestedIFaceTest", new Version ()), "NestedIFaceTest", ModuleKind.Dll)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly "silly" question, but why are we using System.Reflection.Emit to create MyAssembly.dll instead of using some C# code?

(Yes, FixAbstractMethodsStep_NestedTypes() does that too, but now I wonder the same thing as well!)

Copy link
Member Author

@jonathanpeppers jonathanpeppers Feb 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using CodeDom... It seems like sacrificing too many chickens, it required me adding global alias for System.dll:

jonathanpeppers@b259e62

ILMerge puts CodeDom types in Xamarin.Android.Build.Tasks.dll:

https://github.com/xamarin/xamarin-android/blob/19cd98b0f0f7709c9383185c64c5500ab9cae00f/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets#L308

I didn't get to the point of how to "add" a new method to an interface of an existing assembly. I guess it would overwrite Mono.Android.dll with a new one?

Using only Mono.Cecil it's easy to fabricate the types to make FixAbstractMethods do its work.

var void_type = assm.MainModule.ImportReference (typeof (void));

// Create interface
var iface = new TypeDefinition ("MyNamespace", "IMyInterface", TypeAttributes.Interface);

var iface_method = new MethodDefinition ("MyMethod", MethodAttributes.Abstract, void_type);
iface.Methods.Add (iface_method);
iface.Methods.Add (new MethodDefinition ("MyMissingMethod", MethodAttributes.Abstract, void_type));

assm.MainModule.Types.Add (iface);

// Create implementing class
var jlo = assm.MainModule.Import (android.MainModule.GetType ("Java.Lang.Object"));
var impl = new TypeDefinition ("MyNamespace", "MyClass", TypeAttributes.Public, jlo);
impl.Interfaces.Add (new InterfaceImplementation (iface));

var explicit_method = new MethodDefinition ("MyNamespace.IMyInterface.MyMethod", MethodAttributes.Abstract, void_type);
explicit_method.Overrides.Add (new MethodReference (iface_method.Name, void_type, iface));
impl.Methods.Add (explicit_method);

assm.MainModule.Types.Add (impl);
assm.Write (assemblyPath);
}
}

static AssemblyDefinition CreateFauxMonoAndroidAssembly ()
{
var assm = AssemblyDefinition.CreateAssembly (new AssemblyNameDefinition ("Mono.Android", new Version ()), "DimTest", ModuleKind.Dll);
Expand Down