www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - C to D bindings: how much do you D-ify the code?

reply Lionello Lunesu <lionello lunesu.remove.com> writes:
There's a lot of expressiveness that can be added to D bindings, when 
compared to the C or C++ headers, for a particular library:

1. enum names vs prefixes
enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}

2. enum vs int parameters (based on a lib's documentation)
void takefoo(int) -> void takefoo(FOO)

3. "in" for input buffers (again, based on docs)
int puts(char*) -> puts(in char*)

4. "out" or "ref" for output parameters
void getdouble(double*) -> void getdouble(out double value)

5. D arrays vs length+pointer pairs
void bar(size_t len, int* ptr) -> void bar(int[] a)

6. D array wrappers
void bar(int* ptr, int size) ->
void bar(int[] a) { bar(a.ptr, cast(int)a.length; }

6. library specific sized-int typedefs to D natives
png_uint_16 -> short


These are some of the more trivial ones, but I'd like to see how other 
people go about making bindings. Do you keep as close to C as possible? 
Or do you "add value" by using more D style constructs?

L.
Oct 25 2013
next sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
On Friday, 25 October 2013 at 13:10:05 UTC, Lionello Lunesu wrote:
 There's a lot of expressiveness that can be added to D 
 bindings, when compared to the C or C++ headers, for a 
 particular library:

 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}

 2. enum vs int parameters (based on a lib's documentation)
 void takefoo(int) -> void takefoo(FOO)

 3. "in" for input buffers (again, based on docs)
 int puts(char*) -> puts(in char*)

 4. "out" or "ref" for output parameters
 void getdouble(double*) -> void getdouble(out double value)

 5. D arrays vs length+pointer pairs
 void bar(size_t len, int* ptr) -> void bar(int[] a)

 6. D array wrappers
 void bar(int* ptr, int size) ->
 void bar(int[] a) { bar(a.ptr, cast(int)a.length; }

 6. library specific sized-int typedefs to D natives
 png_uint_16 -> short


 These are some of the more trivial ones, but I'd like to see 
 how other people go about making bindings. Do you keep as close 
 to C as possible? Or do you "add value" by using more D style 
 constructs?

 L.
I would go for a two stage approach: 1) Write bindings that map as closely as possible to the C API, only adding anything extra by necessity and/or where it is transparent in correct usage. 2) Create a full on D wrapper around the bindings with the best API you can design using as much D as you like.
Oct 25 2013
parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
On Friday, 25 October 2013 at 13:26:33 UTC, John Colvin wrote:
 On Friday, 25 October 2013 at 13:10:05 UTC, Lionello Lunesu 
 wrote:
 There's a lot of expressiveness that can be added to D 
 bindings, when compared to the C or C++ headers, for a 
 particular library:

 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}

 2. enum vs int parameters (based on a lib's documentation)
 void takefoo(int) -> void takefoo(FOO)

 3. "in" for input buffers (again, based on docs)
 int puts(char*) -> puts(in char*)

 4. "out" or "ref" for output parameters
 void getdouble(double*) -> void getdouble(out double value)

 5. D arrays vs length+pointer pairs
 void bar(size_t len, int* ptr) -> void bar(int[] a)

 6. D array wrappers
 void bar(int* ptr, int size) ->
 void bar(int[] a) { bar(a.ptr, cast(int)a.length; }

 6. library specific sized-int typedefs to D natives
 png_uint_16 -> short


 These are some of the more trivial ones, but I'd like to see 
 how other people go about making bindings. Do you keep as 
 close to C as possible? Or do you "add value" by using more D 
 style constructs?

 L.
I would go for a two stage approach: 1) Write bindings that map as closely as possible to the C API, only adding anything extra by necessity and/or where it is transparent in correct usage.
As an aside, I would suggest that function overloads that take arrays instead of pointer + length is normally a harmless addition to an otherwise 1:1 set of bindings.
Oct 25 2013
next sibling parent "Dicebot" <public dicebot.lv> writes:
On Friday, 25 October 2013 at 16:22:46 UTC, John Colvin wrote:
 As an aside, I would suggest that function overloads that take 
 arrays instead of pointer + length is normally a harmless 
 addition to an otherwise 1:1 set of bindings.
I disagree. You can always add those wrappers in stage-2 module and get it inlined so there is no profit in doing it in stage-1 module. But losing ability to auto-generate stuff is huge.
Oct 25 2013
prev sibling parent Lionello Lunesu <lionello lunesu.remove.com> writes:
On 10/25/13, 18:22, John Colvin wrote:
 On Friday, 25 October 2013 at 13:26:33 UTC, John Colvin wrote:
 On Friday, 25 October 2013 at 13:10:05 UTC, Lionello Lunesu wrote:
 There's a lot of expressiveness that can be added to D bindings, when
 compared to the C or C++ headers, for a particular library:

 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}

 2. enum vs int parameters (based on a lib's documentation)
 void takefoo(int) -> void takefoo(FOO)

 3. "in" for input buffers (again, based on docs)
 int puts(char*) -> puts(in char*)

 4. "out" or "ref" for output parameters
 void getdouble(double*) -> void getdouble(out double value)

 5. D arrays vs length+pointer pairs
 void bar(size_t len, int* ptr) -> void bar(int[] a)

 6. D array wrappers
 void bar(int* ptr, int size) ->
 void bar(int[] a) { bar(a.ptr, cast(int)a.length; }

 6. library specific sized-int typedefs to D natives
 png_uint_16 -> short


 These are some of the more trivial ones, but I'd like to see how
 other people go about making bindings. Do you keep as close to C as
 possible? Or do you "add value" by using more D style constructs?

 L.
I would go for a two stage approach: 1) Write bindings that map as closely as possible to the C API, only adding anything extra by necessity and/or where it is transparent in correct usage.
As an aside, I would suggest that function overloads that take arrays instead of pointer + length is normally a harmless addition to an otherwise 1:1 set of bindings.
Thanks for the feedback, but your last reasoning could be applied to the other points just as well :) L.
Oct 26 2013
prev sibling next sibling parent reply "Dicebot" <public dicebot.lv> writes:
I think best approach is to have 2-step bindings. First step is 
pure 1-to-1 translation with no D-ification at all. Second step 
is D wrapper that expresses same functionality in more native 
syntax (probably even more type-safe). Step-2 module imports 
Step-1 module of course.

