Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell crashes when using ref parameter in .NET event

PowerShell crashes when an .NET-event with a ref parameter I've subscribed to, is invoked.

I've reduced the problem to the following code.

.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

When i run the PowerShell script i get this:

enter image description here

If i press Debug i get this from Visual Studio:

enter image description here

The copy exception detail to the clipboard gives me this

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.

I guess i have to provide the string to the -Action script-block somehow, but i don't know how.

P.S. I'm using PowerShell 4:

PS C:\Windows\system32> $PSVersionTable.PSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1
like image 784
Jon List Avatar asked Jun 22 '15 18:06

Jon List


1 Answers

I found a work-around for this problem:

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

The result:

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

Previously I tried to create a (object sender, EventArgs e) type of handler to wrap the (ref string str) handler, but that did not work, because the PS event was fired out of sync with the .NET event.

Passing a Func in stead of using an event handler seems to work a lot better, as demonstrated in the code above.

I tried to generalize the BadEventSubscriber to use a Expression<Func<object, handler>> for selecting the event off of the passed object in the Subscribe method, but generics and Delegate don't play well together. So you would have to create a BadEventSubsciber-type class for every class and handler you would want to subscribe to (that PS does not "support"), but is it good enough for me at the moment.

Its no real solution, as you would still need the additional assembly to "wrap" the event into a Func, but as a work-around i think it is okay.

EDIT:

Now that i think about it, you can just define the subscriber class directly in PowerShell like @RomanKuzmin said in his comment to my original question. So instead of the additional assembly you just have the following in your 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
like image 190
Jon List Avatar answered Nov 04 '22 09:11

Jon List