2015-06-22 13 views
13

PowerShell ulega awarii po wywołaniu zdarzenia .NET z parametrem ref, który subskrybowałem.PowerShell ulega awarii podczas używania parametru ref w zdarzeniu .NET

Sprowadziłem problem do następującego kodu.

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 

kiedy uruchomić skrypt PowerShell uzyskać to:

enter image description here

Jeśli pędzę Debug i dostać to od Visual Studio:

enter image description here

The copy exception detail to the clipboard daje mi ten

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. 

Chyba musiał dostarczyć ciąg do skryptu bloku -Action jakoś, ale nie wiem jak.

P.S. Używam PowerShell 4:

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

Już debugowałem. Odkryłem, że blok skryptu PowerShell jest "niezsynchronizowany" z kodem .NET. Jeśli zrobię ten sam kod .NET bez "ref", widzę, że blok skryptu zdarzenia nie zostanie wykonany przed zakończeniem wywołania "InvokeBad". Co oznacza, że ​​w przypadku z parametrem 'ref' wykonanie kodu .NET mija punkt, w którym odniesienie może zostać zaktualizowane po wykonaniu bloku skryptu zdarzenia PowerShell. Może to być spowodowane tym, że wywołałem zdarzenie z programu PowerShell? –

+1

Co zwróci '$ HOST.Runspace.ApartmentState'? STA lub MTA? –

+0

@ MathiasR.Jessen '$ HOST.Runspace.ApartmentState' zwraca' STA' zarówno wewnątrz, jak i na zewnątrz zdarzenia blockblock. –

Odpowiedz

6

znalazłem obejście dla tego problemu:

.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 wynik:

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

Poprzednio próbowałem utworzyć instrukcję obsługi typu (object sender, EventArgs e), aby owinąć obsługę obsługi (ref string str), ale to nie zadziałało, ponieważ zdarzenie PS zostało wyrzucone poza synchronizację ze zdarzeniem .NET.

Przekazywanie numeru Func zamiast korzystania z obsługi zdarzeń wydaje się działać lepiej, jak pokazano w powyższym kodzie.

Starałem się uogólnić BadEventSubscriber użyć Expression<Func<object, handler>> wyboru zdarzenie off Przekazany obiekt w metodzie Subscribe, ale rodzajowych i Delegate nie dobrze grać razem. Musielibyśmy więc stworzyć klasę BadEventSubsciber -type dla każdej klasy i obsługi, którą chcielibyście zasubskrybować (PS nie "wspiera"), ale jest to w tej chwili wystarczająco dobre dla mnie.

Jego nie rzeczywiste rozwiązanie, gdyż trzeba jeszcze dodatkowy zespół do „wrap” zdarzenie w Func, ale jako obejście myślę, że to jest w porządku.

EDIT:

Teraz, gdy o tym myślę, można po prostu określić klasę abonenta bezpośrednio w PowerShell jak @RomanKuzmin powiedział w jego comment do mojego pierwotnego pytania. Więc zamiast dodatkowego zespołu masz w PowerShell tylko:

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 
+0

To działa dla prostego przypadku, który napisałem, ale "w prawdziwym świecie" natknąłem się na problemy z 'Runspace' (oryginalne wydarzenie jest uruchamiane z innego wątku lub coś podobnego). Mój skrypt PS jest już głównie obejściem tworzenia zdarzenia za pomocą funkcji 'ref'-parameter, więc dodaje więcej obejść, aby uzyskać skryptblok w prawo' Runspace' z powodu 'PSInvalidOperationException: Brak dostępnego Runspace do uruchamiania skryptów w tym wyjątek dla wątku, wydaje się zbyt dużym nakładem pracy, kiedy mogę po prostu stworzyć prostą aplikację .NET i sprawić, by działała. Mimo to nauczyłem się czegoś. –

Powiązane problemy