Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin/MonoTouch: Unable to compile in "Link SDK only" mode due to missing symbols

I am trying to compile my iOS application using MonoTouch in Link SDK only mode. It compiles completely fine if I turn off linking entirely but it also produces a colossal binary which is not suitable for release mode.

Unfortunately, one of the libraries that I need somehow invokes Expression.Visit and I can't figure out how to instruct the linker to not strip it out. This results in this compilation error:

Error MT2002: Failed to resolve "System.Linq.Expressions.Expression System.Linq.Expressions.ExpressionVisitor::Visit(System.Linq.Expressions.Expression)" reference from "System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e" (MT2002) (MyApp)

As per the documentation on custom linking, I have set up a linker file to try to stop this happening:

<?xml version="1.0" encoding="UTF-8" ?>
<linker>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.ExpressionVisitor">
      <method signature="System.Linq.Expressions.Expression Visit(System.Linq.Expressions.Expression)" />
    </type>
  </assembly>
</linker>

Unfortunately, that doesn't have any effect - the error just happens as before.

The documentation mentions a preserve="fields" parameter I can supply to <type>, so I tried that:

<?xml version="1.0" encoding="UTF-8" ?>
<linker>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.ExpressionVisitor" preserve="methods" />
  </assembly>
</linker>

That didn't work either.

I then introduced an XML syntax error into the file and the build failed saying it couldn't parse the linker file so I at least know that the file is being read.

But then I tried introducing errors into the assembly name:

<?xml version="1.0" encoding="UTF-8" ?>
<linker>
  <assembly fullname="NonexistentAssembly">
  </assembly>
</linker>

That caused an error, complaining that the assembly couldn't be resolved.

I then tried to mangle the type name:

<?xml version="1.0" encoding="UTF-8" ?>
<linker>
  <assembly fullname="System.Core">
    <type fullname="NonExistentType" preserve="methods" />
  </assembly>
</linker>

Now, that just started showing the same "unable to resolve expressionvisitor::visit" error I was seeing before. It seems that type names are not checked.

Also, neither are signature names as this also presented the same compilation error.

<?xml version="1.0" encoding="UTF-8" ?>
<linker>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.ExpressionVisitor">
      <method signature="jg98j23890rj2390erk90fk3ew!]['##'#'[" />
    </type>
  </assembly>
</linker>

So it seems that so long as you specify the assembly name and have a valid XML structure, the linker file does not cause any exceptions to be thrown. The documentation for the linker file though is not especially verbose for such a complex topic and includes numerous syntax errors, i.e.:

<type fullname="Foo" preserve="fields" />
    <method name=".ctor" />
</type>

Also, it doesn't give a technical description of the linker file format so it's entirely possible that my definition is totally wrong.

I have also tried just skipping the linking of System.Core entirely with --linkskip=System.Core but this has no effect. I have tried this flag both with and without --xml.

In the MvvmCross project, I tried specifying a new class to call the Visit method in the same style as the LinkerPleaseInclude.cs file that gets added to every iOS project by the MvvmCross NuGet package:

[Preserve]
public class PleaseIncludeVisitor : ExpressionVisitor
{
    public PleaseIncludeVisitor()
    {
        this.Visit(null);
    }
}

Unfortunately, this also had no effect.

I am currently using Mono 3.2.6 ((no/9b58377) and Xamarin.iOS v7.0.7.2.

like image 776
Steve Rukuts Avatar asked Feb 27 '14 11:02

Steve Rukuts


1 Answers

The main issue is that it is not possible to do code generation at runtime on iOS.

That means that a large part of System.Linq.Expressions would, normally, be impossible to provide under normal circumstances (just like System.Refection.Emit can't be provided).

To workaround this Xamarin.iOS has been providing an alternative implementation (that can interpret the most common expressions).

However the current code is not 100% API compatible with the .NET framework (that's fixed but not yet released). E.g.

System.Linq.Expressions.Expression System.Linq.Expressions.ExpressionVisitor::Visit(System.Linq.Expressions.Expression)

The above returns void in the current Xamarin.iOS (7.0.x) releases. That why the linker complains (it's not a bug) as it cannot find this member reference (and if it can't find it it cannot recreate the smaller, linked, assemblies). It is not possible to preserve a member that does not exist (which is why your attempts to use XML files or attributes won't help).

The current workaround is to identify which of your assemblies is using this API and rebuild it (from source) to use the existing System.Core.dll that is shipped with Xamarin.iOS.

Notes

I can't figure out how to instruct the linker to not strip it out.

The linker does not try to strip it out it tries to keep it in (i.e. it must load the reference to be able to save it back). However it cannot find that member in System.Core.dll making it impossible to provide a valid linked version of the assemblies.

<method signature="System.Linq.Expressions.Expression Visit(System.Linq.Expressions.Expression)" />

That's asking to preserve something that does not exist. It will be ignored, i.e. when the void-returning version is found it will not match the XML description.

like image 115
poupou Avatar answered Nov 12 '22 12:11

poupou