2015-08-12 13 views
8

Sto provando a creare un componente aggiuntivo di PowerShell in .NET che consentirà di accedere ad alcune funzionalità di Windows 10. Conosco C#, ma PowerShell è relativamente nuovo per me.Eventi C# mai gestiti in PowerShell 5

Ho tutte le chiamate di funzione e i cmdlet attivi. Posso fare in modo che le classi C# facciano cose da PowerShell. Quello che mi fa inciampare è l'innalzamento e la gestione degli eventi per far richiamare il codice C#. Sono stato su questo per tre sere ora.

Esempi online e qui in SO mostrano sempre i timer e gli osservatori del file system, o talvolta un Windows Form. Non ho mai visto un esempio usando una classe che qualcuno ha scritto da sé. Non sono sicuro che ci sia dello zucchero necessario per aumentare gli eventi o qualcos'altro.

La principale differenza che ho riscontrato tra il mio codice e altri è che ho una classe factory che restituisce un'istanza dell'oggetto che solleva gli eventi. Lo uso invece di chiamare new-object. Tuttavia, PowerShell riconosce il tipo ed è felice di elencare i membri su di esso, quindi ho presunto che non è questo il problema.

Ho creato un cmdlet e una classe di repro semplice e script. Eccoli:

GetTestObject cmdlet

using System.Management.Automation; 

namespace PeteBrown.TestFoo 
{ 
    [Cmdlet(VerbsCommon.Get, "TestObject")] 

    public class GetTestObject : PSCmdlet 
    { 
     protected override void ProcessRecord() 
     { 
      var test = new TestObject(); 
      WriteObject(test); 
     } 
    } 
} 

classe TestObject

using System; 

namespace PeteBrown.TestFoo 
{ 
    public class TestObject 
    { 
     public string Name { get; set; } 

     public event EventHandler FooEvent; 

     public void CauseFoo() 
     { 
      Console.WriteLine("c#: About to raise foo event."); 
      try 
      { 
       if (FooEvent != null) 
        FooEvent(this, EventArgs.Empty); 
       else 
        Console.WriteLine("c#: no handlers wired up."); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
      } 
      Console.WriteLine("c#: Raised foo event. Should be handler executed above this line."); 
     } 


     public void CauseAsyncFoo() 
     { 
      Console.WriteLine("c#: About to raise async foo event."); 
      try 
      { 
       if (FooEvent != null) 
       { 
        // yeah, I know this is silly 
        var result = FooEvent.BeginInvoke(this, EventArgs.Empty, null, null); 
        FooEvent.EndInvoke(result); 
       } 
       else 
        Console.WriteLine("c#: no handlers wired up."); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
      } 
      Console.WriteLine("c#: Raised async foo event."); 
     } 

    } 
} 

E qui ci sono gli script e la loro produzione

test-events1.ps1 utilizzando la sintassi gestore semplice evento .NET

Import-Module "D:\U.....\Foo.dll" 

Write-Output "Getting test object -------------------------------------- " 
[PeteBrown.TestFoo.TestObject]$obj = Get-TestObject 

# register for the event 
Write-Output "Registering for .net object event ------------------------ " 
$obj.add_FooEvent({Write-Output "Powershell: Event received"}) 

Write-Output "Calling the CauseFoo method to raise event --------------- " 
$obj.CauseFoo() 

