wxOCaml, camlidl and Class Modules

Authors: Çagdas Bozman
Date: 2013-04-02
Category: Tooling



Last week, I was bored doing some paperwork, so I decided to hack a little to relieve my mind...

Looking for a GUI Framework for OCaml Beginners

Some time ago, at OCamlPro, we had discussed the fact that OCaml was lacking more GUI frameworks. Lablgtk is powerful, but I don’t like it (and I expect that some other people in the OCaml community share my opinion) for several reasons:

Now, the question was, which GUI framework to support for OCaml ? A long time ago, I had heard that wxWidgets (formerly wxWindows) had contributed to the popularity of Python at some point, and I remembered that there was a binding called wxCaml that had been started by SooHyoung Oh a few years ago. I had managed to compile it a two years ago, but not to make the examples work, so I decided it was worth another try.

From wxEiffel to wxCaml, through wxHaskell

wxCaml is based on wxHaskell, itself based on wxEiffel, a binding for wxWidgets done for the Eiffel programming language. Since wxWidgets is written in C++, and most high-level programming languages only support bindings to C functions, the wxEiffel developers wrote a first binding from C++ to C, called the ELJ library: for each class wxCLASS of wxWidgets, and for each method Method of that class, they wrote a function wxCLASS_Method, that takes the object as first argument, the other arguments of the method, and then call the method on the first argument, with the other arguments. For example, the code for the wxWindow looks a lot like that:

EWXWEXPORT(bool,wxWindow_Close)(wxWindow* self,bool _force)
{
    return self->Close(_force);
}

From what I understood, they stopped maintaining this library, so the wxHaskell developers took the code and maintained it for wxHaskell. In wxHaskell, a few include files describe all these C functions. Then, they use a program ‘wxc’ that generates Haskell stubs for all these functions, in a class hierarchy.

In the first version of wxCaml, camlidl was used to generate OCaml stubs from these header files. The header files had to be modified a little, for two reasons:

Since the version of wxCaml I downloaded used outdated versions of wxWidgets (wxWindows 2.4.2 when current version is wxWidgets 2.9) and wxHaskell (0.7 when current version is 0.11), I decided to upgrade wxCaml to the current versions. I copied the ELJ library and the header files from the GitHub repository of wxHaskell. Unfortunately, the corresponding wxWidgets version is 2.9.4, which is not yet officially distributed by mainstream Linux distributions, so I had to compile it also.

After the painful work of fixing the new header files for camlidl, I was able to run a few examples of wxCaml. But I was not completely satisfied with it:

wxCamlidl, modifying camlidl for wxOCaml

So, I decided to write a new typed interface, where each class would be translated into an abstract type, a module containing its methods as functions, and a few cast functions, from children to ancestors.

I wrote just what was needed to make two simple examples work (hello_world and two_panels, from wxWidgets tutorials), I was happy with the result:

wxOCaml-screenshot-hello.png

wxOCaml-screenshot-panels.png

But writting by hand the complete interface for all classes and methods would not be possible, so I decided it was time to write a tool for that task.

My first attempt at automating the generation of the typed interface failed because the basic tool I wrote didn’t have enough information to correctly do the task: sometimes, methods would be inherited by a class from an ancestor, without noticing that the descendant had its own implementation of the method. Moreover, I didn’t like the fact that camlidl would write all the stubs into a single file, and my tool into another file, making any small wxOCaml application links itself with these two huge modules and the complete ELJ library, even if it would use only a few of its classes.

As a consequence, I decided that the best spot to generate a modular and typed interface would be camlidl itself. I got a copy of its sources, and created a new module in it, using the symbolic IDL representation to generate the typed version, instead of the untyped version. The module would compute the hierarchy of classes, to be able to propagate statically methods from ancestors to children, and to generate cast functions from children to ancestors.

A first generated module, called WxClasses defines all the wxWidgets classes as abstract types:

type eLJDragDataObject  
and eLJMessageParameters  
…  
and wxDocument  
and wxFrameLayout  
and wxMenu  
and wxMenuBar  
and wxProcess  
and …  

Types started by “eLJ…” are classes defined in the ELJ library for wxWidgets classes where methods have to be defined to override basic behaviors.

Classes as modules

For each wxWidget class, a specific module is created with:

For example, for the WxFrame module, the tool generates this signature:

open WxClasses

external wxnew : (* constructor *)  
wxWindow -> int -> wxString -> int -> int -> int -> int -> int  
-> wxFrame  
= “camlidl_wxc_idl_wxFrame_Create_bytecode”  
… (* direct methods *)  
external setToolBar : wxFrame -> wxToolBar -> unit  
= “camlidl_wxc_idl_wxFrame_SetToolBar”  
… (* inherited methods *)  
external setToolTip : wxFrame -> wxString -> unit  
= “camlidl_wxc_idl_wxWindow_SetToolTip”  
…  
(* string wrappers *)  
val wxnew : wxWindow -> int -> string -> int -> int -> int -> int -> int -> wxFr  
ame  
val setToolTip : wxFrame -> string -> unit  
…  
val ptrNULL : wxFrame (* a NULL pointer *)  
…  
external wxWindow : wxFrame -> wxWindow = “%identity” (* cast function *)  
…  

