This document discusses the theory and practice behind writing a wrapper around a typical object-oriented Perl library. We use Pl_LWP_UserAgent as an example. (This is the high-level wrapper around the LWP::UserAgent library).
Don't worry - writing wrappers is really not very hard at all. I hope that you, the reader, will write some wrappers around your favorite Perl libraries and contribute them back to perl4caml development.
I'm going to use LWP::UserAgent as my example throughout this document. Substitute that for whatever library you want to wrap up and call from OCaml. First of all make sure you have the library installed and working under Perl, and make sure you have the manual page for that library in front of you:
perldoc LWP::UserAgent
or follow this link.
The low-level Perl module offers two useful functions and a useful datatype which we'll be using extensively. The useful functions are:
| Function name | Perl equivalent | Description | 
|---|---|---|
| call_class_method | $obj = LWP::UserAgent->new (args...) | Calls a static method or constructor on a class. | 
| call_method | $obj->some_method (args...) | Calls an instance method on an object. | 
      The useful datatype is called the Perl.sv (an
      abstract type), which represents a scalar value in Perl (anything
      you would normally write in Perl with a $, including
      numbers, strings, references and blessed objects). To find out
      more about "SVs" see the perlguts(3) man page.
    
      To see how these three things interact, let's create an
      LWP::UserAgent object and call a method on it:
    
# #load "perl4caml.cma";; # open Perl;; # let sv = call_class_method "LWP::UserAgent" "new" [];; val sv : Perl.sv = <abstr> # let agent = call_method sv "agent" [];; val agent : Perl.sv = <abstr> # string_of_sv agent;; - : string = "libwww-perl/5.69"
      Note how the variable sv contains the actual Perl
      object (an instance of LWP::UserAgent).  To be
      quite clear, this is the equivalent of the following Perl code:
    
$sv = LWP::UserAgent->new (); $agent = $sv->agent (); print $agent;
      You could actually just continue to use the low-level interface
      to access Perl objects directly, but there are three problems with
      this: firstly it's cumbersome because you have to continually
      convert to and from SVs; secondly it's not type safe
      (eg. calling string_of_sv might fail if the SV
      isn't actually a string); thirdly there are more pleasant ways
      to present this interface.
    
Writing a high-level wrapper around these low-level operations is what is described in the rest of this document ...
      Our general plan, therefore, will be to create an OCaml class
      corresponding to LWP::UserAgent, which hides the
      implementation (the sv, the calls to
      call_method, and the conversion of arguments
      to and from SVs). We will also need to write one or more constructor
      function.
    
We will write at least one method for every method exported by the Perl interface. Sometimes we'll write two methods for each Perl method, as in the case where a Perl method is a "getter/setter":
$ua->agent([$product_id]) Get/set the product token that is used to identify the user agent on the network. The agent value is sent as the "User-Agent" header in the requests.
becomes two methods in the OCaml version:
class lwp_useragent sv = object (self) (* ... *) method agent : string method set_agent : string -> unit end
We will also write at least one function for every constructor or static function exported from Perl.
      The OCaml object itself contains the sv which
      corresponds to the Perl SV (ie. the actual Perl object).
    
Here is the shape of our class:
(** Wrapper around Perl [LWP::UserAgent] class. * * Copyright (C) 20xx your_organisation * * $ Id $ *) open Perl let _ = eval "use LWP::UserAgent" class lwp_useragent sv = object (self) The methods will go here ... end (* The "new" constructor. Note that "new" is a reserved word in OCaml. *) let new_ ... = ... let sv = call_class_method "LWP::UserAgent" "new" [args ...] in new lwp_useragent sv Any other static functions will go here ...
Notice a few things here:
Perl..
      eval "use LWP::UserAgent" when the module
	is loaded. This is required by Perl.
      sv (scalar value representing the actual
	object) is passed as a parameter to the class.
    Of all types of methods, getters and setters are the easiest to write. First of all, check the manual page to find out what type the slot is. You'll need to write one get method and one set method. (Rarely you'll find getters and setters which are quasi-polymorphic, for instance they can take a string or an arrayref. You'll need to think more deeply about these, because they require one set method for each type, and the get method can be complicated).
Here's our getter and setter for the agent slot, described above. The agent is a string:
  method agent =
    string_of_sv (call_method sv "agent" [])
  method set_agent v =
    call_method_void sv "agent" [sv_of_string v]
    Note:
agent (not
	"get_agent"). This is the standard for OCaml code.
      string_of_sv and sv_of_string
	to convert to and from SVs. This will ensure that the
	class interface will have the correct type (string), and
	thus be type safe as far as the calling code is concerned,
	and also means the caller doesn't need to worry about
	SVs.
      call_method_void which
	we haven't seen before. This is exactly the same as
	call_method except that the method is called
	in a "void context" - in other words, any return value is
	thrown away. This is slightly more efficient than ignoring
	the return value.
    Here's another example, with a boolean slot:
  method parse_head =
    bool_of_sv (call_method sv "parse_head" [])
  method set_parse_head v =
    call_method_void sv "parse_head" [sv_of_bool v]
    
      Other methods are perhaps simpler to wrap than getters and
      setters. LWP::UserAgent contains an interesting
      method called request (which actually runs the
      request).
    
      What's particularly interesting about this method are the
      parameter and return value. It takes an HTTP::Request
      object and returns an HTTP::Response.
    
      I have already wrapped HTTP::Request and
      HTTP::Response as modules
      Pl_HTTP_Request and
      Pl_HTTP_Response
      respectively. You should go and look at the code in those
      modules now.
    
      If request requires a parameter, what should that
      parameter be? Naturally it should be the SV corresponding to
      the HTTP::Request object. To get this, I provided
      an #sv method on the http_request class.
    
      And what will request return? Naturally it will
      return an SV which corresponds to the (newly created inside
      Perl) HTTP::Response object. We need to wrap
      this up and create a new OCaml http_response object,
      containing that SV.
    
This is what the final method looks like:
  method request (request : http_request) =
    let sv = call_method sv "request" [request#sv] in
    new http_response sv
    It's actually not so complicated.
      Constructors are fairly simple, although the new_
      function inside Pl_LWP_UserAgent is complicated
      by the many optional arguments which LWP::UserAgent->new
      can take.
    
Here is the guts, omitting all but one of the optional args:
let new_ ?agent (* ... *) () = let args = ref [] in let may f = function None -> () | Some v -> f v in may (fun v -> args := sv_of_string "agent" :: sv_of_string v :: !args) agent; (* ... *) let sv = call_class_method "LWP::UserAgent" "new" !args in new lwp_useragent sv
      It works simply enough, first building up a list of svs
      corresponding to the arguments, then calling
      call_class_method to create the Perl object, then
      returning a constructed OCaml lwp_useragent object
      containing that sv.
    
If you write a wrapper for a Perl class, particularly one from CPAN, I urge you to contribute it back to the perl4caml development effort. Your contribution enriches the project as a whole, and makes OCaml more useful too.