0

Having:

struct packet_sample_t {
   int common_id;
   int some;
   char data[];
   float foo;
}

Having the following function:

void dispatch_packet(void *packet);

My goal would be to parse the packet id and then call its handler, but I can't retrieve the struct variable common_id from void *.

I would like to create something like an interface in hight level languages, assuming that all my packets structures should have the variable common_id.
So I'm looking something that would work like below:

struct packet_base {
   int common_id;
}

void dispatch_packet(void *packet) {
   int common_id = ( (packet_base *)packet )->common_id;
   switch(common_id) {...}
}

void test() {
   packet_sample_t packet = {.common_id = 10, ...};
   dispatch_packet((void *) &packet); //this function should retrieve `common_id`

   packet_other_t other = {.common_id = 1};
   dispatch_packet((void *) &other); // again with another packet
}

Im not that familiar to C language and I dont really know how I could do this. But in simple words, I would like to be able to cast a packet to its packet_base, that are sharing both a common variable.

EDIT: more details in the example

lordjj
  • 29
  • 5
  • 1
    You can freely cast any object pointer type to and from `void*` as long as it is actually pointing to the data of that type... – Eugene Sh. Jun 18 '18 at 19:05
  • ye but a `packet_sample_t` is not a `packet_base_t` – lordjj Jun 18 '18 at 19:06
  • 1
    I see. Take a look : https://stackoverflow.com/questions/252552/why-do-we-need-c-unions Pretty much about your case – Eugene Sh. Jun 18 '18 at 19:07
  • I would try to not use void pointers. Give the compiler a chance and help you as much as possible – Ed Heal Jun 18 '18 at 19:09
  • @EugeneSh. Could you provide an example related to my topic? I can't apply the unions to my need.. That's why I asked this question – lordjj Jun 18 '18 at 19:12
  • Ok, wait up.... – Eugene Sh. Jun 18 '18 at 19:14
  • When you pass a `packet_sample_t` pointer into `dispatch_packet`, and inside that function you cast it to a `struct packet` pointer in order to get the `common_id`, the input parameter packet is still pointing to the `struct packet_sample_t` that was passed in, so when you call whatever function you need in the dispatcher, the memory is still intact. – bruceg Jun 18 '18 at 19:16
  • I cannot find any union in your code? – klutt Jun 18 '18 at 19:17
  • ye my function `dispatch_packet` is generic and has to workd for all packets I will make. And they are both having the variable `int common_id` – lordjj Jun 18 '18 at 19:18
  • @klutt you're right, I know my problem can be resolved by an union but I can't apply it to my request – lordjj Jun 18 '18 at 19:18
  • Or maybe could I do a tricky thing, like.. memcpy the first bytes of my `void *` into a `packet_base *` assuming all packets got at the begin of their struct the `common_id` variable – lordjj Jun 18 '18 at 19:20

2 Answers2

1

Your technique is valid. There's a number of ways to do struct inheritance in C, and this is one of them. 21st Century C might be a good read for you as well as Object-Oriented Programming with ANSI C.

You have a problems with how you're declaring and using your structs and types. Let's look at this.

struct packet_base {
   int common_id;
};

This has the type struct packet_base. If you want to declare a pointer to this type you need to write struct packet_base *packet. If you want to cast a variable of this type it's (struct packet_base *)thing.

This is annoying, so you typically declare a type alias to the struct using typedef. The syntax is typedef <type> <alias>

typedef struct {
   int common_id;
} packet_base_t;

That says the type struct { int common_id; } is aliased to packet_base_t. Now you can use packet_base_t as the type. packet_base_t *packet declares a pointer and (packet_base_t *)thing casts.

With that fixed, plus some small errors, it works. See What is the difference between char array vs char pointer in C? for char *data vs char data[].

typedef struct {
   int common_id;
   int some;
   char *data;
   float foo;
} packet_sample_t;

typedef struct {
   int common_id;
} packet_base_t;