In this example, we can see that:

All functions that could not be put in such files are gathered in a module WxMisc. Finally, the tool also generates a module WxWidgets containing a copy of all constructors with simpler names:

…  
val wxFrame : wxWindow -> int -> string -> int -> int -> int -> int -> int -> wxFrame  
val wxFontMapper : unit -> wxFontMapper  
…  

and functions to ignore the results of functions:

…  
external ignore_wxFontMapper : wxFontMapper -> unit = “%ignore”  
external ignore_wxFrame : wxFrame -> unit = “%ignore”  
…  

We expect wxOCaml applications to just start with “open WxWidgets” to get access to these constructors, to use functions prefixed by the class module names, and to use constants from the Wxdefs module.

Here is how the minimal application looks like:

open WxWidgets  
let _ =  
let onInit event =  
let frame_id = wxID () in  
let quit_id = wxID() in  
let about_id = wxID() in

(* Create toplevel frame *)  
let frame = wxFrame WxWindow.ptrNULL frame_id “Hello World”  
50 50 450 350 Wxdefs.wxDEFAULT_FRAME_STYLE in  
WxFrame.setStatusText frame “Welcome to wxWidgets!” 0;

(* Create a menu *)  
let menuFile = wxMenu “” 0 in  
WxMenu.append menuFile about_id “About” “About the application” false;  
WxMenu.appendSeparator menuFile;  
WxMenu.append menuFile quit_id “Exit” “Exit from the application” false;

(* Add the menu to the frame menubar *)  
let menuBar = wxMenuBar 0 in  
ignore_int (WxMenuBar.append menuBar menuFile “&File”);  
WxFrame.setMenuBar frame menuBar;  
ignore_wxStatusBar (WxFrame.createStatusBar frame 1 0);

(* Handler for QUIT menu *)  
WxFrame.connect frame quit_id Wxdefs.wxEVT_COMMAND_MENU_SELECTED  
(fun _ -> exit 0);

(* Handler for ABOUT menu *)  
WxFrame.connect frame about_id Wxdefs.wxEVT_COMMAND_MENU_SELECTED  
(fun _ ->  
ignore_int (  
WxMisc.wxcMessageBox “wxWidgets Hello World example.”  
“About Hello World”  
(Wxdefs.wxOK lor Wxdefs.wxICON_INFORMATION)  
(WxFrame.wxWindow frame)  
Wxdefs.wxDefaultCoord  
Wxdefs.wxDefaultCoord  
)  
);

(* Display the frame *)  
ignore_bool ( WxFrame.show frame );  
ELJApp.setTopWindow (WxFrame.wxWindow frame)  
in  
WxMain.main onInit (* Main WxWidget loop starting the app *)  

Testers welcome

The current code can be downloaded from our repository on GitHub. It should work with wxWidgets 2.9.4, and the latest version of ocp-build (1.99-beta5).

Of course, as I never wrote an application with wxWidgets before, I could only write a few examples, so I would really appreciate any feedback given by beta testers, especially as there might be errors in the translation to IDL, that make important functions impossible to use, that I cannot detect by myself.

I am also particularly interested by feedback on the use of modules for classes, to see if the corresponding style is usable. Our current feeling is that it is more verbose than a purely object-oriented style, but it is simpler for beginners, and improves the readability of code.

Finally, it was a short two-day hack, so it is far from finished. Especially, after hacking wxCamlidl, and looking at the code of the ELJ library, I had the feeling that we could go directly from the C++ header files, or something equivalent, to produce not only the OCaml stubs and the typed interface, but also the C++ to C bindings, and get rid completely of the ELJ library.


About OCamlPro:

OCamlPro is a R&D lab founded in 2011, with the mission to help industrial users benefit from state-of-the art programming languages like OCaml and Rust.

We design, create and implement custom ad-hoc software for our clients. We also have a long experience in developing and maintaining open-source tooling for OCaml, such as Opam, TryOCaml, ocp-indent, ocp-index and ocp-browser, and we contribute to the core-development of OCaml, notably with our work on the Flambda optimizer branch.

Another area of expertise is that of Formal Methods, with tools such as our SMT Solver Alt-Ergo (check our Alt-Ergo Users'). We also provide vocational trainings in OCaml and Rust, and we can build courses on formal methods on-demand. Do not hesitate to reach out by email: contact@ocamlpro.com.