I've been playing with using PowerShell to Emit CIL into a DynamicMethod and then run it, and basic operations work, so I'm confident the approach is OK. I can push int and string values onto the stack and print them, which involves finding the right overloads of [console]::WriteLine([string]) and [console]::WriteLine([int32]) - i.e. getting all the overloads and filtering by their parameter types.
Now I'm trying to call [String]::Join() but this is a Generic method and needs to be typed to join an array of [int32[]]. A C# disassembly shows that the CIL instructions are:
IL_0018: ldstr ", "
IL_001d: ldloc.2
IL_001e: call string [mscorlib]System.String::Join<int32>(string,
class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0023: stloc.3
IL_0024: ldloc.3
IL_0025: call void [mscorlib]System.Console::WriteLine(string)
That's pushing the separator, loading the array reference, calling join, (store/load the result string), calling WriteLine.
My PowerShell (a lot of code because it sets up the array in CIL, but it's as small as I can make a complete runnable example), looks like this:
using namespace System.Reflection.Emit
# new dynamic method
$methodInfo = new-object Reflection.Emit.DynamicMethod -ArgumentList @('Test', [int], @())
$IL = $methodInfo.GetILGenerator()
# new array of [int32[]] items, stored in a local variable
$arrayVar = $IL.DeclareLocal([int32[]])
$IL.Emit([OpCodes]::Ldc_I4, 2)
$IL.Emit([OpCodes]::Newarr, [int32])
$IL.Emit([OpCodes]::Stloc, $arrayVar)
# Store 4 into index 0
$IL.Emit([OpCodes]::Ldloc, $arrayVar)
$IL.Emit([OpCodes]::Ldc_I4, 0)
$IL.Emit([OpCodes]::Ldc_I4_4)
$IL.Emit([OpCodes]::Stelem, [int32])
# Store 5 into index 1
$IL.Emit([OpCodes]::Ldloc, $arrayVar)
$IL.Emit([OpCodes]::Ldc_I4, 1)
$IL.Emit([OpCodes]::Ldc_I4_5)
$IL.Emit([OpCodes]::Stelem, [int32])
#
# *** question here ***
#
# The bit I can't get to work - [String]::Join('; ', $array)
# Push separator string onto the stack, then load the array, then call the Join method
$IL.Emit([OpCodes]::Ldstr, '; ')
$IL.Emit([OpCodes]::Ldloc, $arrayVar)
$IL.EmitCall([opcodes]::Call, [string].GetDeclaredMethods('Join').where{$_.IsGenericMethod}[0].MakeGenericMethod([int32[]]), $null)
# Should leave a string on the stack; print it.
$IL.EmitCall([OpCodes]::Call, [console].GetDeclaredMethods('WriteLine').where{$p = $_.GetParameters(); $p.Count -eq 1 -and $p.ParameterType -eq [string]}[0], $null)
# return 0
$IL.Emit([OpCodes]::Ldc_I4_0)
$IL.Emit([OpCodes]::Ret)
# run code
$Test = $methodInfo.CreateDelegate([System.Func[[int]]])
$Test.Invoke()
and it throws:
Exception calling "Invoke" with "0" argument(s): "Operation could destabilize the runtime."
If you comment out the two lines which load the array and call to Join, then the code runs fine and simply prints the separator string ;. So that part appears to be OK. I'm fairly sure the array setup code is also OK, but not absolutely sure. It's at least not got the value and index the wrong way round (that throws an Index out of bounds exception). And the OpCodes I emit look like the C# disassembly for storing values in an array.
So, what is the right PowerShell code to emit the call to String.Join typed to int32 as seen in the C# disassembly?