Writing a Win32 Application in SBCL Common Lisp

The sucess of dynamic languages such as Python and Ruby in the web has created a renewed interest in Lisp. After all, Lisp is the grandfather of all dynamic languages. 

For Windows users, however, it has been traditionally difficult to find good implementations of Common Lisp. The main implementations of Lisp have some difficulties that make them unsuitable to use on Windows. For example, CMU-CL runs only on UN*X systems. Allegro Lisp (from Franz Inc.) runs well on Windows, but is not free. CLisp runs on Windows using cygwin, but it is not well integrated on Windows, and is an interpreted Common Lisp.

Recently, though, SBCL, a variant of CMU-CL has been ported to Windows. There are several things that make SBCL attractive as a development compiler.

 

  • It is really fast
  • It is stable, with a large community of dedicated Lisp users
  • Generates executables, so you can distribute your program without saying in which language it was created — good if you want to sell your programs.

 

To find out if SBCL is really suitable for general development on Windows, I decided to create a graphical Win32 application. If that works, then it should be safe to use SBCL to write Windows applications. In this article, I will explain how to use SBCL to write such a Win32 application. 

Installing SBCL

The first step is to install the SBCL sytem. You can get it from http://www.sbcl.org/ and use the installer. The installer will even put the executable directory of sbcl in your path.

To create windows applications, however, you will need a windows calling library, created by Alastair Bridgewater. With this little piece of glue code, you can directly call the Win32 API functions. You can also create a callback function in Common Lisp, and have it passed into the Windows functions.  This is how a window procedure looks like:

 (defun window-procedure (hwnd imsg wparam lparam)

(cond

    ((= imsg wm_paint)   (on-window-paint hwnd lparam) 0)
    ((= imsg wm_lbuttondown) (messagebox hwnd "test" "gedit" 0) 0)
    ((= imsg wm_lbuttondown) (on-mouse-down hwnd lparam) 0)
    ((= imsg wm_destroy) (postquitmessage 0) 0)
    (t (defwindowproc hwnd imsg wparam lparam))))

The Drawing Application

The application I created is a simple drawing program. It just draws a line whenever we click, extending from the previous mouse location to the current mouse location. Here is the portion of code that captures the mouse location:

(defvar *point1* (make-my-point :x 10 :y 10))
(defvar *point2* (make-my-point :x 100 :y 100))
(defun on-mouse-down (hwnd lparam)
  ; save current point
  (setf (my-point-x *point1*) (my-point-x *point2*))
  (setf (my-point-y *point1*) (my-point-y *point2*))
  ; set second point
  (set-point-from-windows-msg lparam *point2*)
  (invalidaterect hwnd nil t)))

Notice that we receive the mouse coodinates in the lparam argument, which is in encoded format (as usual in the Win32 API). We need to define the function set-point-from-windows-msg to get the real position of the new point. This function can be defined as follows:

