This post is intended to give a step-by-step example of linking D with a C library. If you want to mimic my windows development environment, to start with, you'll need to download Tango and DigitalMars C. I used the below versions, but you may also want to check for the latest. I'm also toying with the idea of creating a Rack-style D http server, and coming up with a D web framework. Download dm850c.zip from here, and tango-0.99.6-bin-win32-dmd.1.029.zip from here. Amusingly, i'd like to highlight that here we're combining Ragel, which creates the Mongrel HTTP parser C code, which was intended for a Ruby web server, finally linking it with D. Download this article's code here Make sure you read this first, too. The (awesome) Mongrel HTTP parser I grabbed parser.c and parser.h from Ebb, as i had problems with the Mongrel one. You can get it here. Place the two files into a folder and run the following command to compile them with this command: \dm\bin\dmc -c parser.c -I. This will produce the parser.obj if you've installed dmc (Digitalmars' C) correctly. Bindings between C and D To make bindings to D, I basically placed the contents of the parser.h into an 'extern(C)' block and deleted all the 'const' references, like so:
extern(C)
{
  enum { MONGREL_CONTENT_LENGTH
       , MONGREL_CONTENT_TYPE
       , MONGREL_FRAGMENT
       , MONGREL_HTTP_VERSION
       , MONGREL_QUERY_STRING
       , MONGREL_REQUEST_PATH
       , MONGREL_REQUEST_METHOD
       , MONGREL_REQUEST_URI
       };

  typedef void (*field_cb)(void *data, char *field, size_t flen, char *value, size_t vlen);
  typedef void (*element_cb)(void *data, int type, char *at, size_t length);

  struct http_parser { 
    int cs;
    int overflow_error;
    size_t body_start;
    size_t content_length;
    size_t nread;
    size_t mark;
    size_t field_start;
    size_t field_len;
    size_t query_start;

    void *data;

    field_cb http_field;
    element_cb on_element;
  };

  void http_parser_init(http_parser *parser);
  int http_parser_finish(http_parser *parser); 
  size_t http_parser_execute(http_parser *parser, char *data, size_t len, size_t off);
  int http_parser_has_error(http_parser *parser);
  int http_parser_is_finished(http_parser *parser);
}
Since mongrel uses a couple of call-back functions, they must also be within 'extern(C)' blocks:
extern (C) void http_field_cb(void *data, char *field, size_t flen, char *value, size_t vlen)
{ ...code here... }

extern (C) void on_element(void *data, int type, char *at, size_t length)
{ ...code here... }
The D Server I'm using Tango in the simplest possible way to listen to port 80, feed the input to Mongrel, and return an html page. Here's the entire serve.d:
import tango.io.Stdout;
import tango.stdc.stringz;
import tango.net.ServerSocket, tango.net.SocketConduit;
         
// Stuff for the mongrel parser
extern(C)
{

  enum { MONGREL_CONTENT_LENGTH
       , MONGREL_CONTENT_TYPE
       , MONGREL_FRAGMENT
       , MONGREL_HTTP_VERSION
       , MONGREL_QUERY_STRING
       , MONGREL_REQUEST_PATH
       , MONGREL_REQUEST_METHOD
       , MONGREL_REQUEST_URI
       };

  typedef void (*field_cb)(void *data, char *field, size_t flen, char *value, size_t vlen);
  typedef void (*element_cb)(void *data, int type, char *at, size_t length);

  struct http_parser { 
    int cs;
    int overflow_error;
    size_t body_start;
    size_t content_length;
    size_t nread;
    size_t mark;
    size_t field_start;
    size_t field_len;
    size_t query_start;

    void *data;

    field_cb http_field;
    element_cb on_element;
  };

  void http_parser_init(http_parser *parser);
  int http_parser_finish(http_parser *parser); 
  size_t http_parser_execute(http_parser *parser, char *data, size_t len, size_t off);
  int http_parser_has_error(http_parser *parser);
  int http_parser_is_finished(http_parser *parser);
}
// End of stuff for the mongrel parser

// My callback functions

extern (C) void http_field_cb(void *data, char *field, size_t flen, char *value, size_t vlen)
{
  char[]* display = cast(char[]*)data;
  *display ~= "<i>http_field_cb</i> <b>";
  *display ~= field[0..flen];
  *display ~= "</b> = ";
  *display ~= value[0..vlen];
  *display ~= "<br>";
}

extern (C) void on_element(void *data, int type, char *at, size_t length)
{
  char[]* display = cast(char[]*)data;

  char[] line;
  line ~= "<i>on_element</i> <b>";
  if (type==MONGREL_CONTENT_LENGTH) line ~= "MONGREL_CONTENT_LENGTH";
  if (type==MONGREL_CONTENT_TYPE) line ~= "MONGREL_CONTENT_TYPE";
  if (type==MONGREL_FRAGMENT) line ~= "MONGREL_FRAGMENT";
  if (type==MONGREL_HTTP_VERSION) line ~= "MONGREL_HTTP_VERSION";
  if (type==MONGREL_QUERY_STRING) line ~= "MONGREL_QUERY_STRING";
  if (type==MONGREL_REQUEST_PATH) line ~= "MONGREL_REQUEST_PATH";
  if (type==MONGREL_REQUEST_METHOD) line ~= "MONGREL_REQUEST_METHOD";
  if (type==MONGREL_REQUEST_URI) line ~= "MONGREL_REQUEST_URI";
  line ~= "</b> = " ~ at[0..length] ~ "<br>";
  *display ~= line;
}

void main()
{
  // Set up the server
  Stdout("Now open your browser to http://localhost/").newline();
  Stdout("Ctrl-Break or Ctrl-C to quit").newline();
  auto server = new ServerSocket (new InternetAddress(80));

  while(1)
  {
    // wait for requests
    SocketConduit request = server.accept;

    // wait for the 'http get ...'
    char[1024] response;
    uint len = request.read (response);
    Stdout(response[0..len]).newline; // you'll want to comment this out if you run apachebench against this...

    // parse it
    char[] display;
    display = "";
    http_parser parser;
    http_parser_init(&parser);
    parser.data = &display; // data that is sent to the callback function
    parser.http_field = &http_field_cb;
    parser.on_element = &on_element;
    http_parser_execute( &parser
                       , cast(char*)response
                       , len
                       , 0
                       );
    display ~= http_parser_has_error(&parser) ? "-mongrel error;" : "-no mongrel error;";
    display ~= http_parser_is_finished(&parser) ? "mongrel finished-" : "mongrel not finished-";;
    
    // send HTML
    request.output.write("HTTP/1.1 200 OK

<html>
<style>
  body {font-family:trebuchet ms;background:#524D4A;color:#fff;}
  .comment {font-size:80%;margin:0;color:#cccccc;}
  form {background:#6B7552; margin:20px 0 20px 0; padding:5px;}
</style>
<body>
  <h1>D + Mongrel's HTTP parser</h1>

  <form method='get'>
    <h2>GET form</h2>
    <input name='blah' value='test value'>
    <input type='submit'>
  </form>
  
  <form method='post'>
    <h2>POST form</h2>
    <input name='blah' value='test value'>
    <input type='submit'>
  </form>  

  <form method='post'>
    <h2>POST form with file</h2>
    <input type='file' name='blah'>
    <input type='submit'>
  </form>
  
  <form>
    <h2>Mongrel Parser Results:</h2>
    " ~ display ~ " 
  </form>
  </body>
</html>");

    request.close();
  }
}
Compiling it all together To compile it together, use the following command, which will produce serve.exe: \dmd\bin\dmd serve.d parser.obj Then open your web browser to http://localhost/ and you should see this: [[posterous-content:JylngbAclaJhIuBBEHCz]] Voila! That's all until next time.

Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.

Chris Hulbert

(Comp Sci, Hons - UTS)

Software Developer (Freelancer / Contractor) in Australia.

I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Coles, Woolworths, Trust Bank, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!

Get in touch:
[email protected]
github.com/chrishulbert
linkedin



 Subscribe via RSS