PowerShell-如何反射。发出正确的IL OpCode以在DynamicMethod中调用[String] :: Join?

时间:2018-12-21 19:25:54

标签: .net powershell reflection.emit

我一直在使用PowerShell将CIL发出到DynamicMethod中,然后运行它,并且基本的操作都可以正常工作,因此我相信这种方法是可以的。我可以将int和字符串值压入堆栈并进行打印,这涉及找到[console]::WriteLine([string])[console]::WriteLine([int32])的正确重载-即获取所有重载并按其参数类型进行过滤。

现在我正在尝试调用[String]::Join(),但这是一个泛型方法,需要键入它才能加入[int32[]]数组。 C#反汇编显示CIL指令为:

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)

这将推动分隔符,加载数组引用,调用join,(存储/加载结果字符串),调用WriteLine。

我的PowerShell(很多代码,因为它在CIL中设置了数组,但是它很小,我可以制作一个完整的可运行示例),如下所示:

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()

它抛出:

Exception calling "Invoke" with "0" argument(s): "Operation could destabilize the runtime."

如果注释掉加载数组并调用Join的两行,则代码运行良好,仅打印分隔符字符串;。因此,该部分似乎正常。我相当确定数组设置代码也可以,但是不能完全确定。至少没有正确地获得值和索引(将Index超出范围抛出异常)。而且我发出的OpCode看起来像C#反汇编,用于将值存储在数组中。

那么,什么是正确的PowerShell代码才能发出对String.Join的调用,如在C#反汇编中看到的那样,输入类型为int32?

1 个答案:

答案 0 :(得分:0)

需要将其键入数组[int]中的项目,而不是整个数组[int[]]。现在,更改我的PowerShell以删除多余的[]即可:

$IL.EmitCall([opcodes]::Call, [string].GetDeclaredMethods('Join').where{
    $_.IsGenericMethod
}[0].MakeGenericMethod([int32]), $null)

我是怎么找到的

我以this article on C-SharpCorner为指导,将DynamicMethod另存为.exe,然后在其上使用ILDasm将生成的代码与C#版本进行了比较。

我需要的位置:

string [mscorlib]System.String::Join<int32>(string,
                            class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)

我正在得到这个:

string [mscorlib]System.String::Join<int32[]>(string,
                            class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)

特别是Join<int32[]>而不是Join<int32>