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:
If i press Debug
i get this from Visual Studio:
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With