Benefit of such approach is that you can generate bindings using 
automatic tool when new header version is out without wasting 
time on adjusting those to D style again and again - you only 
need to change step-2 module if there are some breaking API 
changes.
Oct 25 2013
parent Lionello Lunesu <lionello lunesu.remove.com> writes:
On 10/25/13, 15:34, Dicebot wrote:
 I think best approach is to have 2-step bindings. First step is pure
 1-to-1 translation with no D-ification at all. Second step is D wrapper
 that expresses same functionality in more native syntax (probably even
 more type-safe). Step-2 module imports Step-1 module of course.

 Benefit of such approach is that you can generate bindings using
 automatic tool when new header version is out without wasting time on
 adjusting those to D style again and again - you only need to change
 step-2 module if there are some breaking API changes.
That's a good point. A "diff" is much more manageable with a 1:1 conversion. L.
Oct 26 2013
prev sibling next sibling parent Mike Parker <aldacron gmail.com> writes:
On 10/25/2013 10:10 PM, Lionello Lunesu wrote:
 These are some of the more trivial ones, but I'd like to see how other
 people go about making bindings. Do you keep as close to C as possible?
 Or do you "add value" by using more D style constructs?
IMO, a binding to an existing library should never add anything extra if it is intended to be released to the public. It should adhere as closely as possible to the C API. This is especially important if the C library is well-known. It would mean that existing sample code, tutorials and so on would require minimal adjustment to work in D. In that case, the two-step process recommended in other replies is the way to go. If it's for internal use only, then I think it doesn't really matter either way (with the caveat that D-ifying the binding may increase maintenance costs when the C library is updated -- but I don't think it's so high anyway). However, if it were me and I weren't binding an existing C library but, instead, developing a new one and a D binding to go along with it, I would be more inclined to D-ify the binding in that case.
Oct 25 2013
prev sibling next sibling parent Paulo Pinto <pjmlp progtools.org> writes:
Am 25.10.2013 15:10, schrieb Lionello Lunesu:
 There's a lot of expressiveness that can be added to D bindings, when
 compared to the C or C++ headers, for a particular library:

 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}

 2. enum vs int parameters (based on a lib's documentation)
 void takefoo(int) -> void takefoo(FOO)

 3. "in" for input buffers (again, based on docs)
 int puts(char*) -> puts(in char*)

 4. "out" or "ref" for output parameters
 void getdouble(double*) -> void getdouble(out double value)

 5. D arrays vs length+pointer pairs
 void bar(size_t len, int* ptr) -> void bar(int[] a)

 6. D array wrappers
 void bar(int* ptr, int size) ->
 void bar(int[] a) { bar(a.ptr, cast(int)a.length; }

 6. library specific sized-int typedefs to D natives
 png_uint_16 -> short


 These are some of the more trivial ones, but I'd like to see how other
 people go about making bindings. Do you keep as close to C as possible?
 Or do you "add value" by using more D style constructs?

 L.
Speaking from my experience in other languages, try to map 1:1 to the C API as much as possible, given the language features offered for FFI. Offer another layer that makes use of this binding API in a more canonical way for the given language. -- Paulo
Oct 25 2013
prev sibling next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Friday, 25 October 2013 at 13:10:05 UTC, Lionello Lunesu wrote:
 These are some of the more trivial ones, but I'd like to see 
 how other people go about making bindings. Do you keep as close 
 to C as possible? Or do you "add value" by using more D style 
 constructs?

 L.
I also think keeping the C bindings as faithful as possible is the best, correct even, approach. By keeping the C bindings faithful, one can defer to the existing documentation of the C library, and user code can be ported from C trivially. In a good wrapper, the C bindings are present only because the second layer depends on them and for compatibility purposes, while the second layer - the idiomatic D interface - should cover all use cases. I've found that with D's expressive modelling power and metaprogramming capabilities, the second layer does not need to compromise on performance or functionality while providing a safer, more intuitive and more convenient interface.
Oct 25 2013
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2013-10-25 15:10, Lionello Lunesu wrote:
 There's a lot of expressiveness that can be added to D bindings, when
 compared to the C or C++ headers, for a particular library:
I agree with what others have said. I can add that I sometimes use three layers. 1. The actual C bindings. Stay as close as possible to the original code 2. Slightly D-ify the C bindings. I.e. be able to pass D strings instead of C strings 3. Create wrappers. Either object oriented with classes or something in between, like structs with some internal sate and a couple of methods -- /Jacob Carlborg
Oct 26 2013
parent Lionello Lunesu <lionello lunesu.remove.com> writes:
On 10/26/13, 12:54, Jacob Carlborg wrote:
 2. Slightly D-ify the C bindings. I.e. be able to pass D strings instead
 of C strings
Hadn't even mentioned that one, but yeah, that's one of my favorite overloads! L.
Oct 29 2013
prev sibling parent reply Benjamin Thaut <code benjamin-thaut.de> writes:
Am 25.10.2013 15:10, schrieb Lionello Lunesu:
 There's a lot of expressiveness that can be added to D bindings, when
 compared to the C or C++ headers, for a particular library:

 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}

 2. enum vs int parameters (based on a lib's documentation)
 void takefoo(int) -> void takefoo(FOO)

 3. "in" for input buffers (again, based on docs)
 int puts(char*) -> puts(in char*)

 4. "out" or "ref" for output parameters
 void getdouble(double*) -> void getdouble(out double value)

 5. D arrays vs length+pointer pairs
 void bar(size_t len, int* ptr) -> void bar(int[] a)

 6. D array wrappers
 void bar(int* ptr, int size) ->
 void bar(int[] a) { bar(a.ptr, cast(int)a.length; }

 6. library specific sized-int typedefs to D natives
 png_uint_16 -> short


 These are some of the more trivial ones, but I'd like to see how other
 people go about making bindings. Do you keep as close to C as possible?
 Or do you "add value" by using more D style constructs?

 L.
I actually do number 1 whenever creating C bindings. Because its not really DRY to write: FOO.FOO_A Kind Regards Benjamin Thaut
Oct 26 2013
parent reply Mike Parker <aldacron gmail.com> writes:
On 10/26/2013 9:09 PM, Benjamin Thaut wrote:
 Am 25.10.2013 15:10, schrieb Lionello Lunesu:
 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}
I actually do number 1 whenever creating C bindings. Because its not really DRY to write: FOO.FOO_A
alias FOO = int; enum { FOO_A, FOO_B }; Otherwise, it doesn't match the C API.
Oct 26 2013
parent reply Michel Fortin <michel.fortin michelf.ca> writes:
On 2013-10-26 14:52:42 +0000, Mike Parker <aldacron gmail.com> said:

 On 10/26/2013 9:09 PM, Benjamin Thaut wrote:
 Am 25.10.2013 15:10, schrieb Lionello Lunesu:
 
 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}
I actually do number 1 whenever creating C bindings. Because its not really DRY to write: FOO.FOO_A
alias FOO = int; enum { FOO_A, FOO_B }; Otherwise, it doesn't match the C API.
But then you lose the type-safety of an enum. Why not this: enum FOO { FOO_A, FOO_B }; alias FOO.FOO_A FOO_A; alias FOO.FOO_B FOO_B; ? Overly verbose perhaps. -- Michel Fortin michel.fortin michelf.ca http://michelf.ca
Oct 26 2013
parent Mike Parker <aldacron gmail.com> writes:
On 10/27/2013 12:11 AM, Michel Fortin wrote:
 On 2013-10-26 14:52:42 +0000, Mike Parker <aldacron gmail.com> said:

 On 10/26/2013 9:09 PM, Benjamin Thaut wrote:
 Am 25.10.2013 15:10, schrieb Lionello Lunesu:
 1. enum names vs prefixes
 enum FOO { FOO_A, FOO_B }; -> enum FOO {A,B}
I actually do number 1 whenever creating C bindings. Because its not really DRY to write: FOO.FOO_A
alias FOO = int; enum { FOO_A, FOO_B }; Otherwise, it doesn't match the C API.
But then you lose the type-safety of an enum. Why not this: enum FOO { FOO_A, FOO_B }; alias FOO.FOO_A FOO_A; alias FOO.FOO_B FOO_B; ? Overly verbose perhaps.
When you do it by hand, as I do, this approach can make you cry.
Oct 26 2013