void dispatch_packet(void *arg) {
    // It's simpler to cast to a new variable once then to muck
    // up the code with casts.
    packet_base_t *packet = (packet_base_t *)arg;

    int common_id = packet->common_id;
    printf("id: %d\n", common_id);
}
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • assuming `common_id` is always at the top of each packet? – lordjj Jun 18 '18 at 19:33
  • "Your technique is valid". Unfortunately the C standard doesn't seem to share this optimism. – n. m. could be an AI Jun 18 '18 at 19:36
  • @lordjj Yes. The technique relies on derived structs having the same memory layout as the base. Assuming 64 bit integers, `packet->common_id` basically says "get me the first 64 bits in the memory `packet` points to". [There are complications](https://stackoverflow.com/questions/5435841/memory-alignment-in-c-structs#5435890). – Schwern Jun 18 '18 at 19:37
  • @n.m. Yeah, but it's a thing people seem to get away with. Perl 5 relies heavily on this technique. As for really doing polymorphism in C, I defer to whatever "*21st Century C*" says. – Schwern Jun 18 '18 at 19:41
  • @Schwern I see, this is so dope, I didn't expect that my sample code was about to work or even compile XD – lordjj Jun 18 '18 at 19:42
  • "it's a thing people seem to get away with" true fact! – n. m. could be an AI Jun 18 '18 at 19:45
  • @lordjj This technique will only get you so far before it gets messy. You'll want to look into vtables and n.m.'s struct embedding technique as an alternative. Or use a more sophisticated language that already supports inheritance (or inheritance-like things) such as C++, Go, or Rust. – Schwern Jun 18 '18 at 19:46
  • @Schwern `__attribute__((__packed__))` should fix the problem of padding, right? – lordjj Jun 18 '18 at 20:02
  • but is the padding problem appliable on my example? – lordjj Jun 18 '18 at 20:09
  • @Schwern :( ? sorry but could u reply – lordjj Jun 18 '18 at 20:38
  • @lordjj I don't know much about padding issues, just that it's a gotcha with this technique. You'll have to ask another question. Also, Object-oriented programming and polymorphism in C is a very large and complex topic and I recommend you don't tackle it if you're new to C. I'd recommend reading those books I recommended and various other questions here about OOP in C. And consider whether C is the right language for your project. – Schwern Jun 18 '18 at 21:41
  • I read about, and `__attribute__((__packed__)) ` after the `struct` keyword will solve the padding issue, assuming the compilation is performed by gcc or clang ;) – lordjj Jun 19 '18 at 11:03
0

You can use union to aggregate the data of different types in a single structure

struct packet1_t
{
   // Packet1 specific data
   ......
}

struct packet2_t
{
   // Packet2 specific data
   ......
}

struct packet_t
{
    int common_id;
    union
    {
        struct packet1_t packet1;
        struct packet2_t packet2;
        ......
    }
} packet;

Now, based on the ID you can pick the correct type from the union:

int do_something_with_packet(packet_t packet)
{
    switch (packet.common_id)
    {
        case 1:
            do_something_with_packet1(packet.packet1);
            break;
        case 2:
            do_something_with_packet2(packet.packet2);
            break;
        ..................
    }
    ....
}
Eugene Sh.
  • 17,802
  • 8
  • 40
  • 61
  • I see, so unions are not solving my problem. my function `dispatch_packet` was about to work without knowing the packet types. it was supposed to be generic – lordjj Jun 18 '18 at 19:23
  • The `do_something_with_packet` is generic. – Eugene Sh. Jun 18 '18 at 19:24
  • At some point you have to know what the actual type. But up to that point you can keep generic as `do_something_with_packet` is doing. – Eugene Sh. Jun 18 '18 at 19:25
  • @lordjj - If you start using a cast, you are probably doing something wrong. When you think about using a cast - have another think about the design – Ed Heal Jun 18 '18 at 19:25
  • I see, this is not really that kind of genericity I was looking for >< I'll edit my topic, second – lordjj Jun 18 '18 at 19:26
  • Finally, I could replace the union by a void *, and my packet_t would be simply a wrapper.. – lordjj Jun 18 '18 at 19:27
  • Why do you need `void` ? Everywhere you cast you can replace it with proper type picked from the union. – Eugene Sh. Jun 18 '18 at 19:28
  • I'm making something like a library and my code should work without knowing any type u know – lordjj Jun 18 '18 at 19:29
  • The trick is to avoid void pointers. Use the compiler and its abilities to find out errors in your code.Switch on all warnings etc. – Ed Heal Jun 18 '18 at 19:29
  • Well just use the facade pattern. – Ed Heal Jun 18 '18 at 19:30
  • 1
    But you know the set of possible protocols? – Eugene Sh. Jun 18 '18 at 19:30
  • of course.. but is that really important, I don't get you – lordjj Jun 18 '18 at 19:36
  • Then you can define `packet1_t`, `packet2_t` and so on precisely, can't you? It's not about working with unknown type, it's about working with one of the known types. – Eugene Sh. Jun 18 '18 at 19:37
  • "Everywhere you cast you can replace it with proper type picked from the union" Then you have to maintain that union, which can turn into a dependency hell rather quickly. – n. m. could be an AI Jun 18 '18 at 19:39
  • I see but but I dont have knowned types since I dont have any built packets on my lib. My code is used by some users that define their packets and handlers in a upper scope – lordjj Jun 18 '18 at 19:40
  • @n.m. Indeed. It would depend on the size of the project and the chosen style. Apparently the OP might choose the perfectly legit `void` option. It's just the question was about using `unions` :) – Eugene Sh. Jun 18 '18 at 19:41
  • @lordjj If you have some types to cast into - then you have these types. If you don't - then ok, you need a different approach. Which would involve the requirement from user to provide some interface functions together with the types. – Eugene Sh. Jun 18 '18 at 19:42
  • @EugeneSh. thanks for you help, the other answer is ok.. Finally, my sample code worked and I didnt know it.. – lordjj Jun 18 '18 at 19:46
  • I will specify in my doc that `int common_id` is necessary on each packet header – lordjj Jun 18 '18 at 19:46
  • I didn't expect that code worked at all.. Else I would have tried – lordjj Jun 18 '18 at 19:56