How to do generic containers in C?

Suppose I have a generic container, say, a list, that holds objects of unknown type. I want to copy everything into the container behind the scenes. I’ve come quite far with copy and destruction callbacks and internal storage declared as char*, so I can have memory blocks of arbitrary size. Roughly like this:

typedef void (*copy_f)(void *dest, const void *src);
typedef void (*destroy_f)(void *element);

typedef struct ListNode {
    struct ListNode *next,
    char *data,
} ListNode;

typedef struct List {
    ListNode *head,
    copy_f copy_element,        // these two can be NULL
    destroy_f destroy_element,
    size_t element_size,
} List;

// ...

ListNode *ListNode_new(const List *L, const void *element) {
    ListNode *n = malloc(sizeof(*n));
    n->data = malloc(L->element_size);
    L->copy_element(n->data, element);
    // or use memcpy if L->copy is NULL
    // ...
}

// ...

It works. I can use this with arbitrarily nested types and according destructors and copy constructors, but it feels like a hack. It also results in a lot of ugly compiler warnings about incompatible pointer types. (The function typedefs have void*, but I don’t want to cast from void* to the actual type in a string destructor or every simple int comparison callback.)

The more I dig, the more I appreciate C++ and the STL. C is probably just not meant to be used like this, but is there a better way?