Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Ruby und Microsoft Windows



Ruby wurde für eine Posix-Umgebung geschrieben, das heißt es werden all die schönen System-Aufrufe und -Bibliotheken benutzt, an die Unix-Programmierer so gewöhnt sind.

Es gibt aber auch eine Reihe von Eigenschaften und Erweiterungen, die Ruby auch in einer Microsoft-Windows-Umgebung zu einem nützlichen Werkzeug machen. In diesem Kapitel werden wir uns diese Eigenschaften ansehen und einige Geheimnisse erzählen, wie man Ruby effektiv unter Windows nutzen kann.

Ruby Ports

Windows stellt selber keine POSIX-Umgebung zur Verfügung, deshalb braucht man eine Art von Emulations-Bibliothek, um die nötigen Funktionen zu unterstützen. Es gibt für Ruby verschiedene Ports für Windows: der gebräuchlichste beruht auf der GNU-Win32-Umgebung und heißt ``cygwin32''-Port. Dieser cygmin32-Port arbeitet gut mit Erweiterungs-Bibliotheken zusammen und es gibt ihn im Web als vorkompilierte Binärdatei. Ein anderer Port, ``mswin32,'' beruht nicht auf cygwin. Diesen gibt es momentan nur als Quellcode. Im Rest dieses Kapitels beziehen wir uns auf den cygwin32-Port.

Ruby unter Windows laufen lassen

In der cygwin32-Ruby-Distribution sind zwei ausführbare Dateien enthalten: ruby.exe und rubyw.exe.

ruby.exe benutzt man mit der Kommandozeile (eine DOS-Shell), genauso wie in der Unix-Version. Das ist prima bei Applikationen, die vom Standard-In- und Output lesen und schreiben. Es heißt aber auch, dass jedes Mal, wenn man ruby.exe laufen lässt, eine DOS-Shell erscheint, auch wenn man sie gar nicht haben will --- Windows erzeugt automatisch ein neues Kommandozeilen-Fenster, während Ruby läuft. Das ist manchmal nicht so das passende Verhalten, wenn man etwa auf ein Ruby-Script doppelklickt, das ein graphisches Interface (wie Tk) benutzt, oder wenn man ein RubyScript als Hintergrundprozess von einer anderen Anwendung aus laufen lässt.

In all diesen Fällen wird man dann stattdessen rubyw.exe nehmen. Das ist genau das gleiche wie ruby.exe, außer dass es kein Standard-In, kein Standard-Out und kein Standard-Error unterstützt und dass eben keine DOS-Shell gestartet wird.

Man kann auch Dateiverknüpfungen benutzen,[Mit View/Options/Filetypes bzw Ansicht/Ordneroptionen/Dateitypen im Explorer.] so dass Dateien mit der Endung ``.rb'' automatisch von rubyw.exe ausgeführt werden. Damit kann man dann mit einem Doppelklick Ruby-Scripts starten, ohne dass das lästige DOS-Fenster aufpoppt.

Win32API

Wenn man bei der Ruby-Programmierung direkt auf Windows32-API-Funktionen zugreifen will oder die Einstiegspunkte von irgendwelchen anderen DLLs benutzen möchte, da gibt es eine feine Sache, nämlich --- die Win32API-Erweiterung.

Das Win32API-Modul wird ab Seite 512 beschrieben, aber hier gibts erstmal einen kleinen Überblick darüber, wie es funktioniert..

Man erzeugt ein Win32API-Objekt, das den Aufruf eines speziellen DLL-Einstiegspunkts repräsentiert, indem man den Namen der Funktion angibt, den Namen der diese Funktion enthaltenden DLL sowie die Signatur der Funktion (Typen der Argumente und Rückgabetyp). Mit dem erzeugten Objekt kann man dann den Funktionsaufruf realisieren.

Viele dieser Argumente für die DLL-Funktionen sind irgendwelche binäre Strukturen. Diese werden von Win32API als Ruby-String-Objekte weitergereicht. Diese muss man notfalls packen und entpacken (siehe das Beispiel auf Seite 512).

Windows Automation

Wenn einem das Herumkriechen in den Niederungen von Windows API nicht so interessiert, dann vielleicht die Windows-Automation --- dank einer Erweiterung namens WIN32OLE von Masaki Suketa kann man Ruby als Client für diese Windows Automation einsetzen. Die Beispiele in diesem Abschnitt stammen alle aus dieser WIN32OLE-Distribution.

Windows Automation gibt einem Automations-Controller (ein Client) die Möglichkeit, Kommandos und Anfragen an einen Automations-Server zu richten, wie etwa Microsoft Excel, Word, Power Point und so weiter.

Man führt eine Methode eines Automations-Servers aus, indem man eine Methode mit dem selben Namen eines WIN32OLE-Objekts aufruft. Als Beispiel erzeugen wir einen neuen WIN32OLE-Client, der eine neue Ausführung des Internet-Explorers startet und ihm sagt, die Home-Page zu besuchen.

ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = true
ie.gohome

Wenn eine Methode bei WIN32OLE nicht bekannt ist (wie etwa visible oder gohome), wird sie an die WIN32OLE#invoke-Methode weitergereicht, die die passenden Kommandos an den Server sendet. Die Referenz für WIN32OLE ab Seite 509 beschreibt diese Klasse genauer, aber wir werden hier schon ein paar Einzelheiten behandeln.

