Search code examples
c#cilmono.cecil

Pinned variables in Mono.Cecil


How can i make a local variable pinned using Mono.Cecil ?

enter image description here


Solution

  • Full example:

    This:

    public unsafe static void Assign2(byte[] arr) 
    {
        fixed (byte* ptr = arr)
            *ptr = 255;
    

    can be translated to:

    var assembly = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition("Test", new Version()), "Test", ModuleKind.Dll);
    
    var module = assembly.MainModule;
    
    var testClassType = new TypeDefinition(
        "TestNamespace",
        "TestClass",
        TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed,
        module.ImportReference(typeof(object)));
    
    module.Types.Add(testClassType);
    
    var assignMethod = new MethodDefinition(
        "Assign",
        MethodAttributes.Public | MethodAttributes.Static,
        module.ImportReference(typeof(void)));
    
    assignMethod.Parameters.Add(new ParameterDefinition("arr", ParameterAttributes.None, module.ImportReference(typeof(byte[]))));
    
    testClassType.Methods.Add(assignMethod);
    
    // Get ILProcessor for the method body
    var ilProcessor = assignMethod.Body.GetILProcessor();
    
    ilProcessor.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(byte).MakePointerType())));
    
    // THIS IS WHAT YOU WANT!
    // using Mono.Cecil.Rocks or new PinnedType(module.ImportReference(...))
    ilProcessor.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(byte[])).MakePinnedType()));
    
    var instrLabelA = ilProcessor.Create(OpCodes.Ldc_I4_0);
    var instrLabelB = ilProcessor.Create(OpCodes.Ldloc_1);
    var instrLabelC = ilProcessor.Create(OpCodes.Ldloc_0);
    
    ilProcessor.Emit(OpCodes.Ldarg_0);
    ilProcessor.Emit(OpCodes.Dup);
    ilProcessor.Emit(OpCodes.Stloc_1);
    ilProcessor.Emit(OpCodes.Brfalse_S, instrLabelA);
    
    ilProcessor.Emit(OpCodes.Ldloc_1);
    ilProcessor.Emit(OpCodes.Ldlen);
    ilProcessor.Emit(OpCodes.Conv_I4);
    ilProcessor.Emit(OpCodes.Brtrue_S, instrLabelB);
    
    ilProcessor.Append(instrLabelA);
    ilProcessor.Emit(OpCodes.Conv_U);
    ilProcessor.Emit(OpCodes.Stloc_0);
    ilProcessor.Emit(OpCodes.Br_S, instrLabelC);
    
    ilProcessor.Append(instrLabelB);
    ilProcessor.Emit(OpCodes.Ldc_I4_0);
    ilProcessor.Emit(OpCodes.Ldelema, module.ImportReference(typeof(byte)));
    ilProcessor.Emit(OpCodes.Conv_U);
    ilProcessor.Emit(OpCodes.Stloc_0);
    
    ilProcessor.Append(instrLabelC);
    ilProcessor.Emit(OpCodes.Ldc_I4, 255);
    ilProcessor.Emit(OpCodes.Stind_I1);
    ilProcessor.Emit(OpCodes.Ldnull);
    ilProcessor.Emit(OpCodes.Stloc_1);
    ilProcessor.Emit(OpCodes.Ret);
    
    // Save the assembly to disk
    assembly.Write(@"test.dll");
    

    The IL code was taken from SharpLab.

    What you want is:

    ilProcessor.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(byte[])).MakePinnedType()));
    

    remember to use

    using Mono.Cecil.Rocks;
    

    Without using Rocks you can directly:

    ilProcessor.Body.Variables.Add(new VariableDefinition(new PinnedType(module.ImportReference(typeof(byte[])))));
    

    Note that this:

    public unsafe static void Assign(byte[] arr) 
    {
        fixed (byte* ptr = &arr[0])
            *ptr = 255;
    

    should generate the same code, but it doesn't.

    var ilProcessor = assignMethod.Body.GetILProcessor();
    
    // THIS IS WHAT YOU WANT!
    // using Mono.Cecil.Rocks or new PinnedType(module.ImportReference(...))
    ilProcessor.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(byte)).MakePinnedType()));
    
    ilProcessor.Emit(OpCodes.Ldarg_0);
    ilProcessor.Emit(OpCodes.Ldc_I4_0);
    ilProcessor.Emit(OpCodes.Ldelema, module.ImportReference(typeof(byte)));
    ilProcessor.Emit(OpCodes.Stloc_0);
    ilProcessor.Emit(OpCodes.Ldloc_0);
    ilProcessor.Emit(OpCodes.Conv_U);
    ilProcessor.Emit(OpCodes.Ldc_I4, 255);
    ilProcessor.Emit(OpCodes.Stind_I1);
    
    ilProcessor.Emit(OpCodes.Ldc_I4_0);
    ilProcessor.Emit(OpCodes.Conv_U);
    ilProcessor.Emit(OpCodes.Stloc_0);
    ilProcessor.Emit(OpCodes.Ret);
    

    I'll say that this happens because the case arr == null || arr.Length == 0 must be explicitly handled by code in the first case, while it is handled by the .NET (that will throw an exception), in the case &arr[0].