Подтвердить что ты не робот

PowerShell падает при использовании параметра ref в событии .NET

Сбой PowerShell при вызове .NET-события с параметром ref, на который я подписался.

Я уменьшил проблему до следующего кода.

.NET:

namespace PSEventTest
{
    public delegate void BadHandler(ref string str);

    public class PSEventTest
    {
        public event BadHandler BadEvent;

        public void InvokeBad()
        {
            var handler = BadEvent;
            if (handler != null)
            {
                var str = "bad";
                handler(ref str);
            }
        }
    }
}

PowerShell:

Add-Type -path $PSScriptRoot"\PSEventTest.dll"

$obj = New-Object PSEventTest.PSEventTest

$bad = Register-ObjectEvent -InputObject $obj -EventName BadEvent -Action {
    Write-Host "bad!" # it also crashes if this script-block is empty 
}

$obj.InvokeBad(); # crash

Когда я запускаю PowerShell script, я получаю следующее:

enter image description here

Если я нажимаю Debug, я получаю это из Visual Studio:

enter image description here

copy exception detail to the clipboard дает мне это

System.NullReferenceException was unhandled
Message: An unhandled exception of type 'System.NullReferenceException' occurred in Unknown Module.
Additional information: Object reference not set to an instance of an object.

Мне кажется, я должен каким-то образом предоставить строку в -Action script -block, но я не знаю, как это сделать.

P.S. Я использую PowerShell 4:

PS C:\Windows\system32> $PSVersionTable.PSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1
4b9b3361

Ответ 1

Я нашел рабочий процесс для этой проблемы:

.NET:

namespace PSEventTest
{
    public delegate void BadHandler(ref string str);

    public class PSEventTest
    {
        public event BadHandler BadEvent;

        public string InvokeBad()
        {
            var handler = BadEvent;
            if (handler != null)
            {
                var str = "hi from .NET!";
                handler(ref str);
                return "from .NET: " + str;
            }
            return null;
        }
    }

    // as i mention below, this class can be implemented directly in the PS script
    public class BadEventSubscriber
    {
        public void Subscribe(PSEventTest legacyObj, Func<string, string> func)
        {
            legacyObj.BadEvent += delegate(ref string str)
            {
                var handler = func; 
                if (handler != null)
                {
                    var result = handler(str);
                    str = result;
                }
            };
        }
    }
}

PowerShell:

Add-Type -path $PSScriptRoot"\PSEventTest.dll"

$legacyObj = New-Object PSEventTest.PSEventTest
$subscriber = New-Object PSEventTest.BadEventSubscriber

$subscriber.Subscribe($legacyObj, {
    param([string]$str)
    write-host "from PS: $str"
    "hi from PS!" 
})

write-host $legacyObj.InvokeBad();

Результат:

from PS: hi from .NET!
from .NET: hi from PS!

Раньше я пытался создать тип обработчика (object sender, EventArgs e) для переноса обработчика (ref string str), но это не сработало, потому что событие PS было уволено из синхронизации с событием .NET.

Передача Func вместо использования обработчика событий, похоже, работает намного лучше, как показано в приведенном выше коде.

Я попытался обобщить BadEventSubscriber, чтобы использовать Expression<Func<object, handler>> для выбора события из переданного объекта в методе Subscribe, но generics и Delegate не работают хорошо. Таким образом, вам нужно будет создать класс BadEventSubsciber -type для каждого класса и обработчика, на который вы хотели бы подписаться (что PS не поддерживает), но для меня это сейчас достаточно хорошо.

Это не настоящее решение, так как вам все равно понадобится дополнительная сборка для "обертывания" события в Func, но как work-around, я думаю, что все в порядке.

EDIT:

Теперь, когда я думаю об этом, вы можете просто определить класс подписчика непосредственно в PowerShell, как @RomanKuzmin сказал в своем comment к моему первоначальному вопросу. Поэтому вместо дополнительной сборки у вас просто есть следующее в PowerShell:

Add-Type @"
    using System;

    public class BadEventSubscriber
    {
        public void Subscribe(PSEventTest.PSEventTest legacyObj, Func<string, string> func)
        {
            legacyObj.BadEvent += delegate(ref string str)
            {
                var handler = func; 
                if (handler != null)
                {
                    var result = handler(str);
                    str = result;
                }
            };
        }
    }
"@ -ReferencedAssemblies $PSScriptRoot"\PSEventTest.dll"

$subscriber = New-Object BadEventSubscriber