C# von Perl aus benutzen
Hin und wieder wird es wichtig Fremdcode in Perl einzubinden. Für viele Sprachen gibt es Module wie z.B die
module wie oder ähnliches für Javascript und auch PHP. Speziell für C# gibt es keine solchen Brücken. Das bedeutet man muss sich sich selber bauen. Da sowohl C# als auch Perl interprteiert werden fallen Crosscompiler schnell weg. Der einfachste weg ist nun eine Interprozesskommunikation. Dabei Tritt das C# Programm als Server auf und das Perl Script sendet anfragen.Nun alles Schritt für Schritt (sofern ich nichts vergessen habe)
1. Ich habe den C# Teil mit Monodevelop erstellt. Ich habe aber nur eine momo-spezifische Bibliothek benutzt (
). Ich hatte einfach keine Lust nur zum Parsen der Kommandozeilenoptionen noch eine neue Lib an zu schauen.2. Ich nutze XML-RPC um die Daten zwischen Perl und C# zu transferieren. Auf der C#-Seite ist das
und auf der perl-Seite .3. Wenn kein Service (C#) läuft wird er vom Client gestartet und verwaltet. Das macht es möglich den Service auch getrennt zu starten und mehrere Clients darauf zugreifen zu lassen.
4. Ich habe nicht viel Übung in C# und dementsprechend sieht der Code auch aus. :-) Zunächst der Service in C#. Zunächst habe ich ein neues Projekt angelegt:
Ein Fenster geht Auf in dem man um Feld auswählt. Nachdem man einen Projektnamen eingeben hat. (Ich wählte "xml_rpc_deamon") wird das Projekt erstellt. Man bekommt eine Datei "Main.cs" in die man folgendes eintragen sollte:using System; using System.Net; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using CookComputing.XmlRpc; using Mono.GetOptions; // hatte keine Lust mir noch ein anderes an zu schauen namespace xml_rpc_deamon { //####################################################################### // Klasse zum Parsen der Kommandozeielenargumente ( mono spezifisch ) class SampleOptions : Options { [Option ("Runn as deamon", 'd')] public bool deamon=false; [Option ("set port who runns on", 'p')] public int port=5678; [Option ("set the ip to bind to", "s")] public string host=null; public SampleOptions() { base.ParsingMode = OptionsParsingMode.Both; } } //####################################################################### // Klasse zum verwalten der Serveranfragen // XML-RPC public class TestServer : MarshalByRefObject { // nachfolgend wird der name der "funktion" benannt // er Aufruf ist dasnn ugefähr http://127.0.0.1/test/test.Running [XmlRpcMethod("test.Running")] public void testRunning() { Console.Error.WriteLine("IS RUNNING"); } // siehe oben [XmlRpcMethod("test.Get")] public string GetTest(int Number) { if (Number < 1 || Number > test_List.Length) return ""; return test_List[Number-1]; } private static string[] test_List={ "test1", "test2", "test3", "test4", "test5" }; } //####################################################################### // Mainklasse public class MainClass { public static void Main (string[] args) { // parsen der Kommadozeilenargumente SampleOptions options = new SampleOptions(); options.ProcessArgs (args); IDictionary props = new Hashtable(); // Name des Prozesses // ist hier nicht wichtig // kann alles drin stehen props["name"] = "MyHttpChannel"; // der Port auf dem gelauscht wird // default 5678 props["port"] = options.port; // auf eine IP begrenzen // default: Alle IPS // wenn eine ungültige IP, // dann localhost (meist 127.0.0.1) if(options.host != null) { IPAddress ip=null; try { ip=IPAddress.Parse(options.host); } catch {} if(ip != null) { props["bindTo"] = ip.ToString(); } else { props["bindTo"] = IPAddress.Loopback.ToString(); } } //Ip:Port belegen und lauschen HttpChannel channel=null; try { //könnte fehl schlagen // ip existiert nicht oder // port ist belegt channel = new HttpChannel(props,null,new XmlRpcServerFormatterSinkProvider()); } catch { //bindTo ignorieren props.Remove("bindTo"); try { // port könnte belegt sein channel = new HttpChannel(props,null,new XmlRpcServerFormatterSinkProvider()); } catch { // aufgeben return; } } ChannelServices.RegisterChannel(channel,false); // Service anmelden. es wird eien Instanz von der Klasse ober erzeugt // und unter "http://127.0.0.1/test/" registriert RemotingConfiguration.RegisterWellKnownServiceType(typeof(TestServer),"test",WellKnownObjectMode.Singleton); // als deamon gestartet // in enen endlosen loop // oder andernfalls // ein paar ausgeben und aur return zum beenden warten if(options.deamon) { // STDIN STDOUT STDERR schließen Console.Out.Close(); //Console.Error.Close(); Console.In.Close(); while(true) System.Threading.Thread.Sleep( 1000 ); } else { Console.WriteLine("Service running as:"); string ip="<all avaiable>"; if(props["bindTo"] != null) ip=props["bindTo"].ToString(); Console.WriteLine("http://{0}:{1}/test/",ip,props["port"]); Console.WriteLine("Press <ENTER> to shutdown"); Console.Out.Close(); Console.ReadLine(); } } } }Die Zeile sollte man entsprechend Anpassen. Will man das nun ausführen mosert der Compiler, dass er einige Libs nicht finden kann. Das Problem löst man indem man im Menu auswählt.
Ein Fenster öffnen sich und kann die Pakete http://www.xml-rpc.net/ herunter laden und entpaken. Dann wählt man im selben Fenster den Reitereintrag und selektiert dort im Entpakten Ordner und fügt es der Liste hinzu. Nun sollte sich das Programm kompilieren und starten lassen.
und zusätzlich auswählen. Für muss man erst die das Paket vonIm Projektordner findet man unter
Bei mit sieht das so aus:
insgesamt 128 -rw-r--r-- 1 topeg topeg 118784 8. Apr 23:02 CookComputing.XmlRpcV2.dll -rwxr-xr-x 1 topeg topeg 6144 8. Apr 23:02 xml_rpc_deamon.exe -rw-r--r-- 1 topeg topeg 967 8. Apr 23:02 xml_rpc_deamon.exe.mdbHalbzeit ist erreicht, der Service in C# steht und sollte sich starten lassen. Nun kommen wir zum Perl Teil: Ich habe die exe und dll in einen Ordner "mono" kopiert und dazu ein script "mono_perl_ipc.pl"
-rwxr--r-- 1 topeg topeg 5254 9. Apr 00:22 mono_perl_ipc.pl drwxr-xr-x 1 topeg topeg 93 8. Apr 23:02 mono -rw-r--r-- 1 topeg topeg 118784 8. Apr 23:02 mono/CookComputing.XmlRpcV2.dll -rwxr-xr-x 1 topeg topeg 6144 8. Apr 23:02 mono/xml_rpc_deamon.exe
Der Inhalt des Scriptes: Ich hoffe der Code ist einigermaßen verständlich. mono_perl_ipc.pl:
#!/usr/bin/perl use strict; use warnings; my $port="5678"; my $wait=2; my $service=csharp_ipc_service->new($port,$wait); print csharp_ipc_service::error()."\n" unless($service); if($service->test_running()) { print "test_running() erfolgreich\n"; } else { print "ERROR:".$service->error()."\n"; } print "#"x80,"\n"; if($service->test_running()) { print "test_running() erfolgreich\n"; } else { print "ERROR:".$service->error()."\n"; } print "#"x80,"\n"; my $val=$service->test_get(1); if(defined($val)) { print "test_get(1) = $val\n"; } else { print "ERROR:".$service->error()."\n"; } print "#"x80,"\n"; $val=$service->test_get(4); if(defined($val)) { print "test_get(4) = $val\n"; } else { print "ERROR:".$service->error()."\n"; } print "#"x80,"\n"; {package csharp_ipc_service; use strict; use warnings; use RPC::XML; use RPC::XML::Client; use FindBin; use POSIX ":sys_wait_h"; my $ERROR=undef; sub new { my $class=shift; my $port=shift; my $wait=shift; my $self={}; $self->{stop}=0; $self->{pid}=0; bless($self, $class); unless($self->_start($port,$wait)) { $ERROR=$self->{ERROR}; return undef; } return $self; } sub test_running { my $self=shift; my $ret=$self->_runn_cmd('test.Running'); return 1 if(defined($ret)); return 0; } sub test_get { my $self=shift; my $number=shift; return $self->_runn_cmd('test.Get',$number); } sub error { my $self=shift; if($self && ref($self) eq __PACKAGE__) { my $err=$self->{ERROR} || ''; $self->{ERROR}=undef if($self->{ERROR}); return $err; } else { my $err=$ERROR; $ERROR=undef; return $err; } } sub _add_error { my $self=shift; my $msg=shift; if($msg) { if($self->{ERROR}) { $self->{ERROR}.="\n$msg"; } else { $self->{ERROR}=$msg; } } } sub _runn_cmd { my $self=shift; my $resp=$self->{ipc}->send_request(@_); if(ref($resp) && ref($resp) ne 'RPC::XML::fault') { return $resp->value(); } else { if(ref($resp)) { $self->_add_error($resp->string()); } else { $self->_add_error("no server connection ($!)"); } } return undef; } sub _sig_child { my $self=shift; my $msg=waitpid($self->{pid},0); $self->_add_error("server died unexpected") unless($self->{stop}); } sub _start { my $self=shift; my $port=shift || 5678; my $wait=shift || 5; unless($self->{pid}) { $self->{stop}=0; $self->{pid}=0; $self->{ipc}=undef; local $SIG{CHLD}=sub{ $self->_sig_child(@_); }; my $host='http://localhost:'.$port.'/test'; $self->{ipc}=RPC::XML::Client->new($host); my $resp=$self->{ipc}->send_request('x'); unless(ref($resp)) { my $cs_pid=fork(); if(defined($cs_pid)) { if($cs_pid) { $self->{pid}=$cs_pid; sleep($wait); } else { exec("/usr/bin/mono $FindBin::Bin/mono/xml_rpc_deamon.exe -d -s localhost -p $port"); exit(10); } } else { $self->_add_error("Fork failed"); return 0; } } return 1; } } sub _stop { my $self=shift; if($self->{pid}) { $self->{stop}=1; my $pid=$self->{pid}; local $SIG{CHLD}='DEFAULT'; kill('KILL',$pid) if(waitpid($pid, WNOHANG)>-1); eval{ local $SIG{ALRM}={die("timeout1\n")}; alarm(20); waitpid($pid,0); alarm(0); }; if($@ && waitpid(-1, WNOHANG)>-1) { kill('TERM',$pid) if(waitpid($pid, WNOHANG)>-1); eval{ local $SIG{ALRM}={die("timeout2\n")}; alarm(5); waitpid($pid,0); alarm(0); }; if($@ && waitpid(-1, WNOHANG)>-1) { $SIG{CHLD}='IGNORE'; die("Can't kill $pid!\n"); } } } } sub DESTROY { _stop(@_); } 1;}
Sollte alles laufen bekommt man eine Ausgabe wie diese:
./mono_perl_ipc.pl IS RUNNING test_running() erfolgreich IS RUNNING test_running() erfolgreich test_get(1) = test1 test_get(4) = test4
Ich habe hier nur das Übertragen von Strings und Integer gezeigt, aber XML-RPC kann auch mit Arrays und Hashes umgehen, wenn man noch komplexere Datenstrukturen übertragen will kann man tiefer in die XML-RPC Kommunikation Einsteigen oder die Daten Serialisieren.