The whole point of writing C is to keep it simple and only build exactly what's needed. No other language I've tried comes close to supporting the same level of custom fitting. This is also the reason it's potentially really fast; more because it encourages simple, direct solutions than anything else.
Virtual dispatch is a function pointer, either embedded directly in the value or together with others in a shared vtable. A type id may be added to the vtable if needed. Avoid inheritance tricks, composition is mostly the right answer in C. Downcasting is a solved [0] problem [1].
[0] https://gitlab.com/sifoo/snigl/blob/master/src/snigl/type.c#...
[1] https://gitlab.com/sifoo/snigl/blob/master/src/snigl/util.h#...