我一直在尝试使用 PowerShell 将 CIL 发射到 DynamicMethod 中,然后运行它,并且基本操作有效,所以我相信该方法是可行的。我可以将 int 和 string 值压入堆栈并打印它们,这涉及找到 [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)
那是插入分隔符,加载数组引用,调用连接,(存储/加载结果字符串),调用 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 的两行代码,那么代码运行良好并且只打印分隔符字符串 ;
。所以那部分看起来没问题。我相当确定数组设置代码也可以,但不是绝对确定。它至少没有以错误的方式获取值和索引(抛出索引越界异常)。我发出的 OpCodes 看起来像用于将值存储在数组中的 C# 反汇编。
那么,如在 C# 反汇编中所见,发出对类型为 int32 的 String.Join 调用的正确 PowerShell 代码是什么?
请您参考如下方法:
需要对数组[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>
.