Как получить добавленные типы PowerShell для использования добавленных типов

Я работаю над проектом PoSh, который генерирует код CSharp, а затем Add-Type сохраняет его в памяти.

Новые типы используют существующие типы в библиотеке DLL на диске, которая загружается через Add-Type.

Все хорошо, пока я действительно не попытаюсь вызвать методы для новых типов. Вот пример того, что я делаю:

$PWD = "."
rm -Force $PWD\TestClassOne*
$code = "
namespace TEST{
public class TestClassOne
{
    public int DoNothing()
    {
        return 1;
    }
}
}"
$code | Out-File tcone.cs
Add-Type -OutputAssembly $PWD\TestClassOne.dll -OutputType Library -Path $PWD\tcone.cs
Add-Type -Path $PWD\TestClassOne.dll
$a = New-Object TEST.TestClassOne
"Using TestClassOne"
$a.DoNothing()


"Compiling TestClassTwo"
Add-Type -Language CSharpVersion3 -TypeDefinition "
namespace TEST{
public class TestClassTwo
{
    public int CallTestClassOne()
    {
        var a = new TEST.TestClassOne();
        return a.DoNothing();
    }
}
}" -ReferencedAssemblies $PWD\TestClassOne.dll
"OK"
$b = New-Object TEST.TestClassTwo
"Using TestClassTwo"
$b.CallTestClassOne()

Выполнение приведенного выше сценария приводит к следующей ошибке в последней строке:

Исключение, вызывающее «CallTestClassOne» с аргументом (ами) «0»: «Не удалось загрузить файл или сборку TestClassOne, ...» или одну из ее зависимостей. Система не может найти указанный файл ». В AddTypeTest.ps1: 39 символов: 20 + $ b.CallTestClassOne ‹------------------------------------------------ () + CategoryInfo: NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId: DotNetMethodException

Что я делаю неправильно?


person Scott Weinstein    schedule 18.04.2010    source источник


Ответы (2)


Это происходит потому, что любые сборки ищутся загрузчиком CLR в базовом каталоге приложения (PowerShell). Конечно, он не находит там вашу сборку. Лучший способ решить эту проблему - перехватить событие AssemblyResolve, как упоминает stej, но использовать его, чтобы сообщить CLR, где находится сборка. Вы не можете сделать это с помощью Register-ObjectEvent PowerShell 2.0, потому что он не работает с событиями, требующими возвращаемого значения (т. Е. Сборкой). В этом случае давайте использовать больше C # через Add-Type, чтобы выполнить эту работу за нас. Этот фрагмент кода работает:

ri .\TestClassOne.dll -for -ea 0

$resolver = @'
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Utils
{
    public static class AssemblyResolver
    {
        private static Dictionary<string, string> _assemblies;

        static AssemblyResolver()
        {
            var comparer = StringComparer.CurrentCultureIgnoreCase;
            _assemblies = new Dictionary<string,string>(comparer);
            AppDomain.CurrentDomain.AssemblyResolve += ResolveHandler;
        }

        public static void AddAssemblyLocation(string path)
        {
            // This should be made threadsafe for production use
            string name = Path.GetFileNameWithoutExtension(path);
            _assemblies.Add(name, path);
        }

        private static Assembly ResolveHandler(object sender, 
                                               ResolveEventArgs args) 
        {
            var assemblyName = new AssemblyName(args.Name);
            if (_assemblies.ContainsKey(assemblyName.Name))
            {
                return Assembly.LoadFrom(_assemblies[assemblyName.Name]);
            }
            return null;
        }
    }
}
'@

Add-Type -TypeDefinition $resolver -Language CSharpVersion3

$code = @'
namespace TEST {
    public class TestClassOne {
        public int DoNothing() {
            return 1;
        }
    }
}
'@
$code | Out-File tcone.cs
Add-Type -OutputAssembly TestClassOne.dll -OutputType Library -Path tcone.cs

# This is the key, register this assembly's location with our resolver utility
[Utils.AssemblyResolver]::AddAssemblyLocation("$pwd\TestClassOne.dll")

Add-Type -Language CSharpVersion3 `
         -ReferencedAssemblies "$pwd\TestClassOne.dll" `
         -TypeDefinition @'
namespace TEST {
    public class TestClassTwo {
        public int CallTestClassOne() {
            var a = new TEST.TestClassOne();
            return a.DoNothing();
        }
    }
}
'@ 

$b = new-object Test.TestClassTwo
$b.CallTestClassOne()
person Keith Hill    schedule 19.04.2010
comment
Неплохо! ;) Однако поведение Posh сложно, потому что можно было бы ожидать, что первая сборка уже загружена. - person stej; 19.04.2010

Когда вы выводите TestClassTwo в dll (в том же каталоге, что и TestClassOne) и Add-Type, он работает. Или, по крайней мере, на моей машине;) Так что это уродливый обходной путь.

При вызове $b.CallTestClassOne() PowerShell пытается (по неизвестной мне причине) найти сборку TestClassOne.dll в следующих местах:

LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.EXE
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.EXE

Это результат работы инструмента fuslogvw. Это может быть вам полезно. Тот же список путей можно увидеть вживую с помощью ProcessMonitor.

Вы также можете попробовать это (перед тем, как позвонить CallTestClassOne()

[appdomain]::CurrentDomain.add_assemblyResolve({
    $global:x = $args
})
$b.CallTestClassOne()
$x | fl

Это покажет вам, какая сборка не удалась, и дополнительную информацию.

Я согласен с тем, что все должно работать так, как вы ожидаете. Вот почему это выглядит несколько ошибочно.

person stej    schedule 19.04.2010
comment
Спасибо за это. Я думаю, что Add-Type не прошел необходимое тестирование удобства использования API. - person Scott Weinstein; 19.04.2010
comment
Он ищет в этих местах, потому что это базовый каталог приложения (PowerShell) - [appdomain] :: CurrentDomain.BaseDirectory. Загрузчик CLR будет искать там или в определенных подкаталогах этого места, если вы не добавите частный путь проверки в файл конфигурации PowerShell.exe (не рекомендуется). - person Keith Hill; 19.04.2010
comment
@ Кит, ты прав. Однако возникает вопрос, почему PowerShell пытается загрузить сборку, даже если сборка уже должна быть загружена. Странно: | - person stej; 19.04.2010
comment
Я считаю, что это связано с тем, какой контекст загрузки используется во время загрузки каждой сборки. Из Reflector видно, что первая загрузка (Add-Type -Path) использует контекст LoadFrom. Я подозреваю, что вторая загрузка сборки использует контекст загрузки. Ознакомьтесь с таблицей на этом сайте. Для контекста загрузки указан один недостаток: blogs.msdn. ru / suzcook / archive / 2003/05/29 / 57143.aspx - person Keith Hill; 20.04.2010