9

I want to hide the internal type from the user of a library.
Currently I have something like this:

foo.h

typedef struct public
{
    uint16 a;
    //...
    unsigned char internals[4];
} public_type;

foo.c

typedef struct public
{
    uint32_t a;
}internals_type;

Then in the functions, I'm doing a cast like.

void bar(public_type * const public_struct)
{
     internals_type* const internals = &public_struct->internals;
     intrnals->a = getSomething();
     // .....
}

Is there a better way of doing this?

I've tried some weird stuff with unions and pointers in the header, but nothing seems to be better and I'm curious if this can get any cleaner or at least if the warnings from casting a pointer from one type to pointer to another could be removed.

Petar Velev
  • 2,305
  • 12
  • 24
  • Related questions include [Expose only required information without including unnecessary header files](https://stackoverflow.com/questions/48440355/expose-only-required-information-without-including-unnecessary-header-files/48442570#48442570), [Is there a way to make GCC/Clang aware of inheritance in C?](https://stackoverflow.com/questions/21183556/is-there-a-way-to-make-gcc-clang-aware-of-inheritance-in-c/21186303#21186303), and… – Eric Postpischil Oct 16 '18 at 13:41
  • … [Is incompatible pointer assign necessary to implement polymorphism in C](https://stackoverflow.com/questions/47543996/is-incompatible-pointer-assign-necessary-to-implement-polymorphism-in-c). – Eric Postpischil Oct 16 '18 at 13:41
  • @EricPostpischil, In this case, it returns an `uint32_t` which is a primitive type and it is copied. Since the type of `internals_type.a` is `uint32_t`. – Petar Velev Oct 16 '18 at 13:47
  • @PetarVelev: Ah, I was skimming to fast and assumed `getSomething` was returning one of the objects that was the subject of a question, not just an integer. I will replace the comment. – Eric Postpischil Oct 16 '18 at 14:00
  • I was about to close this as a duplicate of one of the above, but the new requirements for allocation on the user wide without dynamic memory allocation change it. Those are abnormally restrictive. – Eric Postpischil Oct 16 '18 at 14:00
  • 1
    You can use opaque type without dynamic allocation. In embedded systems you typically use some statically allocated memory pool instead. The allocation method is separate from the design pattern. – Lundin Oct 16 '18 at 14:17
  • 1
    @Lundin: OP has stated a requirement is “the whole memory should be on the user side,” which I interpret to mean that user of the library must provide memory for the objects. That requires the user to have knowledge of the amount of memory needed, which prevents the design from being completely opaque. – Eric Postpischil Oct 16 '18 at 14:48
  • 2
    Adding restrictions like "without memory allocation" after answers arrive (especially good ones) is poor SO etiquette. Better to have accepted it and post another question. Moving targets are hard to answer. – chux - Reinstate Monica Oct 16 '18 at 15:06
  • @EricPostpischil That requirement doesn't make much sense. But it can be handled by the user passing on the memory allocation function as a callback, which in turn returns the amount of memory allocated. `size_t stuff_init (stuff_t** stuff, allocator_t* allocator);` where `allocator_t` is for example a `typedef void* allocator_t (size_t n);`, which in turn happens to be eerie similar to malloc function declaration. In theory it could be something icky like alloca instead, or a embedded system memory pool allocator function. – Lundin Oct 16 '18 at 15:10
  • @chux I agree. So perhaps we could rollback that requirement, leave this question as it is (or close as dupe to the previously used one), and then the OP can ask a follow-up question if needed? Me and Eric have used up our gold badger dupe hammer rights, so maybe you can mediate in the moderation here, being the 3rd golden badger. – Lundin Oct 16 '18 at 15:16
  • 1
    Petar Velev, please roll back your post to not invalidate the good answers here. As desired, post a new question with your additional restrictions. It would be good for the new post to link to this one and details how the new requirements make it different. – chux - Reinstate Monica Oct 16 '18 at 15:21
  • 1
    @chux Done. Ty all. – Petar Velev Oct 16 '18 at 15:30
  • Since my earlier duplicate suggestion seems to have disappeared in all the changes, I'm adding it as a comment : [Partitioning struct into private and public sections?](https://stackoverflow.com/questions/3824329/partitioning-struct-into-private-and-public-sections) – Sander De Dycker Oct 16 '18 at 15:44

3 Answers3

18

I suggest you read more about opaque data types, and consider e.g. the FILE structure.

In short, don't split your structure into "public" and "private" variants (that way lies madness and possible undefined behavior). Instead just declare a structure in a public header file, and have your functions return pointers to that structure or accept arguments that are pointers to that structure.

Then internally in the library you have a private header file which have the definition of the structure, and use that header file for your implementation.


Simple example

Public header file

#ifndef PUBLIC_HEADER_FILE_H
#define PUBLIC_HEADER_FILE_H

typedef my_private_structure MY_PUBLIC_TYPE;

MY_PUBLIC_TYPE *mylib_create(void);
void mylib_destroy(MY_PUBLIC_TYPE *ptr);

#endif

Private header file

#ifndef PRIVATE_HEADER_FILE_H
#define PRIVATE_HEADER_FILE_H

#include "public_header_file.h"

struct my_private_structure
{
    // Some private fields here
};

#endif

Private library source file

#include "private_header_file.h"
#include <stdlib.h>

MY_PUBLIC_TYPE *mylib_create(void)
{
    MY_PUBLIC_TYPE *ptr = malloc(sizeof *ptr);
    return ptr;
}

void mylib_destroy(MY_PUBLIC_TYPE *ptr)
{
    free(ptr);
}

You distribute public_header_file.h together with your library. It's the header file that the users of the library will use.

The source of your library, and especially the private_header_file.h file should not be distributed, or at least not installed if you make an open-source library.

Note that this scheme make all of the structure "private", which is usually a good idea since then you can modify it as you like without the users of the library needing to rebuild their applications using your library. To access members of the private structure you can use functions which simply returns the value of whatever member needs to be accessed.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Can you add example code? What goes in .h and what in .c – Swanand Oct 16 '18 at 12:51
  • @Swanand Okay, done. – Some programmer dude Oct 16 '18 at 13:01
  • That's a great answer. Unfortunately, I forgot to mention that there is a restriction of using dynamic memory allocation since the software is low level and should obey some safety principles. I did some edits. – Petar Velev Oct 16 '18 at 13:33
  • 2
    @PetarVelev The dynamic allocation is just an example. You could just as well return a pointer to a compile-time allocated object (a `static` global variable for example). The basic principles stays the same. – Some programmer dude Oct 16 '18 at 13:38
  • @Someprogrammerdude I also thought of that but it It is not so simple. The library should be used in a multithread environment so my idea was to remove the internal state completely. I'm receiving the memory from the user and I'm hiding my internal parameters there. If I'm using `static` global variables or an array of them I'll restrict the users with configuration at least – Petar Velev Oct 16 '18 at 13:42
  • 1
    @PetarVelev, another solution is to just export the type definitions (so no abstraction hiding) and rely on the user _not_ using the definitions. That will allow the user to allocate an object of the corect size that it can pass to your library functions. So the complete type definition export allows the allocation by the user. – Paul Ogilvie Oct 16 '18 at 13:50
  • @PaulOgilvie Yes, That is correct. Unfortunately, I cannot so I also have to manage the sizes(of internals array in the public structure and the internal structure) and assert which is kinda bad in my opinion. – Petar Velev Oct 16 '18 at 13:53
  • 1
    @PetarVelev, then I am afraid _there is __no__ solution_. The compiler can only allocate an object on the stack or in global memory of it knows its size _at compile time_. – Paul Ogilvie Oct 16 '18 at 13:55
  • Ah, there is one more solution: in the public header define the public object such that it is large enough to hold (encapsulate) the private object. E.g. define a char array of enough bytes. But then you must maintain that size manually. At least you now can pass it as a void * pointer to your library. – Paul Ogilvie Oct 16 '18 at 13:58
  • @PaulOgilvie Yes exactly. This was my solution and I tried to explain this. It is an array of type uint8_t which is cast to the internal structure. However, there is a need of asserting the sizes and also cast the array to the internal structure every time. I did some experiments with unions but I couldn't find a solution. This is why I asked here. I've got curious if there is – Petar Velev Oct 16 '18 at 14:01
  • @Someprogrammerdude This is a good answer so I feel a bit bad to close this as dupe (even though this has been answered lots of times before). The one I used is the one listed as canonical dupe by our C FAQ currently. Since your answer is generic, perhaps you could consider posting this code in the linked dupe? Or we can poke a moderator to merge the threads maybe. – Lundin Oct 16 '18 at 14:20
  • @PetarVelev, you can define both the internal and external structure to have at the beginning a size field that _must_ be filled in. Yuo can use that to asses the size. following the size field, in the public part follows a byte array of that size and in the private part you simply cast it to the internal structure. Microsoft uses this technique too in its Windows API. – Paul Ogilvie Oct 16 '18 at 14:23
4

Typically you use void* to hide your implementation, e.g.

void *create_foo(int param1, int param2);
void print_foo(void* foo);
int operate_on_foo(void* foo);

So you "cast" the void* in your functions to your internal type.

The downside of this is, that the compiler can't help you with the types, e.g. the user of the library can use a int* and pass it to your function and the compiler will accept it. When you use an actual type, the compiler will complain.

hellow
  • 12,430
  • 7
  • 56
  • 79
2

You can do in foo.h:

typedef struct internals_type;
typedef struct {
    uint16 a;
    internals_type* internals;
} public_type

Including foo.h is then enough for your user to compile, without knowing what is exactly inside internals_type.

Ludo
  • 813
  • 1
  • 9
  • 21