Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a string to a C library from OCaml using Ctypes and Foreign

Tags:

ctypes

ocaml

pcap

I'm really new to OCaml, and wanted to try and do some work with pcap as a way of getting started, only, there doesn't seem to be a maintained library for it. After looking at the awesome Real World OCaml book, I figured I'd give writing a binding a go.

Here's the (poor) code snippet:

open Ctypes
open Foreign
open PosixTypes
let char_ptr = "                 "

let pcap_lookupdev = foreign "pcap_lookupdev" (string @-> returning string_opt)

let result = pcap_lookupdev char_ptr

let test2 =
    match result with
    | None -> char_ptr
    | Some str -> str
 ;;

print_string test2;;

The pcap_lookupdev function returns either a string containing the device name or a null pointer. That bit seems to work fine (although I know my code is hardly idiomatic).

When writing this in C, you need to provide a character array to hold any error messages. So if a null pointer is returned, you should fail with the reason held in this character array. This character array should be "PCAP_ERRBUF_SIZE" long. However I can't figure out two things:

  1. How to pull that constant size from the C library and create a string that size
  2. Pass the string correctly to the function so that it gets correctly populated with the error message

Any help most gratefully appreciated :)

like image 219
PeterM Avatar asked Feb 15 '23 06:02

PeterM


1 Answers

For 1) the easiest way for getting #ifdef'd symbols into OCaml is to write a C program that outputs a seperate module with the value of these symbol. You then just use this module in your bindings when you need the symbols. You can find an example of this approach here.

For 2) I'd say ctypes's string is a little bit deceptive as it doesn't seem to act in a bidirectional fashion, that is you should only use it for const char * or return types. In this case you need to use arrays of character and then translate it to a string (this char_array_as_string function should be added to ctypes I think). Here's the full example, note that in future versions of ctypes the Array module will change its name to CArray:

(* Compile with: ocamlfind ocamlopt -package ctypes.foreign -linkpkg -cclib -lpcap \
                                    -o test.native test.ml *)

open Ctypes;;
open Foreign;;

module Pcap : sig
  val lookupdev : unit -> [ `Ok of string | `Error of string ]
end = struct

  let errbuf_size = 256 (* N.B. This should not be hardcoded, see 1) above *)

  let char_array_as_string a =
    let len = Array.length a in 
    let b = Buffer.create len in 
    try 
      for i = 0 to len -1 do 
        let c = Array.get a i in 
        if c = '\x00' then raise Exit else Buffer.add_char b c
      done;
      Buffer.contents b 
    with Exit -> Buffer.contents b

  let lookupdev = 
    foreign "pcap_lookupdev" (ptr char @-> returning string_opt)

  let lookupdev () =
    let err = Array.make char ~initial:'\x00' errbuf_size in
    match lookupdev (Array.start err) with 
    | None -> `Error (char_array_as_string err)
    | Some dev -> `Ok dev

end

let test () = match Pcap.lookupdev () with 
| `Ok dev -> Printf.printf "dev: %s\n" dev
| `Error err -> Printf.printf "error: %s\n" err

let () = test ()
like image 179
Daniel Bünzli Avatar answered Apr 26 '23 01:04

Daniel Bünzli