uscita: (evento viene generato in C#, ma mai trattati in PS)

Getting test object -------------------------------------- 
Registering for .net object event ------------------------ 
Calling the CauseFoo method to raise event --------------- 
c#: About to raise foo event. 
c#: Raised foo event. Should be handler executed above this line. 

test-events2.ps1 utilizzando asincrona/sintassi BeginInvoke nel caso in cui quello era il numero

Import-Module "D:\U.....\Foo.dll" 

Write-Output "Getting test object -------------------------------------- " 
[PeteBrown.TestFoo.TestObject]$obj = Get-TestObject 

# register for the event 
Write-Output "Registering for .net object event ------------------------ " 
$obj.add_FooEvent({Write-Output "Powershell: Event received"}) 

Write-Output "Calling the CauseAsyncFoo method to raise event ---------- " 
$obj.CauseAsyncFoo() 

Uscita: (l'evento è attivato in C#, ma mai gestito in PS. Inoltre, ottengo un'eccezione.)

Getting test object -------------------------------------- 
Registering for .net object event ------------------------ 
Calling the CauseAsyncFoo method to raise event ---------- 
c#: About to raise async foo event. 
System.ArgumentException: The object must be a runtime Reflection object. 
    at System.Runtime.Remoting.InternalRemotingServices.GetReflectionCachedData(MethodBase mi) 
    at System.Runtime.Remoting.Messaging.Message.UpdateNames() 
    at System.Runtime.Remoting.Messaging.Message.get_MethodName() 
    at System.Runtime.Remoting.Messaging.MethodCall..ctor(IMessage msg) 
    at System.Runtime.Remoting.Proxies.RemotingProxy.Invoke(Object NotUsed, MessageData& msgData) 
    at System.EventHandler.BeginInvoke(Object sender, EventArgs e, AsyncCallback callback, Object object) 
    at PeteBrown.TestFoo.TestObject.CauseAsyncFoo() in D:\Users\Pete\Documents\GitHub\Windows-10-PowerShell-MIDI\PeteBrow 
n.PowerShellMidi\Test\TestObject.cs:line 37 
c#: Raised async foo event. 

test-events3.ps1 Utilizzando approccio Registrati-ObjectEvent. Anche qui sono state aggiunte ulteriori informazioni sul tipo di diagnostica all'output.

Import-Module "D:\U......\Foo.dll" 

Write-Output "Getting test object -------------------------------- " 
[PeteBrown.TestFoo.TestObject]$obj = Get-TestObject 

# register for the event 
Write-Output "Registering for .net object event ------------------ " 

$job = Register-ObjectEvent -InputObject $obj -EventName FooEvent -Action { Write-Output "Powershell: Event received" } 

Write-Output "Calling the CauseFoo method to raise event --------- " 
$obj.CauseFoo() 

Write-Output "Job that was created for the event subscription ----" 
Write-Output $job 

# show the event subscribers 
Write-Output "Current event subscribers for this session ---------" 
Get-EventSubscriber 

# this lists the events available 
Write-Output "Events available for TestObject --------------------" 
$obj | Get-Member -Type Event 

uscita: (evento viene generato in C#, ma mai trattati in PS)

Getting test object -------------------------------- 
Registering for .net object event ------------------ 
Calling the CauseFoo method to raise event --------- 
c#: About to raise foo event. 
c#: Raised foo event. Should be handler executed above this line. 
Job that was created for the event subscription ---- 

Id  Name   PSJobTypeName State   HasMoreData  Location    Command 
--  ----   ------------- -----   -----------  --------    ------- 
1  2626de85-523...     Running  True         Write-Output "Powersh... 
Current event subscribers for this session --------- 

SubscriptionId : 1 
SourceObject  : PeteBrown.TestFoo.TestObject 
EventName  : FooEvent 
SourceIdentifier : 2626de85-5231-44a0-8f2c-c2a900a4433b 
Action   : System.Management.Automation.PSEventJob 
HandlerDelegate : 
SupportEvent  : False 
ForwardEvent  : False 

Events available for TestObject -------------------- 

TypeName : PeteBrown.TestFoo.TestObject 
Name  : FooEvent 
MemberType : Event 
Definition : System.EventHandler FooEvent(System.Object, System.EventArgs) 

In nessun caso posso effettivamente ottenere l'azione gestore di eventi sparato. Ho anche provato a utilizzare una variabile per contenere l'azione, ma sembra trattata allo stesso modo. Il codice Console.Writeline in C# serve solo per aiutare il debug. E ho rimosso il percorso completo della DLL negli script incollati solo per renderli più facili da leggere. La DLL carica bene.

Utilizzo di PowerShell 5 su Windows 10 pro 64 bit con .NET Framework 4.6 (CLR 4). VS 2015 RTM.

Name       Value 
----       ----- 
PSVersion      5.0.10240.16384 
WSManStackVersion    3.0 
SerializationVersion   1.1.0.1 
CLRVersion      4.0.30319.42000 
BuildVersion     10.0.10240.16384 
PSCompatibleVersions   {1.0, 2.0, 3.0, 4.0...} 
PSRemotingProtocolVersion  2.3 

Qualche suggerimento per questo noob PowerShell?

+0

Non so se questo è il problema, ma il modo in cui si chiama l'evento non è thread-safe. 'if (FooEvent! = null) FooEvent (this, EventArgs.Empty);' dovrebbe usare una variabile locale per memorizzare prima il valore 'FooEvent', quindi controllare che sia null e chiamarlo. Come in, 'var foo = FooEvent; se (foo! = null) foo (this, EventArgs.Empty); '. O in C# 6, 'Foo? .Invoke (this, EventArgs.Empty);' http://codeblog.jonskeet.uk/2015/01/30/clean-event-handlers-invocation-with-c-6/ –

+0

Dato l'errore asincrono che si vede, ho il sospetto che si possa essere in errore di remoting e proxy remoti. Si consiglia di leggere [rendere gli oggetti remotabili] (https://msdn.microsoft.com/en-us/library/vstudio/wcf3swha (v = vs.100) .aspx), in particolare, il bit su [remoting ed eventi] (https://msdn.microsoft.com/en-us/library/vstudio/ms172343 (v = vs.100) .aspx) –

+0

Quali risultati del lavoro "Ricevi-Lavoro $"? – PetSerAl

risposta

3

Write-Output non visualizza l'oggetto sulla console, invia oggetto al comando successivo nella pipeline. Se si desidera visualizzare immediatamente del testo sulla console, è necessario utilizzare il cmdlet Write-Host o Out-Host.

"Non usare mai Write-Host perché il male!"

Direi che in questo modo: "Non usare mai Write-Host su dati intermedi perché il male!". È possibile visualizzare alcune notifiche di avanzamento con Write-Host (sebbene sia possibile considerare l'utilizzo di Write-Progress) o utilizzarlo per visualizzare il risultato finale, ma Write-Host non inviare nulla al prossimo comando nella pipeline, quindi non è possibile elaborare ulteriormente i dati.

Perché tutti gli altri comandi Write-Output scrivono sulla console?

Quando alcuni oggetti raggiungono la fine ultima della pipeline, PowerShell deve fare qualcosa al riguardo. Di default visualizzarlo su host (console in questo caso), anche se è possibile ignorare questo:

New-Alias Out-Default Set-Clipboard # to copy anything into clipboard 
New-Alias Out-Default Out-GridView # to use grid view by default 

PowerShell non supporta dello script blocco invocazione in qualche thread arbitrario, quindi per eventi asincroni si deve usare Register-ObjectEvent, così PowerShell può gestirli.

+0

Perfetto, grazie. I post che ho visto in realtà stavano dicendo che oltre a visualizzare testo colorato, non dovresti MAI usare "scrivere-host" perché ragioni e malvagità. Avrei dovuto capire. Questo è ciò che alla fine stavo creando: https://github.com/Psychlist1972/Windows-10-PowerShell-MIDI ora, completo di testo colorato. :) – Pete