PowerShell - Как я могу Reflection.Emit правильные IL-коды для вызова [String] :: Join в DynamicMethod? - PullRequest
0 голосов
/ 21 декабря 2018

Я играл с использованием PowerShell для передачи CIL в DynamicMethod, а затем запуска его, и основные операции работают, так что я уверен, что подход в порядке.Я могу поместить значения int и string в стек и распечатать их, что включает в себя поиск правильных перегрузок [console]::WriteLine([string]) и [console]::WriteLine([int32]) - то есть получение всех перегрузок и фильтрацию по их типам параметров.

Теперь я пытаюсь вызвать [String]::Join(), но это универсальный метод, и его необходимо набрать, чтобы объединить массив [int32[]].Разборка AC # показывает, что инструкции 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, тогда код работает нормально и просто выводит строку-разделитель ;.Так что эта часть, кажется, в порядке.Я уверен, что код установки массива тоже в порядке, но не совсем уверен.По крайней мере, оно не получило значение и неправильно указало индекс (что выбрасывает исключение Index вне границ).И опкоды, которые я посылаю, выглядят как разборка C # для хранения значений в массиве.

Итак, каков правильный код PowerShell для передачи вызова String.Join, набранного в int32, как видно из разборки C #?

1 Ответ

0 голосов
/ 22 декабря 2018

Он должен быть набран для элементов в массиве [int], а не для всего массива [int[]].Изменение моего PowerShell для удаления лишних [] теперь работает:

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

Как я нашел

Я использовал эту статью на 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>.

...