Thread .NET API einbinden (43 answers)
Opened by bianca at 2010-03-02 12:18

topeg
 2010-04-09 03:37
#135852 #135852
User since
2006-07-10
2611 Artikel
BenutzerIn

user image
Also auf bitten von bianca will ich zeigen wie man eine Anbindung an C# hin bekommen kann. Es ist nicht beste Weg, oder der sicherste, aber dafür ist er einfach, da man auf normierte Schnittstellen zurückgreifen kann.
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 (Mono::GetOptions). 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 CookComputing.XmlRpc; und auf der perl-Seite RPC::XML::Client.

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: Datei->Neu->Projektmappe...
Ein Fenster geht Auf in dem man um Feld Template C#->Terminal Projekt 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:
more (6.1kb):

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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 namespace xml_rpc_deamon 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 Projekt->Referenzen bearbeiten... auswählt.
Ein Fenster öffnen sich und kann die Pakete System.Runtime.Remoting und Mono.GetOptions zusätzlich auswählen.
Für CookComputing.XmlRpc muss man erst die das Paket von http://www.xml-rpc.net/ herunter laden und entpaken. Dann wählt man im selben Fenster den Reitereintrag .Net-Assembly und selektiert dort im Entpakten Ordner bin/CookComputing.XmlRpcV2.dll und fügt es der Liste hinzu.

Nun sollte sich das Programm kompilieren und starten lassen.

Im Projektordner findet man unter <projektname>/<namespace>/bin/Debug/ eine "dll" und eine "exe" die "exe" Lässt sich mit "mono <name>.exe" starten. Das ist der kompilierte Code von oben.
Bei mit sieht das so aus:

Code: (dl )
1
2
3
4
5
#>ls -l ~/Projects/xml_rpc_deamon/xml_rpc_deamon/bin/Debug/
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.mdb


Halbzeit 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"
Code: (dl )
1
2
3
4
-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 (46.8kb):

Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#!/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);

# Etwas Demonstartion, dass es Funktioniert

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;
}

#-----------------------------------------------------------------------
# Kommandos, die auf dem Server ausgeführt werden sollen
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);
}

#-----------------------------------------------------------------------
# Fehler ausgeben wenn sie auftreten
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;
  }
}

########################################################################
# privat #
########################################################################

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(@_); };

    # XML-IPC Client initialisieren
    my $host='http://localhost:'.$port.'/test';
    $self->{ipc}=RPC::XML::Client->new($host);

    # läuft möglicherweise schon ein Service?
    # wenn ja, keinen eigenen starten.
    my $resp=$self->{ipc}->send_request('x');
    unless(ref($resp))
    {
      #Service starten
      my $cs_pid=fork();
      if(defined($cs_pid))
      {
        if($cs_pid)
        {
          $self->{pid}=$cs_pid;
          # warten dass der Server hochkommt.
          sleep($wait);
        }
        # im Kindprozess C# Programm starten
        # da müsste man noch etwas machen wenn man auch
        # den MS-Interpreter nutzen möchte
        # zudem ist das alles auf linux/Unix abgestimmt
       # (siehe Pfadangabe)
        else
        {
          # programm starten
          exec("/usr/bin/mono $FindBin::Bin/mono/xml_rpc_deamon.exe -d -s localhost -p $port");
          # wenn man hier ankommt lief was verkehrt!
          exit(10);
        }
      }
      else
      # fork hat nicht geklappt!
      {
        $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';
    # zu beenden auffordern
    kill('KILL',$pid) if(waitpid($pid, WNOHANG)>-1);

    # maximal 20 Sekunden warten.
    eval{
      local $SIG{ALRM}={die("timeout1\n")};
      alarm(20);
      waitpid($pid,0);
      alarm(0);
    };

    # 20 Sekunden gewartet ohne dass der Prozess beendet wurde
    if($@ && waitpid(-1, WNOHANG)>-1)
    {

      # Abwürgen
      kill('TERM',$pid) if(waitpid($pid, WNOHANG)>-1);

      # und 2 Sekunden warten
      eval{
        local $SIG{ALRM}={die("timeout2\n")};
        alarm(5);
        waitpid($pid,0);
        alarm(0);
      };

      # Prozess hängt ganz übel
      # Deadlock ??
      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:
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
./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.


Wenn gewünscht, kann ich noch später noch etwas genauer auf den bestimmte Abschnitte eingehen.

EDIT: Immer diese Schreibfehler...

modedit Editiert von pq: more-tags hinzugefügt
Last edited: 2012-02-11 16:03:32 +0100 (CET)

View full thread .NET API einbinden