Programming Ruby: The Pragmatic Programmer's Guide

# Programming Ruby ### The Pragmatic Programmer's Guide
Previous < | Contents ^ | Next >

# Ruby and Microsoft Windows

Ruby is written for POSIX environments, which means that it can take advantage of all of the system calls and libraries that Unix programmers are familiar with.

But there are a number of features and extensions designed to make Ruby more useful in a Microsoft Windows environment, too. In this chapter, we'll look at these features and share some secrets to using Ruby effectively under Windows.

## Ruby Ports

Windows does not provide a POSIX environment by itself, so some sort of emulation library is required in order to provide the necessary functions. There are several ports of Ruby for Windows: the most commonly used one relies on the GNU Win32 environment, and is called the ``cygwin32'' port. The cygwin32 port works well with extension libraries, and is available on the Web as a precompiled binary. Another port, ``mswin32,'' does not rely on cygwin. It is currently available as source code only. The remainder of this chapter will refer to the cygwin32 port.

## Running Ruby Under Windows

There are two executables provided with the cygwin32 Ruby distribution: `ruby.exe` and `rubyw.exe`.

`ruby.exe` is meant to be used at a command prompt (a DOS shell), just as in the Unix version. For applications that read and write to the standard input and output, this is fine. But that also means that anytime you run `ruby.exe`, you'll get a DOS shell even if you don't want one---Windows will create a new command prompt window and display it while Ruby is running. This might not be appropriate behavior if, for example, you double-click on a Ruby script that uses a graphical interface (such as Tk), or if you are running a Ruby script as a background task, or from inside another program.

In these cases, you'll want to use `rubyw.exe`. It is the same as `ruby.exe` except that it does not provide standard in, standard out, or standard error, and does not launch a DOS shell when run.

You can set a file association[Using `View/Options/Filetypes` from Explorer.] so that files with the extension ```.rb`'' will automatically use `rubyw.exe`. By doing this, you can double-click on Ruby scripts and they will simply run without popping up a DOS shell.

## Win32API

If you plan on doing Ruby programming that needs to access some Windows 32 API functions directly, or to use the entry points in some other DLLs, we've got good news for you---the `Win32API` extension.

The `Win32API` module is documented beginning on page 508, but here's a quick peek at how it works.

You create a `Win32API` object that represents a call to a particular DLL entry point by specifying the name of the function, the name of the DLL that contains the function, and the function signature (argument types and return type). The resulting object can then be used to make the function call.

Many of the arguments to DLL functions are binary structures of some form. `Win32API` handles this by using Ruby `String` objects to pass the binary data back and forth. You will need to pack and unpack these strings as necessary (see the example on page 508).

## Windows Automation

If groveling around in the low-level Windows API doesn't interest you, Windows automation might---you can use Ruby as a client for Windows Automation thanks to a Ruby extension called `WIN32OLE`, written by Masaki Suketa. The examples in this section are taken from those provided in the `WIN32OLE` distribution.

Windows automation allows an automation controller (a client) to issue commands and queries against an automation server, such as Microsoft Excel, Word, PowerPoint, and so on.

You can execute a method of an automation server by calling a method of the same name from a `WIN32OLE` object. For instance, you can create a new `WIN32OLE` client that launches a fresh copy of Internet Explorer and commands it to visit the home page.

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

Methods that aren't known to `WIN32OLE` (such as `visible` or `gohome`) are passed on to the `WIN32OLE#invoke` method, which sends the proper commands to the server. The `WIN32OLE` reference beginning on page 505 describes the class in detail, but we'll go over a few of its features here.

### Getting and Setting Properties

You can set and get properties from the server using normal Ruby hash notation. For example, to set the `Rotation` property in an Excel chart, you might write

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

An OLE object's parameters are automatically set up as attributes of the `WIN32OLE` object. This means that you can set a parameter by assigning to an object attribute.

    excelchart.rotation = 45
    r = excelchart.rotation

Because these attributes are conventional Ruby accessor methods, attribute names cannot start with a capital letter. In this example, we have to use `rotation` instead of `Rotation`.

### Named Arguments

Other automation client languages such as Visual Basic have the concept of named arguments. Suppose you had a Visual Basic routine with the signature:

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

Instead of calling it with all three arguments in the order specified, you could use named arguments.

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

This is equivalent to the call `Song(nil, 'Get It On', nil)`.

In Ruby, you can use this feature by passing a hash with the named arguments.

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

### for each

Where Visual Basic has a ``for each'' statement to iterate over a collection of items in a server, a `WIN32OLE` object has an `each` method (which takes a block) to accomplish the same thing.

### An Example

The following example, using Microsoft Excel, illustrates most of these concepts. First, we create a new `WIN32OLE` object attached to Excel and set some cell values. Next we select a range of cells and create a chart. We set the `Type` property in the `excelchart` object to make it a 3D chart. Next we'll loop through and change the chart rotation, 10° at a time. We'll add a few charts, and we'll use `each` to step through and print them out. Finally, we'll close down the Excel application and exit.

    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();

### Optimizing

As with most (if not all) high-level languages, it can be all too easy to churn out code that is unbearably slow, but that can be easily fixed with a little thought.

With `WIN32OLE`, you need to be careful with unnecessary dynamic lookups. Where possible, it is better to assign a `WIN32OLE` object to a variable and then reference elements from it, rather than creating a long chain of ``.'' expressions.

For example, instead of writing

    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

we can eliminate the common subexpressions by saving the first part of the expression to a temporary variable and then make calls from that variable:

    worksheet = workbook.Worksheets(1)

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

Previous < | Contents ^ | Next >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0.
This reference is available for download.