Search code examples
c#mono.cecil

Mono.Cecil in C#: How to call method of super class from another assembly


Assume you have a simple class ClassA in AssemblyA that should be extended using Mono.Cecil to call a method on its super class ClassB that is part of another assembly AssemblyB.

In fact, I am trying to call MonoBehaviour::GetComponent from my own script in a Unity project. MonoBehaviour is in UnityEngine.CoreModule assembly

This is my code:

var processor = hookMethod.Body.GetILProcessor();

// find "GetComponent" in BaseType (which is MonoBehaviour)
var getComponentMethod = typeDefinition.BaseType.Resolve().Methods.First(it => it.Name == "GetComponent");

var firstInstruction = hookMethod.Body.Instructions[0];

var thisCall = processor.Create(OpCodes.Ldarg_0);
var methodCall = processor.Create(OpCodes.Call, getComponentMethod);

processor.InsertBefore(firstInstruction, methodCall);
processor.InsertBefore(methodCall, thisCall);

Unfortunately, I get the following message:

AssemblyResolutionException: Failed to resolve assembly: 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
Mono.Cecil.BaseAssemblyResolver.Resolve (Mono.Cecil.AssemblyNameReference name, Mono.Cecil.ReaderParameters parameters) (at <6f6cad7a41144167841153769a3850ff>:0)
Mono.Cecil.BaseAssemblyResolver.Resolve (Mono.Cecil.AssemblyNameReference name) (at <6f6cad7a41144167841153769a3850ff>:0)
Mono.Cecil.DefaultAssemblyResolver.Resolve (Mono.Cecil.AssemblyNameReference name) (at <6f6cad7a41144167841153769a3850ff>:0)
Mono.Cecil.MetadataResolver.Resolve (Mono.Cecil.TypeReference type) (at <6f6cad7a41144167841153769a3850ff>:0)
Mono.Cecil.ModuleDefinition.Resolve (Mono.Cecil.TypeReference type) (at <6f6cad7a41144167841153769a3850ff>:0)
Mono.Cecil.TypeReference.Resolve () (at <6f6cad7a41144167841153769a3850ff>:0)
MissingBracket.Injection.Editor.InjectionProcessor.Process (Mono.Cecil.TypeDefinition typeDefinition, Mono.Cecil.ModuleDefinition moduleDefinition, System.Boolean& modified) (at Assets/Plugins/Missing Bracket/Injection/Editor/InjectionProcessor.cs:33)
MissingBracket.Common.Editor.Weaver.Weave (System.Reflection.Assembly[] assemblies, Mono.Cecil.AssemblyDefinition assemblyDefinition, Mono.Cecil.IAssemblyResolver asmResolver, System.Boolean& modified) (at Assets/Plugins/Missing Bracket/Common/Editor/Weaver/Weaver.cs:35)
MissingBracket.Common.Editor.WeaverExecutor.WeaveFromFile (System.String assemblyPath) (at Assets/Plugins/Missing Bracket/Common/Editor/Weaver/WeaverExecutor.cs:52)
MissingBracket.Common.Editor.WeaverExecutor.OnCompilationFinished (System.String assemblyPath, UnityEditor.Compilation.CompilerMessage[] messages) (at Assets/Plugins/Missing Bracket/Common/Editor/Weaver/WeaverExecutor.cs:40)
UnityEditor.Compilation.CompilationPipeline+<>c.<SubscribeToEvents>b__26_3 (UnityEditor.Scripting.ScriptCompilation.ScriptAssembly scriptAssembly, UnityEditor.Compilation.CompilerMessage[] messages) (at <97436df440ca462884c5332c1d8ebbe7>:0)
UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface:TickCompilationPipeline(EditorScriptCompilationOptions, BuildTargetGroup, BuildTarget, Int32, String[], Boolean)

After some research I found that I need to import a reference to the base type from that other assemblie's module.:

var methodRef = typeDefinition.BaseType.Module.ImportReference(typeDefinition.BaseType).Resolve();
var getComponentMethod = methodRef.Methods.First(it => it.Name == "GetComponent");

This results in the exact same error message from above.

Any ideas?


Solution

  • Turns out the trick is to add the .dll-File where the required module is to the search path. This way resolving a type from this module is possible:

    using (var asmResolver = new DefaultAssemblyResolver())
    using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
    {
        asmResolver.AddSearchDirectory(PATH_TO_SEARCH_DIRECTORY);           
        // do your logic here
    }
    

    In my case I needed the UnityEngine.CodeModule assembly. You can place a path to the containing directory there or you can search it by looping over all assemblies (do it once and store the path because might take a while):

    var assemblies = AppDomain.CurrentDomain.GetAssemblies();
    var core = assemblies.FirstOrDefault(it => it.FullName.Contains("UnityEngine.CoreModule"));
    
    if (core == null) 
        throw new ArgumentException("Cannot load unity core dll");
    
    var directoryName = Path.GetDirectoryName(core.CodeBase);
    var searchPath = directoryName?.Replace(@"file:\", "");
    

    After doing so you can resolve any type that is contained in any .dll-file from any directory of your search path:

    var typeDefinition = moduleDefinition.ImportReference(typeof(ClassFromAnotherModule)).Resolve();