Thursday, December 2, 2010

Ruby, SWIG, Visual Studio 2008 and Windows 7

If you are like me, you might be wondering why it is so hard to do simple things.

Well this blog is going to be of great use to you if you are trying to have your Ruby code call into your C++ code in a Microsoft development environment. Why does anybody care you ask?

Well, some of us are still stuck in legacy land. The happy, or not so happy, land of C++. The world of software is changing, and those poor souls like you and me are trying to adapt. What are we adapting to you ask? ATDD and TDD.

So if you look around at the world of TDD and C++, the Visual Studio kind, which I might add is not really C++, it is best labeled as VC++, you will find that there are great tools like CPPUTest, Google Test, Boost, etc. This is great for doing TDD and unit testing, but what happens when you want to do ATDD? Well, generally nothing.

But not today. Today is the dawning of a new day, where you can use an awesome language like Ruby to call into your legacy code and start fixing it. Yes, not all of us have the luxury of green field rewrites.

So, here we go:

1. Purpose

To setup a ruby 1.8.7 environment on a windows 7 machine that can be used with SWIG to interface ruby with Visual Studio 2008.

2. Environment
  • Windows 7 64-bit running on a Macbook Pro.
  • Visual Studio 2008
  • Ruby 1.8.7
  • SWIG 2.0.1
3. Detailed steps to get Ruby built and installed

0. Run shortcut for Visual Studio 2008: "%comspec% /k ""c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" x86" as Administrator
1. Extract "ruby-1.8.7-p160.tar.gz" to c:\ruby-1.8.7-p160
1.a. Extract "zlib123.zip" to c:\zlib123
1.b. In the command prompt: cd c:\zlib123
1.c. In the command prompt: nmake -f win32\Makefile.msc
1.d. In the command prompt: nmake -f win32\Makefile.msc test
1.e. In the command prompt: copy C:\zlib123\zlib.h "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include"
1.f. In the command prompt: copy C:\zlib123\zconf.h "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include"
1.g. In the command prompt: copy C:\zlib123\zlib.lib "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib"
1.h. In the command prompt: copy C:\zlib123\zdll.lib "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib"
1.i. In the command prompt: copy C:\zlib123\zdll.exp "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib"
3. In the command prompt: cd c:\ruby-1.8.7-p160\win32
4. In the command prompt: configure
5. In the command prompt: nmake
6. In the command prompt: nmake DESTDIR=c:\ruby187P160 install
7. In the command prompt: cd c:\ruby187P160\bin
8. In the command prompt: copy C:\zlib123\zlib1.dll c:\ruby187P160\bin
9. Add c:\ruby187P160\bin to the environment variable PATH
10. Copy the following files to c:\ruby187P160\bin: dbm3.dll, gdbm3.dll, libeay32-0.9.8-msvcrt.dll, libiconv2.dll, pdcurses.dll, ssleay32-0.9.8-msvcrt.dll, zlib1.dll
11. Create a file: setrbvars.bat

@ECHO OFF
REM Determine where is RUBY_BIN (where this script is)
PUSHD %~dp0.
SET RUBY_BIN=%CD%
POPD

SET RUBYOPT=

REM Add RUBY_BIN to the PATH
REM RUBY_BIN takes higher priority to avoid other tools
REM conflict with our own (mainly the DevKit)
SET PATH=%RUBY_BIN%;%PATH%
SET RUBY_BIN=

REM Display Ruby version
ruby.exe -v

12. In the command prompt: setrbvars
13. Extract "rubygems-1.3.7.zip" to c:\rubygems-1.3.7
14. In the command prompt: cd c:\rubygems-1.3.7
15. In the command prompt: ruby setup.rb
17. open "C:\ruby187P160\lib\ruby\site_ruby\1.8\rubygems\package\tar_input.rb"
18. edit "zipped_stream(entry)" (line 203--216) as follows:

def zipped_stream(entry)
if defined? Rubinius then
zis = Zlib::GzipReader.new entry
dis = zis.read
is = StringIO.new(dis)
else
# This is Jamis Buck's Zlib workaround for some unknown issue
entry.read(10) # skip the gzip header
zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)

######### EDIT START ###
#is = StringIO.new(zis.inflate(entry.read))

__buf = ""
while __temp = entry.read( 128 * 1024 ) do
__buf << zis.inflate( __temp )
end

is = StringIO.new( __buf )
######### EDIT END #####

end
ensure
zis.finish if zis
end

19. In the command prompt: gem install rails rspec rspec-rails webrat cucumber

4. Detailed steps to create a Visual Studio c++ 2008 project to work with Ruby

1. Open Visual Studio 2008
2. Create a new "Visaul C++" Project
3. Choose MFC DLL
4. Name the project "CodingHappy"
5. Just use the defaults
6. Delete all the files but "CodingHappy.h" and "CodingHappy.cpp"
7. Under the Property Pages, in "Configuration Properties" -> "General" choose "Use Standard Windows Libraries"
8. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "General" -> "Additional Include Directories" enter: "C:\ruby187P160\lib\ruby\1.8\i386-mswin32_90"
9. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "Preprocessor" -> "Preprocessor Definitions" enter: "_DEBUG;_WINDOWS;WIN32;_USRDLL;EXAMPLE_EXPORTS;NT=1;IMPORT"
10. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "Code Generation" -> "Code Generation" choose "Multi-threaded Debug (/MTd)"
11. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "Precompiled Headers" -> "Create/Use Precompiled Header" choose "Not Using Precompiled Headers"
12. Under the Property Pages, in "Configuration Properties" -> "Linker" -> "Input" -> "Additional Dependencies" enter: "C:\ruby187P160\lib\msvcr90-ruby18.lib"
13. Under the Property Pages, in "Configuration Properties" -> "Linker" -> "Input" -> "Module Definition File" enter: ""
14. Add a new item, a C++ file -> "CodingHappy_wrap.cxx"
15. Add a new item, an "i" file -> "CodingHappy.i"
16. Add the following to the file

/* CodingHappy.i */
%module CodingHappy
%{
/* Put header files here or function declarations like below */
extern double pi;
extern int factorial(int n);
extern int permutation(int n,int r);
%}

extern double pi;
extern int factorial(int n);
extern int permutation(int n,int r);

17. In CodingHappy.h enter the following:

#ifndef CODINGHAPPY_H
#define CODINGHAPPY_H

int factorial(int n);
int permutation(int n, int r);

#endif

18. In CodingHappy.cpp enter the following:

// CodingHappy.cpp
//

double pi = 3.14159; //not used yet

int factorial(int n) {
if (n <= 1)
return 1;
else
return n*factorial(n-1);
}

int permutation(int n, int r)
{
return factorial(n) / factorial(n-r);
}

20. In the properties for CodingHappy.i on "Configuration Properties" -> "General" -> "Custom Build Step" -> "General" -> "Command Line" enter: "C:\Users\Public\Documents\BDD\swigwin-2.0.1\swig -c++ -ruby $(InputPath)" - note: make sure your path has no spaces, or hard-code it here
21. In the properties for CodingHappy.i on "Configuration Properties" -> "General" -> "Custom Build Step" -> "General" -> "Outputs" enter: "$(ProjectDir)\$(InputName)_wrap.cxx"