(defun set-point-from-windows-msg (lparam point)
  "lparam is a 32 bit value encoding two 16 bit words.
  we create a point from lparam using these two words."
  (let ((s 16))
    (setf (my-point-x point) (mod lparam #x10000))
    (setf (my-point-y point) (ash lparam (- s)))))

Here we are using two Common Lisp arithmetic functions:

 

  • mod returns the module of the division of the first number by the second number. In this case, it returns the low bytes in a 32 bit word.
  • ash shifts the integer value by the number of positions in the second argument. In this case, it shifts lparam 16 positions to the right, and returns the high bytes in a 32 bit word.
Now, we can just paint the two points when necessary (the update is automatically called using the function invalidaterect).

 

(defun paint-line (hdc hwnd rect rect-addr)
  (moveto hdc (my-point-x *point1*) (my-point-y *point1*))
  (lineto hdc (my-point-x *point2*) (my-point-y *point2*)))

(defun on-window-paint (hwnd lparam)
  (with-alien ((ps (struct paintstruct)) (rect (struct rect)))
              (let ((hdc (beginpaint hwnd (addr ps))))
                (paint-line hdc hwnd rect (addr rect))
                (endpaint hwnd (addr ps)))))

Running the Application

You can download the application from here, including the win32 glue files. To run the application, you just need to call

sbcl geditor.lisp 

The program is simple, but it shows that SBCL can handle most features required to write windows programs. The SBCL port for Windows is, however, still experimental (notice the warning printed when sbcl is run). I expect, however, that it will evolve into a great option for Lisp programmers that need to access win32 features.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

Adding a bootsplash screen for Linux

Adding a bootsplash  screen for Linux is a simple process, that can make you distribution look more personalized.

I followed the instructions in the web site bootsplash.org. Here is a quick summary of the necessary steps. (note: I used topologilinux 3.1, a simple version of Linux that can be installed from a Windows machine.)

1 – install the Linux kernel. In my case, I had to download the extra ISO image from the 3.1 distribution. However, instead of burning the image, I mounted it with the command

mount -o loop -t iso9660 file.iso /some/dir

and extracted the tgz file that contains the kernel. This step is important because Topologilinux is patched to use the new NTFS driver (which usually is a pain to setup).

2 – apply the bootsplash patch for Linux, that you can find in the bootsplash.org page. I used the patch for version 2.4.20 of the Linux kernel.

3 – make menuconfig, and in “Console drivers”, framebuffer support, select the following options: “VESA framebuffer”, ”show bootsplash (NEW)”. For me, this was all that was required, but some other people may need initrd, loop device, and ram disk support depending on the configuration of the system.

4 – compile the kernel (make dep; make bzImage). I didn’t compile the modules again, since I didn’t change any of them.

5- Create a modified initrd file. I copied the one from the Windows partition (in my case it is in fact initrd.gz, but the process is the same). Then use the splash utility to write the image to the end of the initrd file. This process is explained in the README.bootsplash file from bootsplash.org

6 – Change the grub menu, to add new kernel options. I used the additional parameters vga=0×317 video=vesa:1024×768@72 splash=silent. They worked fine in my case.

7 – To animate the progress bar, we have to add some commands to the init scripts. There are a lot of scripts comming with bootsplash, but I found a simple way of doing it with:

echo show 65500 < /proc/splash

The number goes from 0 to 65500 (max), and sets the level in the progress bar.

8 – I also created a different configuration for the reboot screen. I just added this command to the rc.6 init script:

splash -s -u 0 /etc/bootsplash/themes/Linux/config/shutdown-bootsplash-1024x768.cfg,

where shutdown-bootsplash-1024×768.cfg, is the name of the new configuration. For this step, I also had to change the silent jpg image in the images directory.

9 – Final touch: with gimp, I changed the init and shutdown images  according to my preferences. You can, for example, add a welcome message.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

Installing CoLinux in my machine

I finally took the time and installed coLinux in my machine, with file access to the Windows files.

For the people interested on this, here are the steps I followed:

  1. Download coLinux from sourceforge
  2. Make the main network interface (the one that connects to the Internet) shared. This will change the address of the image to 192.168.0.1. Change this to the correct address you want to use.
  3. Change the resulting TAP adapter to the address 192.168.0.1.
  4. Start coLinux, and set DNS correctly, in the file /etc/resolv.conf. eg:
    nameserver IPaddress
  5. Share a directory from the windows machine
  6. Mount that directory using the following commands:

smbmount //192.168.0.1/share /home/oliveira/xp -o username=domain\\user,uid=1000,fmask=1777,
rw,credentials=/etc/samba/credentials

7. Create the credential file which is just

username= xxx

password= xxx
8. If you use Debian, add the mount command to rc.d by adding the commands to

/etc/init.d/local

then, setting it to executable, and calling:

update-rc.d local defaults 80

I also updated my Linux image to Topologilinux, which is nothing else than Slackware 9. This is probably the best Linux distro that I’ve ever run!

If you want to do this, just follow the howto in the Wiki page in Colinux. The information there is very detailed.

That is all folks!

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter