www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Interesting C header translation problem

reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
Came across this:

    #ifdef __GNUC__
        #define PACKED __attribute__((packed))
    #else
        #define PACKED
    #endif

    typedef enum {
        // Lots o' stuff
    } PACKED my_enum_t;

    typedef struct {
        // Lots o' stuff
        const void *ptr;
    } PACKED my_struct_t;

There are a handful of interesting (read: annoying ;) ) problems that
are (*ahem*) packed into that:

1. Totally different ABI based on whether or not the **C** side was
compiled with a GNU compiler.

2. How to do conditional "align(1)" with minimal ugliness.

3. WTF does it even mean to have a packed enum?

4. Having a pointer at the end of a packed struct is asking for trouble
according to D's docs ("The garbage collector assumes that pointers and
references to gc allocated objects will be on size_t byte boundaries.
If they are not, undefined behavior will result.") Probably nothing
that can be done about this one, though, other than just expect the
user to be careful.

5. From what I can gather from GCC and DMD docs, it sounds like
__attribute__((packed)) means:

    align(1) struct my_struct_t{
        align(1):
        // blah, blah, members
    }

BUT, are there any other...ummm..."fun" surprises that could pop up and
need to be taken into account?

6. The easy one: I'm pretty sure "const void *ptr" translates to
"const(void)* ptr", right?

For the moment, my "solution" is to just include a note saying "Don't
compile the *.c files with __GNUC__ defined" ;) But what would be the
best realistic way to handle these issues? Any established practices?
Oct 30 2012
parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <alex lycus.org> writes:
On 30-10-2012 10:41, Nick Sabalausky wrote:
 Came across this:

      #ifdef __GNUC__
          #define PACKED __attribute__((packed))
      #else
          #define PACKED
      #endif

      typedef enum {
          // Lots o' stuff
      } PACKED my_enum_t;

      typedef struct {
          // Lots o' stuff
          const void *ptr;
      } PACKED my_struct_t;

 There are a handful of interesting (read: annoying ;) ) problems that
 are (*ahem*) packed into that:

 1. Totally different ABI based on whether or not the **C** side was
 compiled with a GNU compiler.
Absolutely fucking horrible API design. These matters are, if anything, related to the system ABI, not the compiler ABI. Making the layout depend on the compiler ABI makes any sort of sane interoperability with the API from non-C languages virtually impossible to do reliably.
 2. How to do conditional "align(1)" with minimal ugliness.
I can't think of anything that doesn't involve repetitive code or mixins. But worse than that, it's not like you can detect what compiler was used to build some random library.....
 3. WTF does it even mean to have a packed enum?
Nothing.
 4. Having a pointer at the end of a packed struct is asking for trouble
 according to D's docs ("The garbage collector assumes that pointers and
 references to gc allocated objects will be on size_t byte boundaries.
 If they are not, undefined behavior will result.") Probably nothing
 that can be done about this one, though, other than just expect the
 user to be careful.
More generally, a pointer that is not aligned on a 4-byte (on 32-bit systems) or 8-byte (on 64-bit systems) boundary will not be picked up by the GC. This can happen in many situations, e.g.: struct S { align (1): byte b; int* p; byte b; byte b; int* p; } And many other possible layouts. The best way to deal with terrible APIs in this regard is to use GC.addRoot() to keep such pointers alive. This does of course mean that you have to use GC.removeRoot() to make it collectable again...
 5. From what I can gather from GCC and DMD docs, it sounds like
 __attribute__((packed)) means:

      align(1) struct my_struct_t{
          align(1):
          // blah, blah, members
      }

 BUT, are there any other...ummm..."fun" surprises that could pop up and
 need to be taken into account?
It only implies the inner align, not the outer one. GCC has an aligned attribute on variables that can be used to get the effect of the outer one. But no, no real surprises or gotchas here.
 6. The easy one: I'm pretty sure "const void *ptr" translates to
 "const(void)* ptr", right?
Yep.
 For the moment, my "solution" is to just include a note saying "Don't
 compile the *.c files with __GNUC__ defined" ;) But what would be the
 best realistic way to handle these issues? Any established practices?
Honestly, I don't know. I would go stab the original API designers in the face and tell them to clean up their mess. -- Alex Rønne Petersen alex lycus.org http://lycus.org
Oct 30 2012
next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Tue, 30 Oct 2012 11:05:28 +0100
Alex R=F8nne Petersen <alex lycus.org> wrote:

 On 30-10-2012 10:41, Nick Sabalausky wrote:
 Came across this:

      #ifdef __GNUC__
          #define PACKED __attribute__((packed))
      #else
          #define PACKED
      #endif

      typedef enum {
          // Lots o' stuff
      } PACKED my_enum_t;

      typedef struct {
          // Lots o' stuff
          const void *ptr;
      } PACKED my_struct_t;

 There are a handful of interesting (read: annoying ;) ) problems
 that are (*ahem*) packed into that:

 1. Totally different ABI based on whether or not the **C** side was
 compiled with a GNU compiler.
=20 Absolutely fucking horrible API design. These matters are, if anything, related to the system ABI, not the compiler ABI. Making the layout depend on the compiler ABI makes any sort of sane interoperability with the API from non-C languages virtually impossible to do reliably. =20
Yea, sounds like the best thing after all is to just say "Just don't compile the C side that way."
 2. How to do conditional "align(1)" with minimal ugliness.
=20 I can't think of anything that doesn't involve repetitive code or=20 mixins. But worse than that, it's not like you can detect what compiler was used to build some random library..... =20
It would require repeating or mixing in the entire body wouldn't it? I didn't think there was any way to mixin just an attribute or otherwise get the same effect, but thought I'd check.

I see, thanks.
=20
 For the moment, my "solution" is to just include a note saying
 "Don't compile the *.c files with __GNUC__ defined" ;) But what
 would be the best realistic way to handle these issues? Any
 established practices?
=20 Honestly, I don't know. I would go stab the original API designers in=20 the face and tell them to clean up their mess. =20
lol :) I think this one was actually designed to be usable in low-end microcontrollers (nanopb: <http://koti.kapsi.fi/jpa/nanopb/>), so that explains a lot of it. And by binding it to...well, anything but asm...I'm probably in highly-unanticipated waters anyway. I guess I could look for one that's easier to bind with and less obsessive-compulsive about small-size, but I liked that this one does zero dynamic memory allocation unless you're explicit about it.
Oct 30 2012
prev sibling parent "Petteri Aimonen" <jpa digitalmars.mail.kapsi.fi> writes:
Hi,

 Honestly, I don't know. I would go stab the original API 
 designers in the face and tell them to clean up their mess.
I'm sorry to reply to a 2-year-old topic, but as the original API designer, I simply couldn't resist :) Please do not stab me in the face. The nanopb API was never designed to be wrapped, or to be used as a dynamic library. The situation in the targeted embedded systems is that everything is compiled by the same compiler, under same settings, to a single binary. The packed attribute on enums in GCC makes them use the smallest integer type, uint8_t in this case. All this has been replaced with simpler typedef and #defines nowadays though. As for what was being tried to do in this thread, I would think a native protobuf implementation for D would be much saner than trying to wrap a C implementation. -- Petteri
Aug 08 2014