- •Programming Ruby The Pragmatic Programmer's Guide
- •Foreword
- •Preface
- •Ruby Sparkles
- •What Kind of Language Is Ruby?
- •Is Ruby for Me?
- •Why Did We Write This Book?
- •Ruby Versions
- •Installing Ruby
- •Building Ruby
- •Running Ruby
- •Interactive Ruby
- •Ruby Programs
- •Resources
- •Acknowledgments
- •Notation Conventions
- •Roadmap
- •Ruby.New
- •Ruby Is an Object-Oriented Language
- •Some Basic Ruby
- •Arrays and Hashes
- •Control Structures
- •Regular Expressions
- •Blocks and Iterators
- •Reading and 'Riting
- •Onward and Upward
- •Classes, Objects, and Variables
- •Inheritance and Messages
- •Inheritance and Mixins
- •Objects and Attributes
- •Writable Attributes
- •Virtual Attributes
- •Class Variables and Class Methods
- •Class Variables
- •Class Methods
- •Singletons and Other Constructors
- •Access Control
- •Specifying Access Control
- •Variables
- •Containers, Blocks, and Iterators
- •Containers
- •Implementing a SongList Container
- •Blocks and Iterators
- •Implementing Iterators
- •Blocks for Transactions
- •Blocks Can Be Closures
- •Standard Types
- •Numbers
- •Strings
- •Working with Strings
- •Ranges as Sequences
- •Ranges as Conditions
- •Ranges as Intervals
- •Regular Expressions
- •Patterns
- •Anchors
- •Character Classes
- •Repetition
- •Alternation
- •Grouping
- •Pattern-Based Substitution
- •Backslash Sequences in the Substitution
- •Object-Oriented Regular Expressions
- •More About Methods
- •Defining a Method
- •Variable-Length Argument Lists
- •Methods and Blocks
- •Calling a Method
- •Expanding Arrays in Method Calls
- •Making Blocks More Dynamic
- •Collecting Hash Arguments
- •Expressions
- •Operator Expressions
- •Miscellaneous Expressions
- •Command Expansion
- •Backquotes Are Soft
- •Assignment
- •Parallel Assignment
- •Nested Assignments
- •Other Forms of Assignment
- •Conditional Execution
- •Boolean Expressions
- •Defined?, And, Or, and Not
- •If and Unless Expressions
- •If and Unless Modifiers
- •Case Expressions
- •Iterators
- •Break, Redo, and Next
- •Variable Scope and Loops
- •Exceptions, Catch, and Throw
- •The Exception Class
- •Handling Exceptions
- •Tidying Up
- •Play It Again
- •Raising Exceptions
- •Adding Information to Exceptions
- •Catch and Throw
- •Modules
- •Namespaces
- •Instance Variables in Mixins
- •Iterators and the Enumerable Module
- •Including Other Files
- •Basic Input and Output
- •What Is an io Object?
- •Opening and Closing Files
- •Reading and Writing Files
- •Iterators for Reading
- •Writing to Files
- •Talking to Networks
- •Threads and Processes
- •Multithreading
- •Creating Ruby Threads
- •Manipulating Threads
- •Thread Variables
- •Threads and Exceptions
- •Controlling the Thread Scheduler
- •Mutual Exclusion
- •The Mutex Class
- •Condition Variables
- •Running Multiple Processes
- •Spawning New Processes
- •Independent Children
- •Blocks and Subprocesses
- •When Trouble Strikes
- •Ruby Debugger
- •Interactive Ruby
- •Editor Support
- •But It Doesn't Work!
- •But It's Too Slow!
- •Create Locals Outside Blocks
- •Use the Profiler
- •Ruby and Its World
- •Command-Line Arguments
- •Command-Line Options
- •Program Termination
- •Environment Variables
- •Writing to Environment Variables
- •Where Ruby Finds Its Modules
- •Build Environment
- •Ruby and the Web
- •Writing cgi Scripts
- •Using cgi.Rb
- •Quoting
- •Creating Forms and html
- •Cookies
- •Sessions
- •Embedding Ruby in html
- •Using eruby
- •Installing eruby in Apache
- •Improving Performance
- •Ruby Tk
- •Simple Tk Application
- •Widgets
- •Setting Widget Options
- •Getting Widget Data
- •Setting/Getting Options Dynamically
- •Sample Application
- •Binding Events
- •Scrolling
- •Just One More Thing
- •Translating from Perl/Tk Documentation
- •Object Creation
- •Running Ruby Under Windows
- •Windows Automation
- •Getting and Setting Properties
- •Named Arguments
- •For each
- •An Example
- •Optimizing
- •Extending Ruby
- •Ruby Objects in c
- •Value as a Pointer
- •Value as an Immediate Object
- •Writing Ruby in c
- •Evaluating Ruby Expressions in c
- •Sharing Data Between Ruby and c
- •Directly Sharing Variables
- •Wrapping c Structures
- •An Example
- •Memory Allocation
- •Creating an Extension
- •Creating a Makefile with extconf.Rb
- •Static Linking
- •Embedding a Ruby Interpreter
- •Bridging Ruby to Other Languages
- •Ruby c Language api
- •The Ruby Language
- •Source Layout
- •Begin and end Blocks
- •General Delimited Input
- •The Basic Types
- •Integer and Floating Point Numbers
- •Strings
- •Requirements for a Hash Key
- •Symbols
- •Regular Expressions
- •Regular Expression Options
- •Regular Expression Patterns
- •Substitutions
- •Extensions
- •Variable/Method Ambiguity
- •Variables and Constants
- •Scope of Constants and Variables
- •Predefined Variables
- •Exception Information
- •Pattern Matching Variables
- •Input/Output Variables
- •Execution Environment Variables
- •Standard Objects
- •Global Constants
- •Expressions Single Terms
- •Operator Expressions
- •More on Assignment
- •Parallel Assignment
- •Block Expressions
- •Boolean Expressions
- •Truth Values
- •And, Or, Not, and Defined?
- •Comparison Operators
- •Ranges in Boolean Expressions
- •Regular Expressions in Boolean Expressions
- •While and Until Modifiers
- •Break, Redo, Next, and Retry
- •Method Definition
- •Method Arguments
- •Invoking a Method
- •Class Definition
- •Creating Objects from Classes
- •Class Attribute Declarations
- •Module Definitions
- •Mixins---Including Modules
- •Module Functions
- •Access Control
- •Blocks, Closures, and Proc Objects
- •Proc Objects
- •Exceptions
- •Raising Exceptions
- •Handling Exceptions
- •Retrying a Block
- •Catch and Throw
- •Classes and Objects
- •How Classes and Objects Interact
- •Your Basic, Everyday Object
- •Object-Specific Classes
- •Mixin Modules
- •Extending Objects
- •Class and Module Definitions
- •Class Names Are Constants
- •Inheritance and Visibility
- •Freezing Objects
- •Locking Ruby in the Safe
- •Safe Levels
- •Tainted Objects
- •Reflection, ObjectSpace, and Distributed Ruby
- •Looking at Objects
- •Looking Inside Objects
- •Looking at Classes
- •Looking Inside Classes
- •Calling Methods Dynamically
- •Performance Considerations
- •System Hooks
- •Runtime Callbacks
- •Tracing Your Program's Execution
- •How Did We Get Here?
- •Marshaling and Distributed Ruby
- •Custom Serialization Strategy
- •Distributed Ruby
- •Compile Time? Runtime? Anytime!
- •Standard Library
Creating an Extension
Having written the source code for an extension, we now need to compile it so Ruby can use it. We can either do this as a shared object, which is dynamically loaded at runtime, or statically link the extension into the main Ruby interpreter itself. The basic procedure is the same:
Create the C source code file(s) in a given directory.
Create extconf.rb.
Run extconf.rbto create aMakefilefor the C files in this directory.
Run make.
Run make install.
Figure not available... |
Creating a Makefile with extconf.Rb
Figure 17.2 on page 182 shows the overall workflow when building an extension. The key to the whole process is the extconf.rbprogram which you, as a developer, create. Inextconf.rb, you write a simple program that determines what features are available on the user's system and where those features may be located. Executingextconf.rbbuilds a customizedMakefile, tailored for both your application and the system on which it's being compiled. When you run themakecommand against thisMakefile, your extension is built and (optionally) installed.
The simplest extconf.rbmay be just two lines long, and for many extensions this is sufficient.
require 'mkmf' create_makefile("Test") |
The first line brings in the mkmflibrary module (documented beginning on page 451). This contains all the commands we'll be using. The second line creates aMakefilefor an extension called ``Test.'' (Note that ``Test'' is the name of the extension; the makefile will always be called ``Makefile.'')Testwill be built from all the C source files in the current directory.
Let's say that we run this extconf.rbprogram in a directory containing a single source file,main.c. The result is aMakefilethat will build our extension. On our system, this contains the following commands.
gcc -fPIC -I/usr/local/lib/ruby/1.6/i686-linux -g -O2 \ -c main.c -o main.o gcc -shared -o Test.so main.o -lc |
The result of this compilation is Test.so, which may be dynamically linked into Ruby at runtime with ``require''. See how themkmfcommands have located platform-specific libraries and used compiler-specific options automatically. Pretty neat, eh?
Although this basic program works for many simple extensions, you may have to do some more work if your extension needs header files or libraries that aren't included in the default compilation environment, or if you conditionally compile code based on the presence of libraries or functions.
A common requirement is to specify nonstandard directories where include files and libraries may be found. This is a two-step process. First, your extconf.rbshould contain one or moredir_configcommands. This specifies a tag for a set of directories. Then, when you run theextconf.rbprogram, you tellmkmfwhere the corresponding physical directories are on the current system.
If extconf.rbcontains the linedir_config(name), then you give the location of the corresponding directories with the command-line options:
--with-name-include=directory
* Add directory/includeto the compile command.
--with-name-lib=directory
* Add directory/libto the link command.
If (as is common) your include and library directories are both subdirectories of some other directory, and (as is also common) they're called includeandlib, you can take a shortcut:
--with-name-dir=directory
* Add directory/libanddirectory/includeto the link command and compile command, respectively.
There's a twist here. As well as specifying all these --withoptions when you runextconf.rb, you can also use the--withoptions that were specified when Ruby was built for your machine. This means you can find out the locations of libraries that are used by Ruby itself.
To make all this concrete, lets say you need to use libraries and include files for the CD jukebox we're developing. Your extconf.rbprogram might contain
require 'mkmf' dir_config('cdjukebox') # .. more stuff create_makefile("CDJukeBox") |
You'd then run extconf.rbwith something like:
% ruby extconf.rb --with-cdjukebox-dir=/usr/local/cdjb |
The generated Makefilewould assume that the libraries were in/usr/local/cdjb/liband the include files were in/usr/local/cdjb/include.
The dir_configcommand adds to the list of places to search for libraries and include files. It does not, however, link the libraries into your application. To do that, you'll need to use one or morehave_libraryorfind_librarycommands.
have_librarylooks for a given entry point in a named library. If it finds the entry point, it adds the library to the list of libraries to be used when linking your extension.find_libraryis similar, but allows you to specify a list of directories to search for the library.
require 'mkmf' dir_config('cdjukebox') have_library('cdjb', 'CDPlayerNew') create_makefile("CDJukeBox") |
On some platforms, a popular library may be in one of several places. The X Window system, for example, is notorious for living in different directories on different systems. The find_librarycommand will search a list of supplied directories to find the right one (this is different fromhave_library, which uses only configuration information for the search). For example, to create aMakefilethat uses X Windows and a jpeg library,extconf.rbmight contain
require 'mkmf'
if have_library("jpeg","jpeg_mem_init") and find_library("X11", "XOpenDisplay", "/usr/X11/lib", "/usr/X11R6/lib", "/usr/openwin/lib") then create_makefile("XThing") else puts "No X/JPEG support available" end |
We've added some additional functionality to this program. All of the mkmfcommands returnfalseif they fail. This means that we can write anextconf.rbthat generates aMakefileonly if everything it needs is present. The Ruby distribution does this so that it will try to compile only those extensions that are supported on your system.
You also may want your extension code to be able to configure the features it uses depending on the target environment. For example, our CD jukebox may be able to use a high-performance MP3 decoder if the end user has one installed. We can check by looking for its header file.
require 'mkmf' dir_config('cdjukebox') have_library('cdjb', 'CDPlayerNew') have_header('hp_mp3.h') create_makefile("CDJukeBox") |
We can also check to see if the target environment has a particular function in any of the libraries we'll be using. For example, the setprioritycall would be useful but isn't always available. We can check for it with:
require 'mkmf' dir_config('cdjukebox') have_func('setpriority') create_makefile("CDJukeBox") |
Both have_headerandhave_funcdefine preprocessor constants if they find their targets. The names are formed by converting the target name to uppercase and prepending ``HAVE_''. Your C code can take advantage of this using constructs such as:
#if defined(HAVE_HP_MP3_H) # include <hp_mp3.h> #endif
#if defined(HAVE_SETPRIORITY) err = setpriority(PRIOR_PROCESS, 0, -10) #endif |
If you have special requirements that can't be met with all these mkmfcommands, your program can directly add to the global variables$CFLAGSand$LFLAGS, which are passed to the compiler and linker, respectively.