Properties setzen und lesen

Um die properties eines Servers zu setzen, benutzt man die normale Ruby-Hash-Notation. Wenn man etwa das Rotation-Property in einer Excel-Tabelle setzen will, könnte man das so schreiben:

excel = WIN32OLE.new("excel.application")
excelchart = excel.Charts.Add()
...
excelchart['Rotation'] = 45
puts excelchart['Rotation']

Die Parameter eines OLE-Objekts werden automatisch als Attribute des WIN32OLE-Objekts eingerichtet. Das heißt man kann einen Parameter setzen, indem man etwas einem Objekt-Attribut zuweist.

excelchart.rotation = 45
r = excelchart.rotation

Weil diese Methoden ganz normale Ruby-Zugriffs-Methoden sind, dürfen die Attributs-Namen nicht mit einem Großbuchstaben anfangen. In diesem Beispiel mussten wir rotation an Stelle von Rotation benutzen.

Benamte Argumente

Andere Automations-Clients wie Visual Basic kennen das Konzept der benamten Argumente. Man hat etwa eine Visual-Basic-Routine mit der fogenden Signatur:

Song(artist, title, length):    rem Visual Basic

Statt sie mit den drei Argumenten in der vorgegebenen Reihenfolge aufzurufen, kann man auch benamte Argumente benutzen.

Song title := 'Get It On':      rem Visual Basic

Das ist dann äquivalent zu Song(nil, 'Get It On', nil).

In Ruby kann man das machen, indem man einen Hash mit den benamten Argumenten übergibt.

Song.new( 'title' => 'Get It On' )

for each

Wo Visual Basic eine ``for each''-Anweisung besitzt, um über eine Ansammlung von Dingen zu iterieren, besitzt ein WIN32OLE-Objekt eine each-Methode (die einen Block erwartet), um genau das selbe zu erreichen.

Ein Beispiel

Das folgende Beispiel mit Microsoft Excel zeigt im Großen und Ganzen diese Konzepte. Zuerst erzeugen wir ein neues WIN32OLE-Excel-Objekt und setzen ein paar Werte ein. Als Nächstes wählen wir einen Bereich von Zellen aus und erzeugen eine Tabelle. Wir setzen das Type-Property dieses excelchart-Objekts, um daraus eine 3D-Tabelle zu machen. Dann laufen wir durch diese Tabelle und ändern die Tabellen-Rotation, 10° nacheinander. Wir fügen noch ein paar Einträge hinzu und laufen mit each einmal durch und drucken das Ganze aus. Am Ende schließen wir die Excel-Applikation und beenden das Programm

require 'win32ole'

# -4100 is the value for the Excel constant xl3DColumn. ChartTypeVal = -4100;

# Creates OLE object to Excel excel = WIN32OLE.new("excel.application")

# Create and rotate the chart

excel['Visible'] = TRUE; workbook = excel.Workbooks.Add(); excel.Range("a1")['Value'] = 3; excel.Range("a2")['Value'] = 2; excel.Range("a3")['Value'] = 1; excel.Range("a1:a3").Select(); excelchart = workbook.Charts.Add(); excelchart['Type'] = ChartTypeVal;

30.step(180, 10) do |rot|     excelchart['Rotation'] = rot end

excelchart2 = workbook.Charts.Add(); excelchart3 = workbook.Charts.Add();

charts = workbook.Charts charts.each { |i| puts i }

excel.ActiveWorkbook.Close(0); excel.Quit();

Optimierung

Wie bei den meisten (wenn nicht sogar allen) hoch-leveligen Sprachen ist es nur allzu leicht, am laufenden Band Code zu produzieren, der untragbar langsam ist, aber dem kann man mit ein bißchen Nachdenken entgegengehen.

Bei WIN32OLE muss man mit unnötigen dynamischen Zugriffen vorsichtig sein. Wenn möglich sollte man das WIN32OLE-Objekt einer Variablen zuweisen und darüber auf Elemente referenzieren, statt eine lange Kette von .-Ausdrücken zu erzeugen.

Zum Beispiel sollte man statt zu schreiben

workbook.Worksheets(1).Range("A1").value = 1
workbook.Worksheets(1).Range("A2").value = 2
workbook.Worksheets(1).Range("A3").value = 4
workbook.Worksheets(1).Range("A4").value = 8

besser die gemeinsamen Unterausdrücke vermeiden, den vorderen Teil des Ausdrucks einer temporären Variablen zuweisen und die Aufrufe dann von dieser Variablen aus durchführen:

worksheet = workbook.Worksheets(1)

worksheet.Range("A1").value = 1 worksheet.Range("A2").value = 2 worksheet.Range("A3").value = 4 worksheet.Range("A4").value = 8


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Jürgen Katins
Für das englische Original:
© 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0. That reference is available for download.
Diese Lizenz sowie das Original vom Herbst 2001 bilden die Grundlage der Übersetzung
Es wird darauf hingewiesen, dass sich die Lizenz des englischen Originals inzwischen geändert hat.
Für die deutsche Übersetzung:
© 2002 Jürgen Katins
Der Copyright-Eigner stellt folgende Lizenzen zur Verfügung:
Nicht-freie Lizenz:
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Freie Lizenz:
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".