digitalmars.D - C to D bindings: how much do you D-ify the code?
- Lionello Lunesu (21/21) Oct 25 2013 There's a lot of expressiveness that can be added to D bindings, when
- John Colvin (7/30) Oct 25 2013 I would go for a two stage approach:
- John Colvin (4/43) Oct 25 2013 As an aside, I would suggest that function overloads that take
- Dicebot (4/7) Oct 25 2013 I disagree. You can always add those wrappers in stage-2 module
- Lionello Lunesu (4/46) Oct 26 2013 Thanks for the feedback, but your last reasoning could be applied to the...
- Dicebot (10/10) Oct 25 2013 I think best approach is to have 2-step bindings. First step is
- Lionello Lunesu (3/11) Oct 26 2013 That's a good point. A "diff" is much more manageable with a 1:1 convers...
- Mike Parker (14/17) Oct 25 2013 IMO, a binding to an existing library should never add anything extra if...
- Paulo Pinto (7/28) Oct 25 2013 Speaking from my experience in other languages, try to map 1:1 to the C
- Jakob Ovrum (12/17) Oct 25 2013 I also think keeping the C bindings as faithful as possible is
- Jacob Carlborg (10/12) Oct 26 2013 I agree with what others have said. I can add that I sometimes use three...
- Lionello Lunesu (4/6) Oct 29 2013 Hadn't even mentioned that one, but yeah, that's one of my favorite
- Benjamin Thaut (5/26) Oct 26 2013 I actually do number 1 whenever creating C bindings. Because its not
- Mike Parker (4/11) Oct 26 2013 alias FOO = int;
- Michel Fortin (11/24) Oct 26 2013 But then you lose the type-safety of an enum. Why not this:
- Mike Parker (2/22) Oct 26 2013 When you do it by hand, as I do, this approach can make you cry.
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
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
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: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.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.
Oct 25 2013
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
On 10/25/13, 18:22, John Colvin wrote:On Friday, 25 October 2013 at 13:26:33 UTC, John Colvin wrote:Thanks for the feedback, but your last reasoning could be applied to the other points just as well :) L.On Friday, 25 October 2013 at 13:10:05 UTC, Lionello Lunesu 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.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.
Oct 26 2013
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
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
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
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
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
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
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 stringsHadn't even mentioned that one, but yeah, that's one of my favorite overloads! L.
Oct 29 2013
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
On 10/26/2013 9:09 PM, Benjamin Thaut wrote:Am 25.10.2013 15:10, schrieb Lionello Lunesu:alias FOO = int; enum { FOO_A, FOO_B }; Otherwise, it doesn't match the C API.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
Oct 26 2013
On 2013-10-26 14:52:42 +0000, Mike Parker <aldacron gmail.com> said:On 10/26/2013 9:09 PM, Benjamin Thaut wrote: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.caAm 25.10.2013 15:10, schrieb Lionello Lunesu:alias FOO = int; enum { FOO_A, FOO_B }; Otherwise, it doesn't match the C API.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
Oct 26 2013
On 10/27/2013 12:11 AM, Michel Fortin wrote:On 2013-10-26 14:52:42 +0000, Mike Parker <aldacron gmail.com> said:When you do it by hand, as I do, this approach can make you cry.On 10/26/2013 9:09 PM, Benjamin Thaut wrote: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.Am 25.10.2013 15:10, schrieb Lionello Lunesu:alias FOO = int; enum { FOO_A, FOO_B }; Otherwise, it doesn't match the C API.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
Oct 26 2013