update ucx

Mon, 10 Nov 2025 21:52:51 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 10 Nov 2025 21:52:51 +0100
changeset 113
dde28a806552
parent 112
c3f2f16fa4b8
child 114
3da24640513a

update ucx

application/config.c file | annotate | diff | comparison | revisions
libidav/crypto.c file | annotate | diff | comparison | revisions
libidav/crypto.h file | annotate | diff | comparison | revisions
libidav/davqlexec.c file | annotate | diff | comparison | revisions
libidav/davqlparser.c file | annotate | diff | comparison | revisions
libidav/methods.c file | annotate | diff | comparison | revisions
libidav/pwdstore.c file | annotate | diff | comparison | revisions
libidav/resource.c file | annotate | diff | comparison | revisions
libidav/utils.c file | annotate | diff | comparison | revisions
libidav/utils.h file | annotate | diff | comparison | revisions
libidav/webdav.c file | annotate | diff | comparison | revisions
libidav/xml.c file | annotate | diff | comparison | revisions
ucx/array_list.c file | annotate | diff | comparison | revisions
ucx/cx/allocator.h file | annotate | diff | comparison | revisions
ucx/cx/array_list.h file | annotate | diff | comparison | revisions
ucx/cx/buffer.h file | annotate | diff | comparison | revisions
ucx/cx/collection.h file | annotate | diff | comparison | revisions
ucx/cx/common.h file | annotate | diff | comparison | revisions
ucx/cx/common.h.orig file | annotate | diff | comparison | revisions
ucx/cx/compare.h file | annotate | diff | comparison | revisions
ucx/cx/hash_key.h file | annotate | diff | comparison | revisions
ucx/cx/hash_map.h file | annotate | diff | comparison | revisions
ucx/cx/iterator.h file | annotate | diff | comparison | revisions
ucx/cx/json.h file | annotate | diff | comparison | revisions
ucx/cx/kv_list.h file | annotate | diff | comparison | revisions
ucx/cx/linked_list.h file | annotate | diff | comparison | revisions
ucx/cx/list.h file | annotate | diff | comparison | revisions
ucx/cx/map.h file | annotate | diff | comparison | revisions
ucx/cx/mempool.h file | annotate | diff | comparison | revisions
ucx/cx/printf.h file | annotate | diff | comparison | revisions
ucx/cx/properties.h file | annotate | diff | comparison | revisions
ucx/cx/streams.h file | annotate | diff | comparison | revisions
ucx/cx/string.h file | annotate | diff | comparison | revisions
ucx/cx/test.h file | annotate | diff | comparison | revisions
ucx/cx/tree.h file | annotate | diff | comparison | revisions
ucx/cx/utils.h file | annotate | diff | comparison | revisions
ucx/hash_key.c file | annotate | diff | comparison | revisions
ucx/hash_map.c file | annotate | diff | comparison | revisions
ucx/iterator.c file | annotate | diff | comparison | revisions
ucx/json.c file | annotate | diff | comparison | revisions
ucx/kv_list.c file | annotate | diff | comparison | revisions
ucx/linked_list.c file | annotate | diff | comparison | revisions
ucx/list.c file | annotate | diff | comparison | revisions
ucx/map.c file | annotate | diff | comparison | revisions
ucx/mempool.c file | annotate | diff | comparison | revisions
ucx/properties.c file | annotate | diff | comparison | revisions
ucx/string.c file | annotate | diff | comparison | revisions
ucx/tree.c file | annotate | diff | comparison | revisions
ui/cocoa/ListDataSource.m file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.h file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.m file | annotate | diff | comparison | revisions
ui/cocoa/Toolbar.h file | annotate | diff | comparison | revisions
ui/cocoa/Toolbar.m file | annotate | diff | comparison | revisions
ui/cocoa/appdelegate.m file | annotate | diff | comparison | revisions
ui/cocoa/button.m file | annotate | diff | comparison | revisions
ui/cocoa/container.m file | annotate | diff | comparison | revisions
ui/cocoa/image.m file | annotate | diff | comparison | revisions
ui/cocoa/label.m file | annotate | diff | comparison | revisions
ui/cocoa/list.m file | annotate | diff | comparison | revisions
ui/cocoa/objs.mk file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.h file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.m file | annotate | diff | comparison | revisions
ui/cocoa/webview.h file | annotate | diff | comparison | revisions
ui/cocoa/webview.m file | annotate | diff | comparison | revisions
ui/cocoa/widget.h file | annotate | diff | comparison | revisions
ui/cocoa/widget.m file | annotate | diff | comparison | revisions
ui/cocoa/window.m file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/menu.c file | annotate | diff | comparison | revisions
ui/common/menu.h file | annotate | diff | comparison | revisions
ui/common/object.c file | annotate | diff | comparison | revisions
ui/common/object.h file | annotate | diff | comparison | revisions
ui/common/toolbar.c file | annotate | diff | comparison | revisions
ui/common/toolbar.h file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/gtk/headerbar.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/list.h file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.h file | annotate | diff | comparison | revisions
ui/motif/container.c file | annotate | diff | comparison | revisions
ui/motif/container.h file | annotate | diff | comparison | revisions
ui/motif/graphics.c file | annotate | diff | comparison | revisions
ui/motif/graphics.h file | annotate | diff | comparison | revisions
ui/motif/label.c file | annotate | diff | comparison | revisions
ui/motif/label.h file | annotate | diff | comparison | revisions
ui/motif/toolkit.c file | annotate | diff | comparison | revisions
ui/motif/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/toolbar.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/webview.h file | annotate | diff | comparison | revisions
ui/win32/button.c file | annotate | diff | comparison | revisions
ui/win32/container.c file | annotate | diff | comparison | revisions
ui/win32/objs.mk file | annotate | diff | comparison | revisions
ui/win32/text.c file | annotate | diff | comparison | revisions
ui/win32/text.h file | annotate | diff | comparison | revisions
ui/win32/toolkit.c file | annotate | diff | comparison | revisions
ui/win32/toolkit.h file | annotate | diff | comparison | revisions
ui/win32/window.c file | annotate | diff | comparison | revisions
--- a/application/config.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/application/config.c	Mon Nov 10 21:52:51 2025 +0100
@@ -31,7 +31,6 @@
 #include <string.h>
 #include <sys/types.h>
 #include <cx/hash_map.h>
-#include <cx/utils.h>
 #include <errno.h>
 #include <libxml/tree.h>
 
@@ -41,6 +40,8 @@
 #include <libidav/utils.h>
 #include <libidav/config.h>
 
+#include <cx/streams.h>
+
 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
 #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b)
 
--- a/libidav/crypto.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/crypto.c	Mon Nov 10 21:52:51 2025 +0100
@@ -381,12 +381,12 @@
     SHA256_Init(ctx);
 }
 
-void dav_sha256_update(DAV_SHA_CTX *ctx, const void *data, size_t length) {
+void dav_sha256_update(DAV_SHA_CTX *ctx, const char *data, size_t length) {
     SHA256_Update(ctx, data, length);
 }
 
-void dav_sha256_final(char *md, DAV_SHA_CTX *ctx) {
-    SHA256_Final(md, ctx);
+void dav_sha256_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    SHA256_Final(buf, ctx);
 }
 
 #else
@@ -848,13 +848,16 @@
     return ctx;
 }
 
+void dav_sha256_init(DAV_SHA_CTX *ctx) {
+    CC_SHA256_Init(ctx);
+}
+
 void dav_sha256_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
     CC_SHA256_Update(ctx, data, len);
 }
 
 void dav_sha256_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
     CC_SHA256_Final(buf, ctx);
-    free(ctx);
 }
 
 DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
@@ -1422,7 +1425,7 @@
     DAV_SHA_CTX *ctx = dav_sha256_create();
     if(ctx) {
         dav_sha256_update(ctx, data, len);
-        dav_sha256_final(ctx, hash);
+        dav_sha256_final_free(ctx, hash);
     }
     return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
 }
@@ -1448,7 +1451,6 @@
     
     // cleanup
     cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject);
-    free(ctx);
 }
 
 DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
@@ -1514,7 +1516,10 @@
 }
 #endif
 
-
+void dav_sha256_final_free(DAV_SHA_CTX *ctx, unsigned char *buf) {
+    dav_sha256_final(ctx, buf);
+    free(ctx);
+}
 
 CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key) {
     CxBuffer *encbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
--- a/libidav/crypto.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/crypto.h	Mon Nov 10 21:52:51 2025 +0100
@@ -159,6 +159,7 @@
 DAV_SHA_CTX* dav_sha256_create(void);
 void dav_sha256_update(DAV_SHA_CTX *ctx, const char *data, size_t len);
 void dav_sha256_final(DAV_SHA_CTX *ctx, unsigned char *buf);
+void dav_sha256_final_free(DAV_SHA_CTX *ctx, unsigned char *buf);
 
 DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc);
 
--- a/libidav/davqlexec.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/davqlexec.c	Mon Nov 10 21:52:51 2025 +0100
@@ -31,7 +31,6 @@
 #include <string.h>
 #include <inttypes.h>
 
-#include <cx/utils.h>
 #include <cx/map.h>
 #include <cx/hash_map.h>
 #include <cx/printf.h>
--- a/libidav/davqlparser.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/davqlparser.c	Mon Nov 10 21:52:51 2025 +0100
@@ -27,7 +27,6 @@
  */
 
 #include "davqlparser.h"
-#include <cx/utils.h>
 #include <cx/linked_list.h>
 #include <cx/printf.h>
 #include <string.h>
--- a/libidav/methods.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/methods.c	Mon Nov 10 21:52:51 2025 +0100
@@ -36,7 +36,6 @@
 #include "session.h"
 #include "xml.h"
 
-#include <cx/utils.h>
 #include <cx/printf.h>
 #include <cx/hash_map.h>
 
@@ -481,10 +480,6 @@
 DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response) {
     char *url = NULL;
     curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &url);
-    if(!root) {
-        printf("methods.c: TODO: remove\n");
-        root = dav_resource_new_href(sn, util_url_path(url)); // TODO: remove
-    }
     
     //printf("%.*s\n\n", response->size, response->space);
     xmlDoc *doc = xmlReadMemory(response->space, response->size, url, NULL, 0);
--- a/libidav/pwdstore.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/pwdstore.c	Mon Nov 10 21:52:51 2025 +0100
@@ -34,7 +34,8 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <cx/utils.h>
+#include <cx/streams.h> 
+
 #include <cx/hash_map.h>
 
 #ifdef _WIN32
@@ -263,7 +264,7 @@
 }
 
 static void remove_list_entries(PwdStore *s, const char *id) {
-    CxIterator i = cxListMutIterator(s->locations);
+    CxIterator i = cxListIterator(s->locations);
     cx_foreach(PwdIndexEntry*, ie, i) {
         if(!strcmp(ie->id, id)) {
             cxIteratorFlagRemoval(i);
@@ -271,7 +272,7 @@
             break;
         }
     }
-    i = cxListMutIterator(s->noloc);
+    i = cxListIterator(s->noloc);
     cx_foreach(PwdIndexEntry*, ie, i) {
         if(!strcmp(ie->id, id)) {
             cxIteratorFlagRemoval(i);
--- a/libidav/resource.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/resource.c	Mon Nov 10 21:52:51 2025 +0100
@@ -37,7 +37,6 @@
 #include "methods.h"
 #include "crypto.h"
 #include <cx/buffer.h>
-#include <cx/utils.h>
 #include <cx/hash_map.h>
 #include <cx/printf.h>
 #include <cx/mempool.h>
@@ -835,7 +834,7 @@
     HashStream *s = stream;
     if(offset == 0 && whence == SEEK_SET) {
         unsigned char buf[DAV_SHA256_DIGEST_LENGTH];
-        dav_sha256_final(s->sha, buf);
+        dav_sha256_final_free(s->sha, buf);
         s->sha = NULL;
     } else {
         s->error = 1;
@@ -938,7 +937,7 @@
                     data->length);
             
             if(hstr.sha) {
-                dav_sha256_final(hstr.sha, (unsigned char*)data->hash);
+                dav_sha256_final_free(hstr.sha, (unsigned char*)data->hash);
                 char *hash = util_hexstr((unsigned char*)data->hash, 32);
                 dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
                 free(hash);
@@ -1599,7 +1598,7 @@
             property->value = n->children ? dav_convert_xml(sn, n->children) : NULL;
             
             cxmutstr propkey = dav_property_key(property->ns->name, property->name);
-            cxMapPut(map, cx_hash_key_cxstr(propkey), property);
+            cxMapPut(map, propkey, property);
             cx_strfree(&propkey);
         }
         n = n->next;
--- a/libidav/utils.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/utils.c	Mon Nov 10 21:52:51 2025 +0100
@@ -34,7 +34,6 @@
 #include <ctype.h>
 #include <cx/string.h>
 #include <cx/buffer.h>
-#include <cx/utils.h>
 #include <cx/printf.h>
 #include <libxml/tree.h>
 #include <curl/curl.h>
@@ -348,10 +347,81 @@
     return path;
 }
 
+char* util_url_encode(DavSession *sn, const char *url) {
+    CURL *handle = sn ? sn->handle : NULL;
+#if LIBCURL_VERSION_NUM < 0x075200
+    int cleanup_handle = 0;
+    if(!handle) {
+        handle = curl_easy_init();
+        cleanup_handle = 1;
+    }
+#endif
+    
+    char *esc = curl_easy_escape(handle, url, strlen(url));
+    char *ret = esc ? strdup(esc) : NULL;
+    curl_free(esc);
+
+#if LIBCURL_VERSION_NUM < 0x075200
+    if(cleanup_handle) {
+        curl_easy_cleanup(handle);
+    }
+#endif
+    
+    return ret;
+}
+
 char* util_url_decode(DavSession *sn, const char *url) {
-    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
-    char *ret = strdup(unesc);
+    CURL *handle = sn ? sn->handle : NULL;
+#if LIBCURL_VERSION_NUM < 0x075200
+    int cleanup_handle = 0;
+    if(!handle) {
+        handle = curl_easy_init();
+        cleanup_handle = 1;
+    }
+#endif
+    
+    char *unesc = curl_easy_unescape(handle, url, strlen(url), NULL);
+    char *ret = unesc ? strdup(unesc) : NULL;
     curl_free(unesc);
+
+#if LIBCURL_VERSION_NUM < 0x075200
+    if(cleanup_handle) {
+        curl_easy_cleanup(handle);
+    }
+#endif
+    
+    return ret;
+}
+
+cxmutstr util_url_encode_s(const CxAllocator *a, cxstring url) {
+    CURL *handle = NULL;
+#if LIBCURL_VERSION_NUM < 0x075200
+    handle = curl_easy_init();
+#endif
+    
+    char *esc = curl_easy_escape(handle, url.ptr, url.length);
+    cxmutstr ret = esc ? cx_strdup_a(a, cx_str(esc)) : (cxmutstr){NULL, 0};
+    curl_free(esc);
+    
+#if LIBCURL_VERSION_NUM < 0x075200
+    curl_easy_cleanup(handle);
+#endif
+    return ret;
+}
+
+cxmutstr util_url_decode_s(const CxAllocator *a, cxstring url) {
+    CURL *handle = NULL;
+#if LIBCURL_VERSION_NUM < 0x075200
+    handle = curl_easy_init();
+#endif
+    
+    char *unesc = curl_easy_unescape(handle, url.ptr, url.length, NULL);
+    cxmutstr ret = unesc ? cx_strdup_a(a, cx_str(unesc)) : (cxmutstr){NULL, 0};
+    curl_free(unesc);
+    
+#if LIBCURL_VERSION_NUM < 0x075200
+    curl_easy_cleanup(handle);
+#endif
     return ret;
 }
 
--- a/libidav/utils.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/utils.h	Mon Nov 10 21:52:51 2025 +0100
@@ -76,7 +76,10 @@
 cxstring util_url_base_s(cxstring url);
 const char* util_url_path(const char *url);
 cxstring util_url_path_s(cxstring url);
+char* util_url_encode(DavSession *sn, const char *url);
 char* util_url_decode(DavSession *sn, const char *url);
+cxmutstr util_url_encode_s(const CxAllocator *a, cxstring url);
+cxmutstr util_url_decode_s(const CxAllocator *a, cxstring url);
 const char* util_resource_name(const char *url);
 const char* util_resource_name_c(const char *url, char pathseparator);
 const char* util_path_file_name(const char *url);
--- a/libidav/webdav.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/webdav.c	Mon Nov 10 21:52:51 2025 +0100
@@ -36,7 +36,6 @@
 #include "session.h"
 #include "methods.h"
 #include <cx/buffer.h>
-#include <cx/utils.h>
 #include <cx/linked_list.h>
 #include <cx/hash_map.h>
 #include <cx/compare.h>
@@ -353,8 +352,8 @@
     int ret = 0;
     dav_context_lock(context);
     CxList *sessions = context->sessions;
-    ssize_t i = cxListFind(sessions, sn);
-    if(i >= 0) {
+    size_t i = cxListFind(sessions, sn);
+    if(cxListIndexValid(sessions, i)) {
         cxListRemove(sessions, i);
     } else {
         ret = 1;
--- a/libidav/xml.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/libidav/xml.c	Mon Nov 10 21:52:51 2025 +0100
@@ -30,7 +30,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <cx/utils.h>
 #include <cx/printf.h>
 
 #include "xml.h"
--- a/ucx/array_list.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/array_list.c	Mon Nov 10 21:52:51 2025 +0100
@@ -50,7 +50,7 @@
 }
 
 CxArrayReallocator cx_array_default_reallocator_impl = {
-        cx_array_default_realloc, NULL, NULL, 0, 0
+        cx_array_default_realloc, NULL, NULL
 };
 
 CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
@@ -72,11 +72,11 @@
     }
 
     // retrieve the pointer to the actual allocator
-    const CxAllocator *al = alloc->ptr1;
+    const CxAllocator *al = alloc->allocator;
 
     // check if the array is still located on the stack
     void *newmem;
-    if (array == alloc->ptr2) {
+    if (array == alloc->stack_ptr) {
         newmem = cxMalloc(al, n);
         if (newmem != NULL && array != NULL) {
             memcpy(newmem, array, old_capacity*elem_size);
@@ -89,27 +89,45 @@
 
 struct cx_array_reallocator_s cx_array_reallocator(
         const struct cx_allocator_s *allocator,
-        const void *stackmem
+        const void *stack_ptr
 ) {
     if (allocator == NULL) {
         allocator = cxDefaultAllocator;
     }
     return (struct cx_array_reallocator_s) {
             cx_array_advanced_realloc,
-            (void*) allocator, (void*) stackmem,
-            0, 0
+            allocator, stack_ptr,
     };
 }
 
 // LOW LEVEL ARRAY LIST FUNCTIONS
 
-static size_t cx_array_align_capacity(
-        size_t cap,
-        size_t alignment,
-        size_t max
+/**
+ * Intelligently calculates a new capacity, reserving some more
+ * elements than required to prevent too many allocations.
+ *
+ * @param current_capacity the current capacity of the array
+ * @param needed_capacity the required capacity of the array
+ * @param maximum_capacity the maximum capacity (given by the data type)
+ * @return the new capacity
+ */
+static size_t cx_array_grow_capacity(
+    size_t current_capacity,
+    size_t needed_capacity,
+    size_t maximum_capacity
 ) {
-    if (cap > max - alignment) {
-        return cap;
+    if (current_capacity >= needed_capacity) {
+        return current_capacity;
+    }
+    size_t cap = needed_capacity;
+    size_t alignment;
+    if (cap < 128) alignment = 16;
+    else if (cap < 1024) alignment = 64;
+    else if (cap < 8192) alignment = 512;
+    else alignment = 1024;
+
+    if (cap - 1 > maximum_capacity - alignment) {
+        return maximum_capacity;
     } else {
         return cap - (cap % alignment) + alignment;
     }
@@ -177,10 +195,6 @@
 
     // reallocate if possible
     if (newcap > oldcap) {
-        // calculate new capacity (next number divisible by 16)
-        newcap = cx_array_align_capacity(newcap, 16, max_size);
-
-        // perform reallocation
         void *newmem = reallocator->realloc(
                 *array, oldcap, newcap, elem_size, reallocator
         );
@@ -270,22 +284,18 @@
     }
 
     // check if resize is required
-    size_t minsize = index + elem_count;
-    size_t newsize = oldsize < minsize ? minsize : oldsize;
+    const size_t minsize = index + elem_count;
+    const size_t newsize = oldsize < minsize ? minsize : oldsize;
 
-    // reallocate if possible
-    size_t newcap = oldcap;
-    if (newsize > oldcap) {
-        // check, if we need to repair the src pointer
+    // reallocate if necessary
+    const size_t newcap = cx_array_grow_capacity(oldcap, newsize, max_size);
+    if (newcap > oldcap) {
+        // check if we need to repair the src pointer
         uintptr_t targetaddr = (uintptr_t) *target;
         uintptr_t srcaddr = (uintptr_t) src;
         bool repairsrc = targetaddr <= srcaddr
                          && srcaddr < targetaddr + oldcap * elem_size;
 
-        // calculate new capacity (next number divisible by 16)
-        newcap = cx_array_align_capacity(newsize, 16, max_size);
-        assert(newcap > newsize);
-
         // perform reallocation
         void *newmem = reallocator->realloc(
                 *target, oldcap, newcap, elem_size, reallocator
@@ -368,16 +378,16 @@
     }
 
     // store some counts
-    size_t old_size = *size;
-    size_t old_capacity = *capacity;
+    const size_t old_size = *size;
+    const size_t old_capacity = *capacity;
     // the necessary capacity is the worst case assumption, including duplicates
-    size_t needed_capacity = old_size + elem_count;
+    const size_t needed_capacity = cx_array_grow_capacity(old_capacity,
+        old_size + elem_count, SIZE_MAX);
 
     // if we need more than we have, try a reallocation
     if (needed_capacity > old_capacity) {
-        size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX);
         void *new_mem = reallocator->realloc(
-                *target, old_capacity, new_capacity, elem_size, reallocator
+                *target, old_capacity, needed_capacity, elem_size, reallocator
         );
         if (new_mem == NULL) {
             // give it up right away, there is no contract
@@ -385,7 +395,7 @@
             return 1;  // LCOV_EXCL_LINE
         }
         *target = new_mem;
-        *capacity = new_capacity;
+        *capacity = needed_capacity;
     }
 
     // now we have guaranteed that we can insert everything
@@ -782,8 +792,8 @@
 
     // guarantee enough capacity
     if (arl->capacity < list->collection.size + n) {
-        size_t new_capacity = list->collection.size + n;
-        new_capacity = new_capacity - (new_capacity % 16) + 16;
+        const size_t new_capacity = cx_array_grow_capacity(arl->capacity,
+            list->collection.size + n, SIZE_MAX);
         if (cxReallocateArray(
                 list->collection.allocator,
                 &arl->data, new_capacity,
@@ -881,7 +891,7 @@
         const void *elem,
         int prepend
 ) {
-    struct cx_list_s *list = iter->src_handle.m;
+    struct cx_list_s *list = iter->src_handle;
     if (iter->index < list->collection.size) {
         if (cx_arl_insert_element(list,
                 iter->index + 1 - prepend, elem) == NULL) {
@@ -1092,7 +1102,7 @@
 
 static bool cx_arl_iter_valid(const void *it) {
     const struct cx_iterator_s *iter = it;
-    const struct cx_list_s *list = iter->src_handle.c;
+    const struct cx_list_s *list = iter->src_handle;
     return iter->index < list->collection.size;
 }
 
@@ -1105,31 +1115,39 @@
     struct cx_iterator_s *iter = it;
     if (iter->base.remove) {
         iter->base.remove = false;
-        cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
+        cx_arl_remove(iter->src_handle, iter->index, 1, NULL);
         iter->elem_count--;
     } else {
         iter->index++;
         iter->elem_handle =
                 ((char *) iter->elem_handle)
-                + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size;
+                + ((const struct cx_list_s *) iter->src_handle)->collection.elem_size;
     }
 }
 
 static void cx_arl_iter_prev(void *it) {
     struct cx_iterator_s *iter = it;
-    const cx_array_list *list = iter->src_handle.c;
     if (iter->base.remove) {
         iter->base.remove = false;
-        cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
+        cx_arl_remove(iter->src_handle, iter->index, 1, NULL);
         iter->elem_count--;
     }
     iter->index--;
+    cx_array_list *list = iter->src_handle;
     if (iter->index < list->base.collection.size) {
         iter->elem_handle = ((char *) list->data)
                             + iter->index * list->base.collection.elem_size;
     }
 }
 
+static int cx_arl_change_capacity(
+        struct cx_list_s *list,
+        size_t new_capacity
+) {
+    cx_array_list *arl = (cx_array_list *)list;
+    return cxReallocateArray(list->collection.allocator,
+        &arl->data, new_capacity, list->collection.elem_size);
+}
 
 static struct cx_iterator_s cx_arl_iterator(
         const struct cx_list_s *list,
@@ -1139,7 +1157,7 @@
     struct cx_iterator_s iter;
 
     iter.index = index;
-    iter.src_handle.c = list;
+    iter.src_handle = (void*)list;
     iter.elem_handle = cx_arl_at(list, index);
     iter.elem_size = list->collection.elem_size;
     iter.elem_count = list->collection.size;
@@ -1147,7 +1165,7 @@
     iter.base.current = cx_arl_iter_current;
     iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
     iter.base.remove = false;
-    iter.base.mutating = false;
+    iter.base.allow_remove = true;
 
     return iter;
 }
@@ -1167,6 +1185,7 @@
         cx_arl_sort,
         cx_arl_compare,
         cx_arl_reverse,
+        cx_arl_change_capacity,
         cx_arl_iterator,
 };
 
--- a/ucx/cx/allocator.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/allocator.h	Mon Nov 10 21:52:51 2025 +0100
@@ -46,36 +46,22 @@
     /**
      * The allocator's malloc() implementation.
      */
-    void *(*malloc)(
-            void *data,
-            size_t n
-    );
+    void *(*malloc)(void *data, size_t n);
 
     /**
      * The allocator's realloc() implementation.
      */
-    void *(*realloc)(
-            void *data,
-            void *mem,
-            size_t n
-    );
+    void *(*realloc)(void *data, void *mem, size_t n);
 
     /**
      * The allocator's calloc() implementation.
      */
-    void *(*calloc)(
-            void *data,
-            size_t nmemb,
-            size_t size
-    );
+    void *(*calloc)(void *data, size_t nmemb, size_t size);
 
     /**
      * The allocator's free() implementation.
      */
-    void (*free)(
-            void *data,
-            void *mem
-    );
+    void (*free)(void *data, void *mem);
 } cx_allocator_class;
 
 /**
@@ -100,15 +86,13 @@
 /**
  * A pre-defined allocator using standard library malloc() etc.
  */
-cx_attr_export
-extern const CxAllocator * const cxStdlibAllocator;
+CX_EXPORT extern const CxAllocator * const cxStdlibAllocator;
 
 /**
  * The default allocator that is used by UCX.
  * Initialized with cxStdlibAllocator, but you may change it.
  */
-cx_attr_export
-extern const CxAllocator * cxDefaultAllocator;
+CX_EXPORT extern const CxAllocator * cxDefaultAllocator;
 
 /**
  * Function pointer type for destructor functions.
@@ -133,10 +117,33 @@
  * @param data an optional pointer to custom data
  * @param memory a pointer to the object to destruct
   */
-typedef void (*cx_destructor_func2)(
-        void *data,
-        void *memory
-);
+typedef void (*cx_destructor_func2)(void *data, void *memory);
+
+
+/**
+ * Function pointer type for clone functions.
+ *
+ * A clone function is supposed to create a deep copy of the memory pointed to
+ * by the @p source pointer.
+ * If the @p target pointer is non-null, the clone function is supposed to store
+ * the copy into that memory region.
+ * Otherwise, the clone function shall use the specified @p allocator to create
+ * a new object.
+ *
+ * The return value of a clone function is always a pointer to the target
+ * memory region, or @c NULL if any allocation failed.
+ * A clone function SHOULD NOT fail for any other reason than an allocation
+ * failure.
+ *
+ * @param target the target memory or @c NULL, if memory shall be allocated
+ * @param source the source memory
+ * @param allocator the allocator that shall be used
+ * @param data optional additional data
+ * @return either the specified @p target, a pointer to the allocated memory,
+ * or @c NULL, if any error occurred
+ */
+typedef void*(cx_clone_func)(void *target, const void *source,
+                             const CxAllocator *allocator, void *data);
 
 /**
  * Reallocate a previously allocated block and changes the pointer in-place,
@@ -153,13 +160,8 @@
  * @retval non-zero failure
  * @see cx_reallocatearray()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_reallocate_(
-        void **mem,
-        size_t n
-);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_reallocate_(void **mem, size_t n);
 
 /**
  * Reallocate a previously allocated block and changes the pointer in-place,
@@ -180,14 +182,8 @@
  * @retval non-zero failure
  * @see cx_reallocate()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_reallocatearray_(
-        void **mem,
-        size_t nmemb,
-        size_t size
-);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_reallocatearray_(void **mem, size_t nmemb, size_t size);
 
 /**
  * Reallocate a previously allocated block and changes the pointer in-place,
@@ -244,11 +240,7 @@
  * @param mem a pointer to the block to free
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-void cxFree(
-        const CxAllocator *allocator,
-        void *mem
-);
+CX_EXPORT void cxFree(const CxAllocator *allocator, void *mem);
 
 /**
  * Allocate @p n bytes of memory.
@@ -257,16 +249,9 @@
  * @param n the number of bytes
  * @return a pointer to the allocated memory
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_malloc
-cx_attr_dealloc_ucx
-cx_attr_allocsize(2)
-cx_attr_export
-void *cxMalloc(
-        const CxAllocator *allocator,
-        size_t n
-);
+cx_attr_nodiscard cx_attr_nonnull
+cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2)
+CX_EXPORT void *cxMalloc(const CxAllocator *allocator, size_t n);
 
 /**
  * Reallocate the previously allocated block in @p mem, making the new block
@@ -281,16 +266,9 @@
  * @param n the new size in bytes
  * @return a pointer to the reallocated memory
  */
-cx_attr_nodiscard
-cx_attr_nonnull_arg(1)
-cx_attr_dealloc_ucx
-cx_attr_allocsize(3)
-cx_attr_export
-void *cxRealloc(
-        const CxAllocator *allocator,
-        void *mem,
-        size_t n
-);
+cx_attr_nodiscard cx_attr_nonnull_arg(1)
+cx_attr_dealloc_ucx cx_attr_allocsize(3)
+CX_EXPORT void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n);
 
 /**
  * Reallocate the previously allocated block in @p mem, making the new block
@@ -310,17 +288,10 @@
  * @param size the size of each element
  * @return a pointer to the reallocated memory
  */
-cx_attr_nodiscard
-cx_attr_nonnull_arg(1)
-cx_attr_dealloc_ucx
-cx_attr_allocsize(3, 4)
-cx_attr_export
-void *cxReallocArray(
-        const CxAllocator *allocator,
-        void *mem,
-        size_t nmemb,
-        size_t size
-);
+cx_attr_nodiscard cx_attr_nonnull_arg(1)
+cx_attr_dealloc_ucx cx_attr_allocsize(3, 4)
+CX_EXPORT void *cxReallocArray(const CxAllocator *allocator,
+        void *mem, size_t nmemb, size_t size);
 
 /**
  * Reallocate a previously allocated block and changes the pointer in-place,
@@ -338,14 +309,8 @@
  * @retval zero success
  * @retval non-zero failure
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-int cxReallocate_(
-        const CxAllocator *allocator,
-        void **mem,
-        size_t n
-);
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT int cxReallocate_(const CxAllocator *allocator, void **mem, size_t n);
 
 /**
  * Reallocate a previously allocated block and changes the pointer in-place,
@@ -385,15 +350,9 @@
  * @retval zero success
  * @retval non-zero on failure
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-int cxReallocateArray_(
-        const CxAllocator *allocator,
-        void **mem,
-        size_t nmemb,
-        size_t size
-);
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT int cxReallocateArray_(const CxAllocator *allocator,
+        void **mem, size_t nmemb, size_t size);
 
 /**
  * Reallocate a previously allocated block and changes the pointer in-place,
@@ -425,17 +384,9 @@
  * @param size the size of each element in bytes
  * @return a pointer to the allocated memory
  */
-cx_attr_nonnull_arg(1)
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc_ucx
-cx_attr_allocsize(2, 3)
-cx_attr_export
-void *cxCalloc(
-        const CxAllocator *allocator,
-        size_t nmemb,
-        size_t size
-);
+cx_attr_nonnull_arg(1) cx_attr_nodiscard
+cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2, 3)
+CX_EXPORT void *cxCalloc(const CxAllocator *allocator, size_t nmemb, size_t size);
 
 /**
  * Allocate @p n bytes of memory and sets every byte to zero.
@@ -444,16 +395,9 @@
  * @param n the number of bytes
  * @return a pointer to the allocated memory
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_malloc
-cx_attr_dealloc_ucx
-cx_attr_allocsize(2)
-cx_attr_export
-void *cxZalloc(
-        const CxAllocator *allocator,
-        size_t n
-);
+cx_attr_nodiscard cx_attr_nonnull
+cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2)
+CX_EXPORT void *cxZalloc(const CxAllocator *allocator, size_t n);
 
 /**
  * Convenience macro that invokes cxMalloc() with the cxDefaultAllocator.
--- a/ucx/cx/array_list.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/array_list.h	Mon Nov 10 21:52:51 2025 +0100
@@ -47,8 +47,7 @@
  * The maximum item size in an array list that fits into
  * a stack buffer when swapped.
  */
-cx_attr_export
-extern const unsigned cx_array_swap_sbo_size;
+CX_EXPORT extern const unsigned cx_array_swap_sbo_size;
 
 /**
  * Declares variables for an array that can be used with the convenience macros.
@@ -172,10 +171,9 @@
      * Reallocates space for the given array.
      *
      * Implementations are not required to free the original array.
-     * This allows reallocation of static memory by allocating heap memory
-     * and copying the array contents. The information in the custom fields of
-     * the referenced allocator can be used to track the state of the memory
-     * or to transport other additional data.
+     * This allows reallocation of static or stack memory by allocating heap memory
+     * and copying the array contents; namely when @c stack_ptr in this struct
+     * is not @c NULL and @p array equals @c stack_ptr.
      *
      * @param array the array to reallocate
      * @param old_capacity the old number of elements
@@ -184,33 +182,18 @@
      * @param alloc a reference to this allocator
      * @return a pointer to the reallocated memory or @c NULL on failure
      */
-    cx_attr_nodiscard
-    cx_attr_nonnull_arg(5)
-    cx_attr_allocsize(3, 4)
-    void *(*realloc)(
-            void *array,
-            size_t old_capacity,
-            size_t new_capacity,
-            size_t elem_size,
-            struct cx_array_reallocator_s *alloc
-    );
+    void *(*realloc)( void *array, size_t old_capacity, size_t new_capacity,
+            size_t elem_size, struct cx_array_reallocator_s *alloc);
 
     /**
-     * Custom data pointer.
+     * The allocator that shall be used for the reallocations.
      */
-    void *ptr1;
-    /**
-     * Custom data pointer.
-     */
-    void *ptr2;
+    const CxAllocator *allocator;
     /**
-     * Custom data integer.
+     * Optional pointer to stack memory
+     * if the array is originally located on the stack.
      */
-    size_t int1;
-    /**
-     * Custom data integer.
-     */
-    size_t int2;
+    const void *stack_ptr;
 };
 
 /**
@@ -221,32 +204,28 @@
 /**
  * A default array reallocator that is based on the cxDefaultAllocator.
  */
-cx_attr_export
-extern CxArrayReallocator *cx_array_default_reallocator;
+CX_EXPORT extern CxArrayReallocator *cx_array_default_reallocator;
 
 /**
  * Creates a new array reallocator.
  *
  * When @p allocator is @c NULL, the cxDefaultAllocator will be used.
  *
- * When @p stackmem is not @c NULL, the reallocator is supposed to be used
- * @em only for the specific array initially located at @p stackmem.
+ * When @p stack_ptr is not @c NULL, the reallocator is supposed to be used
+ * @em only for the specific array initially located at @p stack_ptr.
  * When reallocation is needed, the reallocator checks if the array is
- * still located at @p stackmem and copies the contents to the heap.
+ * still located at @p stack_ptr and copies the contents to the heap.
  *
  * @note Invoking this function with both arguments being @c NULL will return a
  * reallocator that behaves like #cx_array_default_reallocator.
  *
  * @param allocator the allocator this reallocator shall be based on
- * @param stackmem the address of the array when the array is initially located
+ * @param stack_ptr the address of the array when the array is initially located
  * on the stack or shall not reallocate in place
  * @return an array reallocator
  */
-cx_attr_export
-CxArrayReallocator cx_array_reallocator(
-        const struct cx_allocator_s *allocator,
-        const void *stackmem
-);
+CX_EXPORT CxArrayReallocator cx_array_reallocator(
+        const struct cx_allocator_s *allocator, const void *stack_ptr);
 
 /**
  * Reserves memory for additional elements.
@@ -266,6 +245,9 @@
  * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit
  * architecture. If set to zero, the native word width is used.
  *
+ * @note This function will reserve the minimum required capacity to hold
+ * the additional elements and does not perform an overallocation.
+ *
  * @param array a pointer to the target array
  * @param size a pointer to the size of the array
  * @param capacity a pointer to the capacity of the array
@@ -279,16 +261,9 @@
  * @see cx_array_reallocator()
  */
 cx_attr_nonnull_arg(1, 2, 3)
-cx_attr_export
-int cx_array_reserve(
-        void **array,
-        void *size,
-        void *capacity,
-        unsigned width,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-);
+CX_EXPORT int cx_array_reserve(void **array, void *size, void *capacity,
+        unsigned width, size_t elem_size, size_t elem_count,
+        CxArrayReallocator *reallocator);
 
 /**
  * Copies elements from one array to another.
@@ -308,6 +283,9 @@
  * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit
  * architecture. If set to zero, the native word width is used.
  *
+ * @note When this function does reallocate the array, it may allocate more
+ * space than required to avoid further allocations in the near future.
+ *
  * @param target a pointer to the target array
  * @param size a pointer to the size of the target array
  * @param capacity a pointer to the capacity of the target array
@@ -321,20 +299,12 @@
  * @retval zero success
  * @retval non-zero failure
  * @see cx_array_reallocator()
+ * @see cx_array_reserve()
  */
 cx_attr_nonnull_arg(1, 2, 3, 6)
-cx_attr_export
-int cx_array_copy(
-        void **target,
-        void *size,
-        void *capacity,
-        unsigned width,
-        size_t index,
-        const void *src,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-);
+CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width,
+        size_t index, const void *src, size_t elem_size, size_t elem_count,
+        CxArrayReallocator *reallocator);
 
 /**
  * Convenience macro that uses cx_array_copy() with a default layout and
@@ -482,17 +452,9 @@
  * @retval non-zero failure
  */
 cx_attr_nonnull_arg(1, 2, 3, 5)
-cx_attr_export
-int cx_array_insert_sorted(
-        void **target,
-        size_t *size,
-        size_t *capacity,
-        cx_compare_func cmp_func,
-        const void *src,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-);
+CX_EXPORT int cx_array_insert_sorted(void **target, size_t *size, size_t *capacity,
+        cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count,
+        CxArrayReallocator *reallocator);
 
 /**
  * Inserts an element into a sorted array.
@@ -500,7 +462,7 @@
  * If the target array is not already sorted with respect
  * to the specified @p cmp_func, the behavior is undefined.
  *
- * If the capacity is insufficient to hold the new data, a reallocation
+ * If the capacity is not enough to hold the new data, a reallocation
  * attempt is made.
  *
  * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
@@ -611,17 +573,9 @@
  * @retval non-zero failure
  */
 cx_attr_nonnull_arg(1, 2, 3, 5)
-cx_attr_export
-int cx_array_insert_unique(
-        void **target,
-        size_t *size,
-        size_t *capacity,
-        cx_compare_func cmp_func,
-        const void *src,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-);
+CX_EXPORT int cx_array_insert_unique(void **target, size_t *size, size_t *capacity,
+        cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count,
+        CxArrayReallocator *reallocator);
 
 /**
  * Inserts an element into a sorted array if it does not exist.
@@ -738,14 +692,8 @@
  * @see cx_array_binary_search()
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cx_array_binary_search_inf(
-        const void *arr,
-        size_t size,
-        size_t elem_size,
-        const void *elem,
-        cx_compare_func cmp_func
-);
+CX_EXPORT size_t cx_array_binary_search_inf(const void *arr, size_t size,
+        size_t elem_size, const void *elem, cx_compare_func cmp_func);
 
 /**
  * Searches an item in a sorted array.
@@ -764,14 +712,8 @@
  * @see cx_array_binary_search_sup()
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cx_array_binary_search(
-        const void *arr,
-        size_t size,
-        size_t elem_size,
-        const void *elem,
-        cx_compare_func cmp_func
-);
+CX_EXPORT size_t cx_array_binary_search(const void *arr, size_t size,
+        size_t elem_size, const void *elem, cx_compare_func cmp_func);
 
 /**
  * Searches the smallest upper bound in a sorted array.
@@ -796,14 +738,8 @@
  * @see cx_array_binary_search()
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cx_array_binary_search_sup(
-        const void *arr,
-        size_t size,
-        size_t elem_size,
-        const void *elem,
-        cx_compare_func cmp_func
-);
+CX_EXPORT size_t cx_array_binary_search_sup(const void *arr, size_t size,
+        size_t elem_size, const void *elem, cx_compare_func cmp_func);
 
 /**
  * Swaps two array elements.
@@ -814,13 +750,7 @@
  * @param idx2 index of the second element
  */
 cx_attr_nonnull
-cx_attr_export
-void cx_array_swap(
-        void *arr,
-        size_t elem_size,
-        size_t idx1,
-        size_t idx2
-);
+CX_EXPORT void cx_array_swap(void *arr, size_t elem_size, size_t idx1, size_t idx2);
 
 /**
  * Allocates an array list for storing elements with @p elem_size bytes each.
@@ -841,13 +771,8 @@
 cx_attr_nodiscard
 cx_attr_malloc
 cx_attr_dealloc(cxListFree, 1)
-cx_attr_export
-CxList *cxArrayListCreate(
-        const CxAllocator *allocator,
-        cx_compare_func comparator,
-        size_t elem_size,
-        size_t initial_capacity
-);
+CX_EXPORT CxList *cxArrayListCreate(const CxAllocator *allocator,
+        cx_compare_func comparator, size_t elem_size, size_t initial_capacity);
 
 /**
  * Allocates an array list for storing elements with @p elem_size bytes each.
--- a/ucx/cx/buffer.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/buffer.h	Mon Nov 10 21:52:51 2025 +0100
@@ -227,14 +227,8 @@
  * @return zero on success, non-zero if a required allocation failed
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-int cxBufferInit(
-        CxBuffer *buffer,
-        void *space,
-        size_t capacity,
-        const CxAllocator *allocator,
-        int flags
-);
+CX_EXPORT int cxBufferInit(CxBuffer *buffer, void *space, size_t capacity,
+        const CxAllocator *allocator, int flags);
 
 /**
  * Configures the buffer for flushing.
@@ -251,11 +245,7 @@
  * @see cxBufferWrite()
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferEnableFlushing(
-    CxBuffer *buffer,
-    CxBufferFlushConfig config
-);
+CX_EXPORT int cxBufferEnableFlushing(CxBuffer *buffer, CxBufferFlushConfig config);
 
 /**
  * Destroys the buffer contents.
@@ -267,8 +257,7 @@
  * @see cxBufferInit()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxBufferDestroy(CxBuffer *buffer);
+CX_EXPORT void cxBufferDestroy(CxBuffer *buffer);
 
 /**
  * Deallocates the buffer.
@@ -279,8 +268,7 @@
  * @param buffer the buffer to deallocate
  * @see cxBufferCreate()
  */
-cx_attr_export
-void cxBufferFree(CxBuffer *buffer);
+CX_EXPORT void cxBufferFree(CxBuffer *buffer);
 
 /**
  * Allocates and initializes a fresh buffer.
@@ -306,16 +294,9 @@
  * @param flags buffer features (see cx_buffer_s.flags)
  * @return a pointer to the buffer on success, @c NULL if a required allocation failed
  */
-cx_attr_malloc
-cx_attr_dealloc(cxBufferFree, 1)
-cx_attr_nodiscard
-cx_attr_export
-CxBuffer *cxBufferCreate(
-        void *space,
-        size_t capacity,
-        const CxAllocator *allocator,
-        int flags
-);
+cx_attr_malloc cx_attr_dealloc(cxBufferFree, 1) cx_attr_nodiscard
+CX_EXPORT CxBuffer *cxBufferCreate(void *space, size_t capacity,
+        const CxAllocator *allocator, int flags);
 
 /**
  * Shifts the contents of the buffer by the given offset.
@@ -354,11 +335,7 @@
  * @see cxBufferShiftRight()
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferShift(
-        CxBuffer *buffer,
-        off_t shift
-);
+CX_EXPORT int cxBufferShift(CxBuffer *buffer, off_t shift);
 
 /**
  * Shifts the buffer to the right.
@@ -371,11 +348,7 @@
  * @see cxBufferShift()
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferShiftRight(
-        CxBuffer *buffer,
-        size_t shift
-);
+CX_EXPORT int cxBufferShiftRight(CxBuffer *buffer, size_t shift);
 
 /**
  * Shifts the buffer to the left.
@@ -388,11 +361,7 @@
  * @see cxBufferShift()
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferShiftLeft(
-        CxBuffer *buffer,
-        size_t shift
-);
+CX_EXPORT int cxBufferShiftLeft(CxBuffer *buffer, size_t shift);
 
 
 /**
@@ -416,12 +385,7 @@
  *
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferSeek(
-        CxBuffer *buffer,
-        off_t offset,
-        int whence
-);
+CX_EXPORT int cxBufferSeek(CxBuffer *buffer, off_t offset, int whence);
 
 /**
  * Clears the buffer by resetting the position and deleting the data.
@@ -436,8 +400,7 @@
  * @see cxBufferReset()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxBufferClear(CxBuffer *buffer);
+CX_EXPORT void cxBufferClear(CxBuffer *buffer);
 
 /**
  * Resets the buffer by resetting the position and size to zero.
@@ -449,8 +412,7 @@
  * @see cxBufferClear()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxBufferReset(CxBuffer *buffer);
+CX_EXPORT void cxBufferReset(CxBuffer *buffer);
 
 /**
  * Tests, if the buffer position has exceeded the buffer size.
@@ -460,10 +422,8 @@
  * byte of the buffer's contents
  * @retval false otherwise
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-bool cxBufferEof(const CxBuffer *buffer);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT bool cxBufferEof(const CxBuffer *buffer);
 
 
 /**
@@ -481,11 +441,7 @@
  * @see cxBufferShrink()
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferMinimumCapacity(
-        CxBuffer *buffer,
-        size_t capacity
-);
+CX_EXPORT int cxBufferMinimumCapacity(CxBuffer *buffer, size_t capacity);
 
 /**
  * Shrinks the capacity of the buffer to fit its current size.
@@ -504,11 +460,7 @@
  * @see cxBufferMinimumCapacity()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxBufferShrink(
-        CxBuffer *buffer,
-        size_t reserve
-);
+CX_EXPORT void cxBufferShrink(CxBuffer *buffer, size_t reserve);
 
 /**
  * Writes data to a CxBuffer.
@@ -552,13 +504,8 @@
  * @see cxBufferRead()
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cxBufferWrite(
-        const void *ptr,
-        size_t size,
-        size_t nitems,
-        CxBuffer *buffer
-);
+CX_EXPORT size_t cxBufferWrite(const void *ptr, size_t size,
+        size_t nitems, CxBuffer *buffer);
 
 /**
  * Appends data to a CxBuffer.
@@ -580,13 +527,8 @@
  * @see cxBufferRead()
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cxBufferAppend(
-        const void *ptr,
-        size_t size,
-        size_t nitems,
-        CxBuffer *buffer
-);
+CX_EXPORT size_t cxBufferAppend(const void *ptr, size_t size,
+        size_t nitems, CxBuffer *buffer);
 
 /**
  * Performs a single flush-run on the specified buffer.
@@ -642,8 +584,7 @@
  * @see cxBufferEnableFlushing()
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cxBufferFlush(CxBuffer *buffer);
+CX_EXPORT size_t cxBufferFlush(CxBuffer *buffer);
 
 /**
  * Reads data from a CxBuffer.
@@ -661,13 +602,8 @@
  * @see cxBufferAppend()
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cxBufferRead(
-        void *ptr,
-        size_t size,
-        size_t nitems,
-        CxBuffer *buffer
-);
+CX_EXPORT size_t cxBufferRead(void *ptr, size_t size,
+        size_t nitems, CxBuffer *buffer);
 
 /**
  * Writes a character to a buffer.
@@ -689,11 +625,7 @@
  * @see cxBufferTerminate()
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferPut(
-        CxBuffer *buffer,
-        int c
-);
+CX_EXPORT int cxBufferPut(CxBuffer *buffer, int c);
 
 /**
  * Writes a terminating zero to a buffer at the current position.
@@ -707,8 +639,7 @@
  * @return zero, if the terminator could be written, non-zero otherwise
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferTerminate(CxBuffer *buffer);
+CX_EXPORT int cxBufferTerminate(CxBuffer *buffer);
 
 /**
  * Writes a string to a buffer.
@@ -719,13 +650,8 @@
  * @param str the zero-terminated string
  * @return the number of bytes written
  */
-cx_attr_nonnull
-cx_attr_cstr_arg(2)
-cx_attr_export
-size_t cxBufferPutString(
-        CxBuffer *buffer,
-        const char *str
-);
+cx_attr_nonnull cx_attr_cstr_arg(2)
+CX_EXPORT size_t cxBufferPutString(CxBuffer *buffer, const char *str);
 
 /**
  * Gets a character from a buffer.
@@ -736,8 +662,7 @@
  * @return the character or @c EOF, if the end of the buffer is reached
  */
 cx_attr_nonnull
-cx_attr_export
-int cxBufferGet(CxBuffer *buffer);
+CX_EXPORT int cxBufferGet(CxBuffer *buffer);
 
 #ifdef __cplusplus
 }
--- a/ucx/cx/collection.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/collection.h	Mon Nov 10 21:52:51 2025 +0100
@@ -151,7 +151,15 @@
  * @retval true if the elements are currently sorted wrt. the collection's compare function
  * @retval false if the order of elements is unknown
  */
-#define cxCollectionSorted(c) ((c)->collection.sorted)
+#define cxCollectionSorted(c) ((c)->collection.sorted || (c)->collection.size == 0)
+
+/**
+ * Sets the compare function for a collection.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param func (@c cx_compare_func) the compare function that shall be used by @c c
+ */
+#define cxCollectionCompareFunc(c, func) (c)->collection.cmpfunc = (func)
 
 /**
  * Sets a simple destructor function for this collection.
--- a/ucx/cx/common.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/common.h	Mon Nov 10 21:52:51 2025 +0100
@@ -265,15 +265,32 @@
 #define _Thread_local __declspec(thread)
 #endif // _MSC_VER
 
+// ---------------------------------------------------------------------------
+//       Exported and inlined functions
+// ---------------------------------------------------------------------------
+
 #if defined(CX_WINDLL_EXPORT)
-#define cx_attr_export __declspec(dllexport)
+#define CX_EXPORT __declspec(dllexport)
 #elif defined(CX_WINDLL)
-#define cx_attr_export __declspec(dllimport)
+#define CX_EXPORT __declspec(dllimport)
 #else
 /** Only used for building Windows DLLs. */
-#define cx_attr_export
+#define CX_EXPORT
 #endif // CX_WINDLL / CX_WINDLL_EXPORT
 
+#ifdef __GNUC__
+/**
+ * Declares a function to be inlined.
+ */
+#define CX_INLINE __attribute__((always_inline)) static inline
+#else
+#define CX_INLINE static inline
+#endif
+/**
+ * Declares a compatibility function for C++ builds.
+ */
+#define CX_CPPDECL static inline
+
 // ---------------------------------------------------------------------------
 //       Useful function pointers
 // ---------------------------------------------------------------------------
@@ -281,22 +298,12 @@
 /**
  * Function pointer compatible with fwrite-like functions.
  */
-typedef size_t (*cx_write_func)(
-        const void *,
-        size_t,
-        size_t,
-        void *
-);
+typedef size_t (*cx_write_func)(const void*, size_t, size_t, void*);
 
 /**
  * Function pointer compatible with fread-like functions.
  */
-typedef size_t (*cx_read_func)(
-        void *,
-        size_t,
-        size_t,
-        void *
-);
+typedef size_t (*cx_read_func)(void*, size_t, size_t, void*);
 
 // ---------------------------------------------------------------------------
 //       Utility macros
@@ -348,9 +355,7 @@
 #if __cplusplus
 extern "C"
 #endif
-cx_attr_export int cx_szmul_impl(size_t a, size_t b, size_t *result);
+CX_EXPORT int cx_szmul_impl(size_t a, size_t b, size_t *result);
 #endif // cx_szmul
 
-
-
 #endif // UCX_COMMON_H
--- a/ucx/cx/common.h.orig	Sun Oct 19 21:20:08 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-/**
- * \file common.h
- *
- * \brief Common definitions and feature checks.
- *
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
- *
- * \mainpage UAP Common Extensions
- * Library with common and useful functions, macros and data structures.
- * <p>
- * Latest available source:<br>
- * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
- * </p>
- *
- * <p>
- * Repositories:<br>
- * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
- * -&nbsp;or&nbsp;-
- * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
- * </p>
- *
- * <h2>LICENCE</h2>
- *
- * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef UCX_COMMON_H
-#define UCX_COMMON_H
-
-/** Major UCX version as integer constant. */
-#define UCX_VERSION_MAJOR   3
-
-/** Minor UCX version as integer constant. */
-#define UCX_VERSION_MINOR   0
-
-/** Version constant which ensures to increase monotonically. */
-#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
-
-#define __attribute__(...) 
-
-#include <stdlib.h>
-#include <stddef.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-/**
- * Function pointer compatible with fwrite-like functions.
- */
-typedef size_t (*cx_write_func)(
-        void const *,
-        size_t,
-        size_t,
-        void *
-);
-
-/**
- * Function pointer compatible with fread-like functions.
- */
-typedef size_t (*cx_read_func)(
-        void *,
-        size_t,
-        size_t,
-        void *
-);
-
-#ifdef _WIN32
-
-#ifdef __MINGW32__
-#include <sys/types.h>
-#endif // __MINGW32__
-
-#else // !_WIN32
-
-#include <sys/types.h>
-
-#endif // _WIN32
-
-#ifndef __GNUC__
-/**
- * Removes GNU C attributes where they are not supported.
- */
-#define __attribute__(x)
-#endif
-
-#endif // UCX_COMMON_H
--- a/ucx/cx/compare.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/compare.h	Mon Nov 10 21:52:51 2025 +0100
@@ -54,13 +54,7 @@
  * can be used, but they are NOT compatible with this function
  * pointer.
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-typedef int (*cx_compare_func)(
-    const void *left,
-    const void *right
-);
+typedef int (*cx_compare_func)(const void *left, const void *right);
 
 /**
  * Compares two integers of type int.
@@ -74,10 +68,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_int(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_int(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type int.
@@ -89,8 +81,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_int(int i1, int i2);
+CX_EXPORT int cx_vcmp_int(int i1, int i2);
 
 /**
  * Compares two integers of type long int.
@@ -104,10 +95,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_longint(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_longint(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type long int.
@@ -119,8 +108,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_longint(long int i1, long int i2);
+CX_EXPORT int cx_vcmp_longint(long int i1, long int i2);
 
 /**
  * Compares two integers of type long long.
@@ -134,10 +122,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_longlong(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_longlong(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type long long.
@@ -149,8 +135,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_longlong(long long int i1, long long int i2);
+CX_EXPORT int cx_vcmp_longlong(long long int i1, long long int i2);
 
 /**
  * Compares two integers of type int16_t.
@@ -164,10 +149,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_int16(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_int16(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type int16_t.
@@ -179,8 +162,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_int16(int16_t i1, int16_t i2);
+CX_EXPORT int cx_vcmp_int16(int16_t i1, int16_t i2);
 
 /**
  * Compares two integers of type int32_t.
@@ -194,10 +176,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_int32(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_int32(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type int32_t.
@@ -209,8 +189,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_int32(int32_t i1, int32_t i2);
+CX_EXPORT int cx_vcmp_int32(int32_t i1, int32_t i2);
 
 /**
  * Compares two integers of type int64_t.
@@ -224,10 +203,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_int64(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_int64(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type int64_t.
@@ -239,8 +216,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_int64(int64_t i1, int64_t i2);
+CX_EXPORT int cx_vcmp_int64(int64_t i1, int64_t i2);
 
 /**
  * Compares two integers of type unsigned int.
@@ -254,10 +230,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_uint(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_uint(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type unsigned int.
@@ -269,8 +243,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_uint(unsigned int i1, unsigned int i2);
+CX_EXPORT int cx_vcmp_uint(unsigned int i1, unsigned int i2);
 
 /**
  * Compares two integers of type unsigned long int.
@@ -284,10 +257,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_ulongint(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_ulongint(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type unsigned long int.
@@ -299,8 +270,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2);
+CX_EXPORT int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2);
 
 /**
  * Compares two integers of type unsigned long long.
@@ -314,10 +284,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_ulonglong(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_ulonglong(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type unsigned long long.
@@ -329,8 +297,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2);
+CX_EXPORT int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2);
 
 /**
  * Compares two integers of type uint16_t.
@@ -344,10 +311,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_uint16(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_uint16(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type uint16_t.
@@ -359,8 +324,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_uint16(uint16_t i1, uint16_t i2);
+CX_EXPORT int cx_vcmp_uint16(uint16_t i1, uint16_t i2);
 
 /**
  * Compares two integers of type uint32_t.
@@ -374,10 +338,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_uint32(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_uint32(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type uint32_t.
@@ -389,8 +351,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_uint32(uint32_t i1, uint32_t i2);
+CX_EXPORT int cx_vcmp_uint32(uint32_t i1, uint32_t i2);
 
 /**
  * Compares two integers of type uint64_t.
@@ -404,10 +365,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_uint64(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_uint64(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type uint64_t.
@@ -419,8 +378,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_uint64(uint64_t i1, uint64_t i2);
+CX_EXPORT int cx_vcmp_uint64(uint64_t i1, uint64_t i2);
 
 /**
  * Compares two integers of type size_t.
@@ -434,10 +392,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_size(const void *i1, const void *i2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_size(const void *i1, const void *i2);
 
 /**
  * Compares two integers of type size_t.
@@ -449,8 +405,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_size(size_t i1, size_t i2);
+CX_EXPORT int cx_vcmp_size(size_t i1, size_t i2);
 
 /**
  * Compares two real numbers of type float with precision 1e-6f.
@@ -464,10 +419,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_float(const void *f1, const void *f2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_float(const void *f1, const void *f2);
 
 /**
  * Compares two real numbers of type float with precision 1e-6f.
@@ -479,8 +432,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_float(float f1, float f2);
+CX_EXPORT int cx_vcmp_float(float f1, float f2);
 
 /**
  * Compares two real numbers of type double with precision 1e-14.
@@ -494,10 +446,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_double(const void *d1, const void *d2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_double(const void *d1, const void *d2);
 
 /**
  * Compares two real numbers of type double with precision 1e-14.
@@ -509,8 +459,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_double(double d1, double d2);
+CX_EXPORT int cx_vcmp_double(double d1, double d2);
 
 /**
  * Compares the integer representation of two pointers.
@@ -524,10 +473,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_intptr(const void *ptr1, const void *ptr2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_intptr(const void *ptr1, const void *ptr2);
 
 /**
  * Compares the integer representation of two pointers.
@@ -539,8 +486,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2);
+CX_EXPORT int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2);
 
 /**
  * Compares the unsigned integer representation of two pointers.
@@ -554,10 +500,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_uintptr(const void *ptr1, const void *ptr2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_uintptr(const void *ptr1, const void *ptr2);
 
 /**
  * Compares the unsigned integer representation of two pointers.
@@ -569,8 +513,7 @@
  * @retval 1 if the left argument is greater than the right argument
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2);
+CX_EXPORT int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2);
 
 /**
  * Compares the pointers specified in the arguments without dereferencing.
@@ -581,10 +524,8 @@
  * @retval 0 if both arguments are equal
  * @retval 1 if the left argument is greater than the right argument
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cx_cmp_ptr(const void *ptr1, const void *ptr2);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_cmp_ptr(const void *ptr1, const void *ptr2);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/hash_key.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/hash_key.h	Mon Nov 10 21:52:51 2025 +0100
@@ -79,8 +79,7 @@
  * @see cx_hash_key()
  */
 cx_attr_nonnull
-cx_attr_export
-void cx_hash_murmur(CxHashKey *key);
+CX_EXPORT void cx_hash_murmur(CxHashKey *key);
 
 /**
  * Mixes up a 32-bit integer to be used as a hash.
@@ -90,8 +89,7 @@
  * @param x the integer
  * @return the hash
  */
-cx_attr_export
-uint32_t cx_hash_u32(uint32_t x);
+CX_EXPORT uint32_t cx_hash_u32(uint32_t x);
 
 /**
  * Mixes up a 64-bit integer to be used as a hash.
@@ -101,8 +99,7 @@
  * @param x the integer
  * @return the hash
  */
-cx_attr_export
-uint64_t cx_hash_u64(uint64_t x);
+CX_EXPORT uint64_t cx_hash_u64(uint64_t x);
 
 /**
  * Computes a hash key from a 32-bit integer.
@@ -111,8 +108,7 @@
  * @return the hash key
  */
 cx_attr_nodiscard
-cx_attr_export
-CxHashKey cx_hash_key_u32(uint32_t x);
+CX_EXPORT CxHashKey cx_hash_key_u32(uint32_t x);
 
 /**
  * Computes a hash key from a 64-bit integer.
@@ -121,8 +117,7 @@
  * @return the hash key
  */
 cx_attr_nodiscard
-cx_attr_export
-CxHashKey cx_hash_key_u64(uint64_t x);
+CX_EXPORT CxHashKey cx_hash_key_u64(uint64_t x);
 
 /**
  * Computes a hash key from a string.
@@ -132,10 +127,8 @@
  * @param str the string
  * @return the hash key
  */
-cx_attr_nodiscard
-cx_attr_cstr_arg(1)
-cx_attr_export
-CxHashKey cx_hash_key_str(const char *str);
+cx_attr_nodiscard cx_attr_cstr_arg(1)
+CX_EXPORT CxHashKey cx_hash_key_str(const char *str);
 
 /**
  * Computes a hash key from a string.
@@ -148,12 +141,8 @@
  * @param str the string
  * @return the hash key
  */
-cx_attr_nodiscard
-cx_attr_cstr_arg(1)
-cx_attr_export
-static inline CxHashKey cx_hash_key_ustr(const unsigned char *str) {
-    return cx_hash_key_str((const char*)str);
-}
+cx_attr_nodiscard cx_attr_cstr_arg(1)
+CX_EXPORT CxHashKey cx_hash_key_ustr(const unsigned char *str);
 
 /**
  * Computes a hash key from a byte array.
@@ -162,13 +151,8 @@
  * @param len the length
  * @return the hash key
  */
-cx_attr_nodiscard
-cx_attr_access_r(1, 2)
-cx_attr_export
-CxHashKey cx_hash_key_bytes(
-        const unsigned char *bytes,
-        size_t len
-);
+cx_attr_nodiscard cx_attr_access_r(1, 2)
+CX_EXPORT CxHashKey cx_hash_key_bytes(const unsigned char *bytes, size_t len);
 
 /**
  * Computes a hash key for an arbitrary object.
@@ -183,11 +167,7 @@
  */
 cx_attr_nodiscard
 cx_attr_access_r(1, 2)
-cx_attr_export
-CxHashKey cx_hash_key(
-        const void *obj,
-        size_t len
-);
+CX_EXPORT CxHashKey cx_hash_key(const void *obj, size_t len);
 
 /**
  * Computes a hash key from a UCX string.
@@ -196,9 +176,7 @@
  * @return the hash key
  */
 cx_attr_nodiscard
-static inline CxHashKey cx_hash_key_cxstr(cxstring str) {
-    return cx_hash_key(str.ptr, str.length);
-}
+CX_EXPORT CxHashKey cx_hash_key_cxstr(cxstring str);
 
 /**
  * Computes a hash key from a UCX string.
@@ -207,9 +185,7 @@
  * @return the hash key
  */
 cx_attr_nodiscard
-static inline CxHashKey cx_hash_key_mutstr(cxmutstr str) {
-    return cx_hash_key(str.ptr, str.length);
-}
+CX_EXPORT CxHashKey cx_hash_key_mutstr(cxmutstr str);
 
 /**
  * The identity function for the CX_HASH_KEY() macro.
@@ -219,7 +195,7 @@
  * @return a copy of the key
  */
 cx_attr_nodiscard
-static inline CxHashKey cx_hash_key_identity(CxHashKey key) {
+CX_INLINE CxHashKey cx_hash_key_identity(CxHashKey key) {
     return key;
 }
 
@@ -249,25 +225,16 @@
 #endif // __cplusplus
 
 /**
- * Computes a hash key from a UCX string.
- * Convenience macro that accepts both cxstring and cxmutstr.
- * @deprecated use the CX_HASH_KEY() macro instead
- * @param str (@c cxstring or @c cxmutstr) the string
- * @return (@c CxHashKey) the hash key
- */
-#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str))
-
-/**
  * Compare function for hash keys.
  *
- * @param left the first key
- * @param right the second key
+ * The pointers are untyped to be compatible with the cx_compare_func signature.
+ *
+ * @param left (@c CxHashKey*) the first key
+ * @param right (@c CxHashKey*) the second key
  * @return zero when the keys equal, non-zero when they differ
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right);
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT int cx_hash_key_cmp(const void *left, const void *right);
 
 #ifdef __cplusplus
 } // extern "C"
@@ -276,31 +243,31 @@
 // Overloads of CX_HASH_KEY (the C++ version of a _Generic)
 // ----------------------------------------------------------
 
-static inline CxHashKey CX_HASH_KEY(CxHashKey key) {
+CX_CPPDECL CxHashKey CX_HASH_KEY(CxHashKey key) {
     return key;
 }
 
-static inline CxHashKey CX_HASH_KEY(cxstring str) {
+CX_CPPDECL CxHashKey CX_HASH_KEY(cxstring str) {
     return cx_hash_key_cxstr(str);
 }
 
-static inline CxHashKey CX_HASH_KEY(cxmutstr str) {
+CX_CPPDECL CxHashKey CX_HASH_KEY(cxmutstr str) {
     return cx_hash_key_mutstr(str);
 }
 
-static inline CxHashKey CX_HASH_KEY(const char *str) {
+CX_CPPDECL CxHashKey CX_HASH_KEY(const char *str) {
     return cx_hash_key_str(str);
 }
 
-static inline CxHashKey CX_HASH_KEY(const unsigned char *str) {
+CX_CPPDECL CxHashKey CX_HASH_KEY(const unsigned char *str) {
     return cx_hash_key_ustr(str);
 }
 
-static inline CxHashKey CX_HASH_KEY(uint32_t key) {
+CX_CPPDECL CxHashKey CX_HASH_KEY(uint32_t key) {
     return cx_hash_key_u32(key);
 }
 
-static inline CxHashKey CX_HASH_KEY(uint64_t key) {
+CX_CPPDECL CxHashKey CX_HASH_KEY(uint64_t key) {
     return cx_hash_key_u64(key);
 }
 #endif
--- a/ucx/cx/hash_map.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/hash_map.h	Mon Nov 10 21:52:51 2025 +0100
@@ -83,15 +83,9 @@
  * @param buckets the initial number of buckets in this hash map
  * @return a pointer to the new hash map
  */
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc(cxMapFree, 1)
-cx_attr_export
-CxMap *cxHashMapCreate(
-        const CxAllocator *allocator,
-        size_t itemsize,
-        size_t buckets
-);
+cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1)
+CX_EXPORT CxMap *cxHashMapCreate(const CxAllocator *allocator,
+        size_t itemsize, size_t buckets);
 
 /**
  * Creates a new hash map with a default number of buckets.
@@ -129,8 +123,7 @@
  * @retval non-zero if a memory allocation error occurred
  */
 cx_attr_nonnull
-cx_attr_export
-int cxMapRehash(CxMap *map);
+CX_EXPORT int cxMapRehash(CxMap *map);
 
 
 #ifdef __cplusplus
--- a/ucx/cx/iterator.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/iterator.h	Mon Nov 10 21:52:51 2025 +0100
@@ -50,7 +50,10 @@
      * True if the iterator points to valid data.
      */
     bool (*valid)(const void *);
-
+    /**
+     * Original implementation in case the function needs to be wrapped.
+     */
+    bool (*valid_impl)(const void *);
     /**
      * Returns a pointer to the current element.
      *
@@ -62,7 +65,6 @@
      * Original implementation in case the function needs to be wrapped.
      */
     void *(*current_impl)(const void *);
-
     /**
      * Advances the iterator.
      *
@@ -76,7 +78,7 @@
     /**
      * Indicates whether this iterator may remove elements.
      */
-    bool mutating;
+    bool allow_remove;
     /**
      * Internal flag for removing the current element when advancing.
      */
@@ -112,16 +114,7 @@
     /**
      * Handle for the source collection, if any.
      */
-    union {
-        /**
-         * Access for mutating iterators.
-         */
-        void *m;
-        /**
-         * Access for normal iterators.
-         */
-        const void *c;
-    } src_handle;
+    void *src_handle;
 
     /**
      * If the iterator is position-aware, contains the index of the element in the underlying collection.
@@ -149,7 +142,7 @@
  * to be "position-aware", which means that they keep track of the current index within the collection.
  *
  * @note Objects that are pointed to by an iterator are always mutable through that iterator. However,
- * any concurrent mutation of the collection other than by this iterator makes this iterator invalid,
+ * any concurrent mutation of the collection other than by this iterator makes this iterator obsolete,
  * and it must not be used anymore.
  */
 typedef struct cx_iterator_s CxIterator;
@@ -182,13 +175,12 @@
 #define cxIteratorNext(iter) (iter).base.next(&iter)
 
 /**
- * Flags the current element for removal if this iterator is mutating.
- *
- * Does nothing for non-mutating iterators.
+ * Flags the current element for removal if the iterator allows it.
  *
  * @param iter the iterator
+ * @return @c true if removal is allowed, @c false otherwise
  */
-#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating
+#define cxIteratorFlagRemoval(iter) ((iter).base.remove = (iter).base.allow_remove)
 
 /**
  * Obtains a reference to an arbitrary iterator.
@@ -222,22 +214,34 @@
  * use cxIteratorPtr() to create an iterator which directly
  * yields the stored pointers.
  *
+ * While the iterator is in use, the array may only be altered by removing
+ * elements through #cxIteratorFlagRemoval(). Every other change to the array
+ * will bring this iterator to an undefined state.
+ *
+ * When @p remove_keeps_order is set to @c false, removing an element will only
+ * move the last element to the position of the removed element, instead of
+ * moving all subsequent elements by one. Usually, when the order of elements is
+ * not important, this parameter should be set to @c false.
+ *
  * @param array a pointer to the array (can be @c NULL)
  * @param elem_size the size of one array element
  * @param elem_count the number of elements in the array
+ * @param remove_keeps_order @c true if the order of elements must be preserved
+ * when removing an element
  * @return an iterator for the specified array
  * @see cxIteratorPtr()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxIterator cxIterator(
-        const void *array,
-        size_t elem_size,
-        size_t elem_count
-);
+CX_EXPORT CxIterator cxIterator(const void *array,
+        size_t elem_size, size_t elem_count, bool remove_keeps_order);
 
 /**
- * Creates a mutating iterator for the specified plain array.
+ * Creates an iterator for the specified plain pointer array.
+ *
+ * This iterator assumes that every element in the array is a pointer
+ * and yields exactly those pointers during iteration (on the other
+ * hand, an iterator created with cxIterator() would return the
+ * addresses of those pointers within the array).
  *
  * While the iterator is in use, the array may only be altered by removing
  * elements through #cxIteratorFlagRemoval(). Every other change to the array
@@ -248,67 +252,16 @@
  * moving all subsequent elements by one. Usually, when the order of elements is
  * not important, this parameter should be set to @c false.
  *
- * The @p array can be @c NULL, in which case the iterator will be immediately
- * initialized such that #cxIteratorValid() returns @c false.
- *
- *
- * @param array a pointer to the array (can be @c NULL)
- * @param elem_size the size of one array element
- * @param elem_count the number of elements in the array
- * @param remove_keeps_order @c true if the order of elements must be preserved
- * when removing an element
- * @return an iterator for the specified array
- */
-cx_attr_nodiscard
-cx_attr_export
-CxIterator cxMutIterator(
-        void *array,
-        size_t elem_size,
-        size_t elem_count,
-        bool remove_keeps_order
-);
-
-/**
- * Creates an iterator for the specified plain pointer array.
- *
- * This iterator assumes that every element in the array is a pointer
- * and yields exactly those pointers during iteration (on the other
- * hand, an iterator created with cxIterator() would return the
- * addresses of those pointers within the array).
- *
- * @param array a pointer to the array (can be @c NULL)
- * @param elem_count the number of elements in the array
- * @return an iterator for the specified array
- * @see cxIterator()
- */
-cx_attr_nodiscard
-cx_attr_export
-CxIterator cxIteratorPtr(
-        const void *array,
-        size_t elem_count
-);
-
-/**
- * Creates a mutating iterator for the specified plain pointer array.
- *
- * This is the mutating variant of cxIteratorPtr(). See also
- * cxMutIterator().
- *
  * @param array a pointer to the array (can be @c NULL)
  * @param elem_count the number of elements in the array
  * @param remove_keeps_order @c true if the order of elements must be preserved
  * when removing an element
  * @return an iterator for the specified array
- * @see cxMutIterator()
- * @see cxIteratorPtr()
+ * @see cxIterator()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxIterator cxMutIteratorPtr(
-        void *array,
-        size_t elem_count,
-        bool remove_keeps_order
-);
+CX_EXPORT CxIterator cxIteratorPtr(const void *array, size_t elem_count,
+        bool remove_keeps_order);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/json.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/json.h	Mon Nov 10 21:52:51 2025 +0100
@@ -475,8 +475,7 @@
  * @return new JSON writer settings
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonWriter cxJsonWriterCompact(void);
+CX_EXPORT CxJsonWriter cxJsonWriterCompact(void);
 
 /**
  * Creates a default writer configuration for pretty output.
@@ -485,8 +484,7 @@
  * @return new JSON writer settings
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonWriter cxJsonWriterPretty(bool use_spaces);
+CX_EXPORT CxJsonWriter cxJsonWriterPretty(bool use_spaces);
 
 /**
  * Writes a JSON value to a buffer or stream.
@@ -507,13 +505,8 @@
  * @retval non-zero when no or not all data could be written
  */
 cx_attr_nonnull_arg(1, 2, 3)
-cx_attr_export
-int cxJsonWrite(
-    void* target,
-    const CxJsonValue* value,
-    cx_write_func wfunc,
-    const CxJsonWriter* settings
-);
+CX_EXPORT int cxJsonWrite(void* target, const CxJsonValue* value,
+        cx_write_func wfunc, const CxJsonWriter* settings);
 
 /**
  * Initializes the JSON interface.
@@ -523,8 +516,7 @@
  * @see cxJsonDestroy()
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-void cxJsonInit(CxJson *json, const CxAllocator *allocator);
+CX_EXPORT void cxJsonInit(CxJson *json, const CxAllocator *allocator);
 
 /**
  * Destroys the JSON interface.
@@ -533,8 +525,7 @@
  * @see cxJsonInit()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxJsonDestroy(CxJson *json);
+CX_EXPORT void cxJsonDestroy(CxJson *json);
 
 /**
  * Destroys and re-initializes the JSON interface.
@@ -545,11 +536,7 @@
  * @param json the JSON interface
  */
 cx_attr_nonnull
-static inline void cxJsonReset(CxJson *json) {
-    const CxAllocator *allocator = json->allocator;
-    cxJsonDestroy(json);
-    cxJsonInit(json, allocator);
-}
+CX_EXPORT void cxJsonReset(CxJson *json);
 
 /**
  * Fills the input buffer.
@@ -569,10 +556,8 @@
  * @retval non-zero internal allocation error
  * @see cxJsonFill()
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxJsonFilln(CxJson *json, const char *buf, size_t len);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxJsonFilln(CxJson *json, const char *buf, size_t len);
 
 
 /**
@@ -584,7 +569,7 @@
  * @retval non-zero internal allocation error
  */
 cx_attr_nonnull
-static inline int cx_json_fill(CxJson *json, cxstring str) {
+CX_INLINE int cx_json_fill(CxJson *json, cxstring str) {
     return cxJsonFilln(json, str.ptr, str.length);
 }
 
@@ -616,8 +601,7 @@
  * @see cxJsonArrAddValues()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator);
+CX_EXPORT CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator);
 
 /**
  * Creates a new (empty) JSON array.
@@ -628,8 +612,7 @@
  * @see cxJsonArrAddValues()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator);
+CX_EXPORT CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator);
 
 /**
  * Creates a new JSON number value.
@@ -641,8 +624,7 @@
  * @see cxJsonArrAddNumbers()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num);
+CX_EXPORT CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num);
 
 /**
  * Creates a new JSON number value based on an integer.
@@ -654,8 +636,7 @@
  * @see cxJsonArrAddIntegers()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num);
+CX_EXPORT CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num);
 
 /**
  * Creates a new JSON string.
@@ -667,11 +648,8 @@
  * @see cxJsonObjPutString()
  * @see cxJsonArrAddStrings()
  */
-cx_attr_nodiscard
-cx_attr_nonnull_arg(2)
-cx_attr_cstr_arg(2)
-cx_attr_export
-CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str);
+cx_attr_nodiscard cx_attr_nonnull_arg(2) cx_attr_cstr_arg(2)
+CX_EXPORT CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str);
 
 /**
  * Creates a new JSON string.
@@ -684,8 +662,7 @@
  * @see cxJsonArrAddCxStrings()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str);
+CX_EXPORT CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str);
 
 /**
  * Creates a new JSON literal.
@@ -697,8 +674,7 @@
  * @see cxJsonArrAddLiterals()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit);
+CX_EXPORT CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit);
 
 /**
  * Adds number values to a JSON array.
@@ -709,10 +685,8 @@
  * @retval zero success
  * @retval non-zero allocation failure
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count);
 
 /**
  * Adds number values, of which all are integers, to a JSON array.
@@ -723,10 +697,8 @@
  * @retval zero success
  * @retval non-zero allocation failure
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count);
 
 /**
  * Adds strings to a JSON array.
@@ -740,10 +712,8 @@
  * @retval non-zero allocation failure
  * @see cxJsonArrAddCxStrings()
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count);
 
 /**
  * Adds strings to a JSON array.
@@ -757,10 +727,8 @@
  * @retval non-zero allocation failure
  * @see cxJsonArrAddStrings()
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count);
 
 /**
  * Adds literals to a JSON array.
@@ -771,10 +739,8 @@
  * @retval zero success
  * @retval non-zero allocation failure
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count);
 
 /**
  * Add arbitrary values to a JSON array.
@@ -788,10 +754,8 @@
  * @retval zero success
  * @retval non-zero allocation failure
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count);
 
 /**
  * Adds or replaces a value within a JSON object.
@@ -808,8 +772,7 @@
  * @retval non-zero allocation failure
  */
 cx_attr_nonnull
-cx_attr_export
-int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child);
+CX_EXPORT int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child);
 
 /**
  * Creates a new JSON object and adds it to an existing object.
@@ -821,8 +784,7 @@
  * @see cxJsonCreateObj()
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name);
+CX_EXPORT CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name);
 
 /**
  * Creates a new JSON array and adds it to an object.
@@ -834,8 +796,7 @@
  * @see cxJsonCreateArr()
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name);
+CX_EXPORT CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name);
 
 /**
  * Creates a new JSON number and adds it to an object.
@@ -848,8 +809,7 @@
  * @see cxJsonCreateNumber()
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num);
+CX_EXPORT CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num);
 
 /**
  * Creates a new JSON number, based on an integer, and adds it to an object.
@@ -862,8 +822,7 @@
  * @see cxJsonCreateInteger()
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num);
+CX_EXPORT CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num);
 
 /**
  * Creates a new JSON string and adds it to an object.
@@ -877,10 +836,8 @@
  * @see cxJsonObjPut()
  * @see cxJsonCreateString()
  */
-cx_attr_nonnull
-cx_attr_cstr_arg(3)
-cx_attr_export
-CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str);
+cx_attr_nonnull cx_attr_cstr_arg(3)
+CX_EXPORT CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str);
 
 /**
  * Creates a new JSON string and adds it to an object.
@@ -895,8 +852,7 @@
  * @see cxJsonCreateCxString()
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str);
+CX_EXPORT CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str);
 
 /**
  * Creates a new JSON literal and adds it to an object.
@@ -909,8 +865,7 @@
  * @see cxJsonCreateLiteral()
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit);
+CX_EXPORT CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit);
 
 /**
  * Recursively deallocates the memory of a JSON value.
@@ -923,8 +878,7 @@
  *
  * @param value the value
  */
-cx_attr_export
-void cxJsonValueFree(CxJsonValue *value);
+CX_EXPORT void cxJsonValueFree(CxJsonValue *value);
 
 /**
  * Tries to obtain the next JSON value.
@@ -948,10 +902,8 @@
  * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number
  * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error
  */
-cx_attr_nonnull
-cx_attr_access_w(2)
-cx_attr_export
-CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value);
+cx_attr_nonnull cx_attr_access_w(2)
+CX_EXPORT CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value);
 
 /**
  * Checks if the specified value is a JSON object.
@@ -961,7 +913,7 @@
  * @retval false otherwise
  */
 cx_attr_nonnull
-static inline bool cxJsonIsObject(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsObject(const CxJsonValue *value) {
     return value->type == CX_JSON_OBJECT;
 }
 
@@ -973,7 +925,7 @@
  * @retval false otherwise
  */
 cx_attr_nonnull
-static inline bool cxJsonIsArray(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsArray(const CxJsonValue *value) {
     return value->type == CX_JSON_ARRAY;
 }
 
@@ -985,7 +937,7 @@
  * @retval false otherwise
  */
 cx_attr_nonnull
-static inline bool cxJsonIsString(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsString(const CxJsonValue *value) {
     return value->type == CX_JSON_STRING;
 }
 
@@ -1001,7 +953,7 @@
  * @see cxJsonIsInteger()
  */
 cx_attr_nonnull
-static inline bool cxJsonIsNumber(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsNumber(const CxJsonValue *value) {
     return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER;
 }
 
@@ -1014,7 +966,7 @@
  * @see cxJsonIsNumber()
  */
 cx_attr_nonnull
-static inline bool cxJsonIsInteger(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsInteger(const CxJsonValue *value) {
     return value->type == CX_JSON_INTEGER;
 }
 
@@ -1031,7 +983,7 @@
  * @see cxJsonIsNull()
  */
 cx_attr_nonnull
-static inline bool cxJsonIsLiteral(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsLiteral(const CxJsonValue *value) {
     return value->type == CX_JSON_LITERAL;
 }
 
@@ -1045,7 +997,7 @@
  * @see cxJsonIsFalse()
  */
 cx_attr_nonnull
-static inline bool cxJsonIsBool(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) {
     return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL;
 }
 
@@ -1062,7 +1014,7 @@
  * @see cxJsonIsFalse()
  */
 cx_attr_nonnull
-static inline bool cxJsonIsTrue(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) {
     return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE;
 }
 
@@ -1079,7 +1031,7 @@
  * @see cxJsonIsTrue()
  */
 cx_attr_nonnull
-static inline bool cxJsonIsFalse(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) {
     return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE;
 }
 
@@ -1092,7 +1044,7 @@
  * @see cxJsonIsLiteral()
  */
 cx_attr_nonnull
-static inline bool cxJsonIsNull(const CxJsonValue *value) {
+CX_INLINE bool cxJsonIsNull(const CxJsonValue *value) {
     return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL;
 }
 
@@ -1105,11 +1057,8 @@
  * @return the value represented as C string
  * @see cxJsonIsString()
  */
-cx_attr_nonnull
-cx_attr_returns_nonnull
-static inline char *cxJsonAsString(const CxJsonValue *value) {
-    return value->value.string.ptr;
-}
+cx_attr_nonnull  cx_attr_returns_nonnull
+CX_EXPORT char *cxJsonAsString(const CxJsonValue *value);
 
 /**
  * Obtains a UCX string from the given JSON value.
@@ -1121,9 +1070,7 @@
  * @see cxJsonIsString()
  */
 cx_attr_nonnull
-static inline cxstring cxJsonAsCxString(const CxJsonValue *value) {
-    return cx_strcast(value->value.string);
-}
+CX_EXPORT cxstring cxJsonAsCxString(const CxJsonValue *value);
 
 /**
  * Obtains a mutable UCX string from the given JSON value.
@@ -1135,9 +1082,7 @@
  * @see cxJsonIsString()
  */
 cx_attr_nonnull
-static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) {
-    return value->value.string;
-}
+CX_EXPORT cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value);
 
 /**
  * Obtains a double-precision floating-point value from the given JSON value.
@@ -1149,13 +1094,7 @@
  * @see cxJsonIsNumber()
  */
 cx_attr_nonnull
-static inline double cxJsonAsDouble(const CxJsonValue *value) {
-    if (value->type == CX_JSON_INTEGER) {
-        return (double) value->value.integer;
-    } else {
-        return value->value.number;
-    }
-}
+CX_EXPORT double cxJsonAsDouble(const CxJsonValue *value);
 
 /**
  * Obtains a 64-bit signed integer from the given JSON value.
@@ -1170,13 +1109,7 @@
  * @see cxJsonIsInteger()
  */
 cx_attr_nonnull
-static inline int64_t cxJsonAsInteger(const CxJsonValue *value) {
-    if (value->type == CX_JSON_INTEGER) {
-        return value->value.integer;
-    } else {
-        return (int64_t) value->value.number;
-    }
-}
+CX_EXPORT int64_t cxJsonAsInteger(const CxJsonValue *value);
 
 /**
  * Obtains a Boolean value from the given JSON value.
@@ -1189,7 +1122,7 @@
  * @see cxJsonIsLiteral()
  */
 cx_attr_nonnull
-static inline bool cxJsonAsBool(const CxJsonValue *value) {
+CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) {
     return value->value.literal == CX_JSON_TRUE;
 }
 
@@ -1203,7 +1136,7 @@
  * @see cxJsonIsArray()
  */
 cx_attr_nonnull
-static inline size_t cxJsonArrSize(const CxJsonValue *value) {
+CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) {
     return value->value.array.array_size;
 }
 
@@ -1221,10 +1154,8 @@
  * @return the value at the specified index
  * @see cxJsonIsArray()
  */
-cx_attr_nonnull
-cx_attr_returns_nonnull
-cx_attr_export
-CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index);
+cx_attr_nonnull cx_attr_returns_nonnull
+CX_EXPORT CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index);
 
 /**
  * Removes an element from a JSON array.
@@ -1240,8 +1171,7 @@
  * @see cxJsonIsArray()
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index);
+CX_EXPORT CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index);
 
 /**
  * Returns an iterator over the JSON array elements.
@@ -1254,10 +1184,8 @@
  * @return an iterator over the array elements
  * @see cxJsonIsArray()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-CxIterator cxJsonArrIter(const CxJsonValue *value);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value);
 
 /**
  * Returns an iterator over the JSON object members.
@@ -1271,10 +1199,8 @@
  * @return an iterator over the object members
  * @see cxJsonIsObject()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-CxIterator cxJsonObjIter(const CxJsonValue *value);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT CxIterator cxJsonObjIter(const CxJsonValue *value);
 
 /**
  * Internal function, do not use.
@@ -1282,10 +1208,8 @@
  * @param name the key to look up
  * @return the value corresponding to the key
  */
-cx_attr_nonnull
-cx_attr_returns_nonnull
-cx_attr_export
-CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name);
+cx_attr_nonnull cx_attr_returns_nonnull
+CX_EXPORT CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name);
 
 /**
  * Returns a value corresponding to a key in a JSON object.
@@ -1310,8 +1234,7 @@
  * @return the value corresponding to the key or @c NULL when the key is not part of the object
  */
 cx_attr_nonnull
-cx_attr_export
-CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name);
+CX_EXPORT CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name);
 
 /**
  * Removes and returns a value corresponding to a key in a JSON object.
--- a/ucx/cx/kv_list.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/kv_list.h	Mon Nov 10 21:52:51 2025 +0100
@@ -66,15 +66,9 @@
  * @see cxKvListAsMap()
  * @see cxKvListAsList()
  */
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc(cxListFree, 1)
-cx_attr_export
-CxList *cxKvListCreate(
-        const CxAllocator *allocator,
-        cx_compare_func comparator,
-        size_t elem_size
-);
+cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1)
+CX_EXPORT CxList *cxKvListCreate(const CxAllocator *allocator,
+        cx_compare_func comparator, size_t elem_size);
 
 /**
  * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each.
@@ -97,15 +91,9 @@
  * @see cxKvListAsMap()
  * @see cxKvListAsList()
  */
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc(cxMapFree, 1)
-cx_attr_export
-CxMap *cxKvListCreateAsMap(
-        const CxAllocator *allocator,
-        cx_compare_func comparator,
-        size_t elem_size
-);
+cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1)
+CX_EXPORT CxMap *cxKvListCreateAsMap(const CxAllocator *allocator,
+        cx_compare_func comparator, size_t elem_size);
 
 /**
  * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each.
@@ -158,11 +146,8 @@
  * @param map a map pointer that was returned by a call to cxKvListAsMap()
  * @return the original list pointer
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_returns_nonnull
-cx_attr_export
-CxList *cxKvListAsList(CxMap *map);
+cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull
+CX_EXPORT CxList *cxKvListAsList(CxMap *map);
 
 /**
  * Converts a map pointer belonging to a key-value-List back to the original list pointer.
@@ -170,11 +155,8 @@
  * @param list a list created by cxKvListCreate() or cxKvListCreateSimple()
  * @return a map pointer that lets you use the list as if it was a map
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_returns_nonnull
-cx_attr_export
-CxMap *cxKvListAsMap(CxList *list);
+cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull
+CX_EXPORT CxMap *cxKvListAsMap(CxList *list);
 
 /**
  * Sets or updates the key of a list item.
@@ -190,8 +172,7 @@
  * @see cxKvListSetKey()
  */
 cx_attr_nonnull
-cx_attr_export
-int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key);
+CX_EXPORT int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key);
 
 /**
  * Inserts an item into the list at the specified index and associates it with the specified key.
@@ -205,8 +186,7 @@
  * @see cxKvListInsert()
  */
 cx_attr_nonnull
-cx_attr_export
-int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value);
+CX_EXPORT int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value);
 
 /**
  * Sets or updates the key of a list item.
@@ -250,8 +230,7 @@
  * @retval non-zero the index is out of bounds
  */
 cx_attr_nonnull
-cx_attr_export
-int cxKvListRemoveKey(CxList *list, size_t index);
+CX_EXPORT int cxKvListRemoveKey(CxList *list, size_t index);
 
 /**
  * Returns the key of a list item.
@@ -261,8 +240,7 @@
  * @return a pointer to the key or @c NULL when the index is out of bounds or the item does not have a key
  */
 cx_attr_nonnull
-cx_attr_export
-const CxHashKey *cxKvListGetKey(CxList *list, size_t index);
+CX_EXPORT const CxHashKey *cxKvListGetKey(CxList *list, size_t index);
 
 /**
  * Adds an item into the list and associates it with the specified key.
--- a/ucx/cx/linked_list.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/linked_list.h	Mon Nov 10 21:52:51 2025 +0100
@@ -90,15 +90,9 @@
  * @param elem_size the size of each element in bytes
  * @return the created list
  */
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc(cxListFree, 1)
-cx_attr_export
-CxList *cxLinkedListCreate(
-        const CxAllocator *allocator,
-        cx_compare_func comparator,
-        size_t elem_size
-);
+cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1)
+CX_EXPORT CxList *cxLinkedListCreate(const CxAllocator *allocator,
+        cx_compare_func comparator, size_t elem_size);
 
 /**
  * Allocates a linked list for storing elements with @p elem_size bytes each.
@@ -115,7 +109,7 @@
  * @return (@c CxList*) the created list
  */
 #define cxLinkedListCreateSimple(elem_size) \
-    cxLinkedListCreate(NULL, NULL, elem_size)
+        cxLinkedListCreate(NULL, NULL, elem_size)
 
 /**
  * Finds the node at a certain index.
@@ -134,15 +128,9 @@
  * @param index the search index
  * @return the node found at the specified index
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-void *cx_linked_list_at(
-        const void *start,
-        size_t start_index,
-        ptrdiff_t loc_advance,
-        size_t index
-);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT void *cx_linked_list_at(const void *start,size_t start_index,
+        ptrdiff_t loc_advance, size_t index);
 
 /**
  * Finds the node containing an element within a linked list.
@@ -157,15 +145,9 @@
  * @return a pointer to the found node or @c NULL if no matching node was found
  */
 cx_attr_nonnull_arg(1, 4, 5)
-cx_attr_export
-void *cx_linked_list_find(
-        const void *start,
-        ptrdiff_t loc_advance,
-        ptrdiff_t loc_data,
-        cx_compare_func cmp_func,
-        const void *elem,
-        size_t *found_index
-);
+CX_EXPORT void *cx_linked_list_find(const void *start, ptrdiff_t loc_advance,
+        ptrdiff_t loc_data, cx_compare_func cmp_func, const void *elem,
+        size_t *found_index);
 
 /**
  * Finds the first node in a linked list.
@@ -178,13 +160,8 @@
  * @param loc_prev the location of the @c prev pointer
  * @return a pointer to the first node
  */
-cx_attr_nonnull
-cx_attr_returns_nonnull
-cx_attr_export
-void *cx_linked_list_first(
-        const void *node,
-        ptrdiff_t loc_prev
-);
+cx_attr_nonnull cx_attr_returns_nonnull
+CX_EXPORT void *cx_linked_list_first(const void *node, ptrdiff_t loc_prev);
 
 /**
  * Finds the last node in a linked list.
@@ -197,13 +174,8 @@
  * @param loc_next the location of the @c next pointer
  * @return a pointer to the last node
  */
-cx_attr_nonnull
-cx_attr_returns_nonnull
-cx_attr_export
-void *cx_linked_list_last(
-        const void *node,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull cx_attr_returns_nonnull
+CX_EXPORT void *cx_linked_list_last(const void *node, ptrdiff_t loc_next);
 
 /**
  * Finds the predecessor of a node in case it is not linked.
@@ -216,12 +188,7 @@
  * @return the node or @c NULL if @p node has no predecessor
  */
 cx_attr_nonnull
-cx_attr_export
-void *cx_linked_list_prev(
-        const void *begin,
-        ptrdiff_t loc_next,
-        const void *node
-);
+CX_EXPORT void *cx_linked_list_prev(const void *begin, ptrdiff_t loc_next, const void *node);
 
 /**
  * Adds a new node to a linked list.
@@ -236,14 +203,7 @@
  * @param new_node a pointer to the node that shall be appended
  */
 cx_attr_nonnull_arg(5)
-cx_attr_export
-void cx_linked_list_add(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *new_node
-);
+CX_EXPORT void cx_linked_list_add(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node);
 
 /**
  * Prepends a new node to a linked list.
@@ -258,14 +218,7 @@
  * @param new_node a pointer to the node that shall be prepended
  */
 cx_attr_nonnull_arg(5)
-cx_attr_export
-void cx_linked_list_prepend(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *new_node
-);
+CX_EXPORT void cx_linked_list_prepend(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node);
 
 /**
  * Links two nodes.
@@ -276,13 +229,7 @@
  * @param loc_next the location of a @c next pointer within your node struct (required)
  */
 cx_attr_nonnull
-cx_attr_export
-void cx_linked_list_link(
-        void *left,
-        void *right,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+CX_EXPORT void cx_linked_list_link(void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Unlinks two nodes.
@@ -295,13 +242,7 @@
  * @param loc_next the location of a @c next pointer within your node struct (required)
  */
 cx_attr_nonnull
-cx_attr_export
-void cx_linked_list_unlink(
-        void *left,
-        void *right,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+CX_EXPORT void cx_linked_list_unlink(void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Inserts a new node after a given node of a linked list.
@@ -318,15 +259,8 @@
  * @param new_node a pointer to the node that shall be inserted
  */
 cx_attr_nonnull_arg(6)
-cx_attr_export
-void cx_linked_list_insert(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *node,
-        void *new_node
-);
+CX_EXPORT void cx_linked_list_insert(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, void *new_node);
 
 /**
  * Inserts a chain of nodes after a given node of a linked list.
@@ -349,16 +283,8 @@
  * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined)
  */
 cx_attr_nonnull_arg(6)
-cx_attr_export
-void cx_linked_list_insert_chain(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *node,
-        void *insert_begin,
-        void *insert_end
-);
+CX_EXPORT void cx_linked_list_insert_chain(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, void *insert_begin, void *insert_end);
 
 /**
  * Inserts a node into a sorted linked list.
@@ -375,15 +301,8 @@
  * @param cmp_func a compare function that will receive the node pointers
  */
 cx_attr_nonnull_arg(1, 5, 6)
-cx_attr_export
-void cx_linked_list_insert_sorted(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *new_node,
-        cx_compare_func cmp_func
-);
+CX_EXPORT void cx_linked_list_insert_sorted(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func cmp_func);
 
 /**
  * Inserts a chain of nodes into a sorted linked list.
@@ -405,15 +324,8 @@
  * @param cmp_func a compare function that will receive the node pointers
  */
 cx_attr_nonnull_arg(1, 5, 6)
-cx_attr_export
-void cx_linked_list_insert_sorted_chain(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *insert_begin,
-        cx_compare_func cmp_func
-);
+CX_EXPORT void cx_linked_list_insert_sorted_chain(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func);
 
 /**
  * Inserts a node into a sorted linked list if no other node with the same value already exists.
@@ -432,15 +344,8 @@
  * @retval non-zero when a node with the same value already exists
  */
 cx_attr_nonnull_arg(1, 5, 6)
-cx_attr_export
-int cx_linked_list_insert_unique(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *new_node,
-        cx_compare_func cmp_func
-);
+CX_EXPORT int cx_linked_list_insert_unique(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func cmp_func);
 
 /**
  * Inserts a chain of nodes into a sorted linked list, avoiding duplicates.
@@ -460,17 +365,9 @@
  * @param cmp_func a compare function that will receive the node pointers
  * @return a pointer to a new chain with all duplicates that were not inserted (or @c NULL when there were no duplicates)
  */
-cx_attr_nonnull_arg(1, 5, 6)
-cx_attr_nodiscard
-cx_attr_export
-void *cx_linked_list_insert_unique_chain(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *insert_begin,
-        cx_compare_func cmp_func
-);
+cx_attr_nonnull_arg(1, 5, 6) cx_attr_nodiscard
+CX_EXPORT void *cx_linked_list_insert_unique_chain(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func);
 
 /**
  * Removes a chain of nodes from the linked list.
@@ -494,15 +391,8 @@
  * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes)
  */
 cx_attr_nonnull_arg(5)
-cx_attr_export
-size_t cx_linked_list_remove_chain(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *node,
-        size_t num
-);
+CX_EXPORT size_t cx_linked_list_remove_chain(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, size_t num);
 
 /**
  * Removes a node from the linked list.
@@ -524,15 +414,8 @@
  * @param node the node to remove
  */
 cx_attr_nonnull_arg(5)
-static inline void cx_linked_list_remove(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        void *node
-) {
-    cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1);
-}
+CX_EXPORT void cx_linked_list_remove(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node);
 
 /**
  * Determines the size of a linked list starting with @p node.
@@ -542,11 +425,7 @@
  * @return the size of the list or zero if @p node is @c NULL
  */
 cx_attr_nodiscard
-cx_attr_export
-size_t cx_linked_list_size(
-        const void *node,
-        ptrdiff_t loc_next
-);
+CX_EXPORT size_t cx_linked_list_size(const void *node, ptrdiff_t loc_next);
 
 /**
  * Sorts a linked list based on a comparison function.
@@ -571,15 +450,8 @@
  * @param cmp_func the compare function defining the sort order
  */
 cx_attr_nonnull_arg(1, 6)
-cx_attr_export
-void cx_linked_list_sort(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next,
-        ptrdiff_t loc_data,
-        cx_compare_func cmp_func
-);
+CX_EXPORT void cx_linked_list_sort(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func cmp_func);
 
 
 /**
@@ -596,14 +468,8 @@
  * right list, positive if the left list is larger than the right list, zero if both lists are equal.
  */
 cx_attr_nonnull_arg(5)
-cx_attr_export
-int cx_linked_list_compare(
-        const void *begin_left,
-        const void *begin_right,
-        ptrdiff_t loc_advance,
-        ptrdiff_t loc_data,
-        cx_compare_func cmp_func
-);
+CX_EXPORT int cx_linked_list_compare(const void *begin_left, const void *begin_right,
+        ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func);
 
 /**
  * Reverses the order of the nodes in a linked list.
@@ -614,13 +480,7 @@
  * @param loc_next the location of a @c next pointer within your node struct (required)
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-void cx_linked_list_reverse(
-        void **begin,
-        void **end,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+CX_EXPORT void cx_linked_list_reverse(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/list.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/list.h	Mon Nov 10 21:52:51 2025 +0100
@@ -39,8 +39,6 @@
 #include "common.h"
 #include "collection.h"
 
-#include <assert.h>
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -85,54 +83,38 @@
      * The data pointer may be @c NULL, in which case the function shall only allocate memory.
      * Returns a pointer to the allocated memory or @c NULL if allocation fails.
      */
-    void *(*insert_element)(
-            struct cx_list_s *list,
-            size_t index,
-            const void *data
-    );
+    void *(*insert_element)(struct cx_list_s *list, size_t index, const void *data);
 
     /**
      * Member function for inserting multiple elements.
      *
+     * The data pointer may be @c NULL, in which case the function shall only allocate memory.
+     * Returns the number of successfully inserted or allocated elements.
+     *
      * @see cx_list_default_insert_array()
      */
-    size_t (*insert_array)(
-            struct cx_list_s *list,
-            size_t index,
-            const void *data,
-            size_t n
-    );
+    size_t (*insert_array)(struct cx_list_s *list, size_t index, const void *data, size_t n);
 
     /**
      * Member function for inserting sorted elements into a sorted list.
+     * Returns the number of successfully inserted elements.
      *
      * @see cx_list_default_insert_sorted()
      */
-    size_t (*insert_sorted)(
-            struct cx_list_s *list,
-            const void *sorted_data,
-            size_t n
-    );
+    size_t (*insert_sorted)(struct cx_list_s *list, const void *sorted_data, size_t n);
 
     /**
      * Member function for inserting multiple elements if they do not exist.
-     *
+     * Implementations shall return the number of successfully processed elements
+     * (including those which were not added because they are already contained).
      * @see cx_list_default_insert_unique()
      */
-    size_t (*insert_unique)(
-            struct cx_list_s *list,
-            const void *sorted_data,
-            size_t n
-    );
+    size_t (*insert_unique)(struct cx_list_s *list, const void *sorted_data, size_t n);
 
     /**
      * Member function for inserting an element relative to an iterator position.
      */
-    int (*insert_iter)(
-            struct cx_iterator_s *iter,
-            const void *elem,
-            int prepend
-    );
+    int (*insert_iter)(struct cx_iterator_s *iter, const void *elem, int prepend);
 
     /**
      * Member function for removing elements.
@@ -144,12 +126,7 @@
      * The function SHALL return the actual number of elements removed, which
      * might be lower than @p num when going out of bounds.
      */
-    size_t (*remove)(
-            struct cx_list_s *list,
-            size_t index,
-            size_t num,
-            void *targetbuf
-    );
+    size_t (*remove)(struct cx_list_s *list, size_t index, size_t num, void *targetbuf);
 
     /**
      * Member function for removing all elements.
@@ -161,28 +138,17 @@
      *
      * @see cx_list_default_swap()
      */
-    int (*swap)(
-            struct cx_list_s *list,
-            size_t i,
-            size_t j
-    );
+    int (*swap)(struct cx_list_s *list, size_t i, size_t j);
 
     /**
      * Member function for element lookup.
      */
-    void *(*at)(
-            const struct cx_list_s *list,
-            size_t index
-    );
+    void *(*at)(const struct cx_list_s *list, size_t index);
 
     /**
      * Member function for finding and optionally removing an element.
      */
-    size_t (*find_remove)(
-            struct cx_list_s *list,
-            const void *elem,
-            bool remove
-    );
+    size_t (*find_remove)(struct cx_list_s *list, const void *elem, bool remove);
 
     /**
      * Member function for sorting the list.
@@ -196,10 +162,7 @@
      * to another list of the same type.
      * If set to @c NULL, the comparison won't be optimized.
      */
-    int (*compare)(
-            const struct cx_list_s *list,
-            const struct cx_list_s *other
-    );
+    int (*compare)(const struct cx_list_s *list, const struct cx_list_s *other);
 
     /**
      * Member function for reversing the order of the items.
@@ -207,13 +170,15 @@
     void (*reverse)(struct cx_list_s *list);
 
     /**
+     * Optional member function for changing the capacity.
+     * If the list does not support overallocation, this can be set to @c NULL.
+     */
+    int (*change_capacity)(struct cx_list_s *list, size_t new_capacity);
+
+    /**
      * Member function for returning an iterator pointing to the specified index.
      */
-    struct cx_iterator_s (*iterator)(
-            const struct cx_list_s *list,
-            size_t index,
-            bool backward
-    );
+    struct cx_iterator_s (*iterator)(const struct cx_list_s *list, size_t index, bool backward);
 };
 
 /**
@@ -229,8 +194,7 @@
  * You can use this as a placeholder for initializing CxList pointers
  * for which you do not want to reserve memory right from the beginning.
  */
-cx_attr_export
-extern CxList *const cxEmptyList;
+CX_EXPORT extern CxList *const cxEmptyList;
 
 /**
  * Default implementation of an array insert.
@@ -247,13 +211,8 @@
  * @return the number of elements actually inserted
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cx_list_default_insert_array(
-        struct cx_list_s *list,
-        size_t index,
-        const void *data,
-        size_t n
-);
+CX_EXPORT size_t cx_list_default_insert_array(struct cx_list_s *list,
+        size_t index, const void *data, size_t n);
 
 /**
  * Default implementation of a sorted insert.
@@ -272,12 +231,8 @@
  * @return the number of elements actually inserted
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cx_list_default_insert_sorted(
-        struct cx_list_s *list,
-        const void *sorted_data,
-        size_t n
-);
+CX_EXPORT size_t cx_list_default_insert_sorted(struct cx_list_s *list,
+        const void *sorted_data, size_t n);
 
 /**
  * Default implementation of an array insert where only elements are inserted when they don't exist in the list.
@@ -296,12 +251,8 @@
  * @return the number of elements from the @p sorted_data that are definitely present in the list after this call
  */
 cx_attr_nonnull
-cx_attr_export
-size_t cx_list_default_insert_unique(
-        struct cx_list_s *list,
-        const void *sorted_data,
-        size_t n
-);
+CX_EXPORT size_t cx_list_default_insert_unique(struct cx_list_s *list,
+        const void *sorted_data, size_t n);
 
 /**
  * Default unoptimized sort implementation.
@@ -315,8 +266,7 @@
  * @param list the list that shall be sorted
  */
 cx_attr_nonnull
-cx_attr_export
-void cx_list_default_sort(struct cx_list_s *list);
+CX_EXPORT void cx_list_default_sort(struct cx_list_s *list);
 
 /**
  * Default unoptimized swap implementation.
@@ -332,8 +282,7 @@
  * allocation for the temporary buffer fails
  */
 cx_attr_nonnull
-cx_attr_export
-int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
+CX_EXPORT int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
 
 /**
  * Initializes a list struct.
@@ -380,14 +329,9 @@
  * @param elem_size the size of one element
  */
 cx_attr_nonnull_arg(1, 2, 3)
-cx_attr_export
-void cx_list_init(
-    struct cx_list_s *list,
-    struct cx_list_class_s *cl,
-    const struct cx_allocator_s *allocator,
-    cx_compare_func comparator,
-    size_t elem_size
-);
+CX_EXPORT void cx_list_init(struct cx_list_s *list,
+    struct cx_list_class_s *cl, const struct cx_allocator_s *allocator,
+    cx_compare_func comparator, size_t elem_size);
 
 /**
  * Returns the number of elements currently stored in the list.
@@ -396,9 +340,7 @@
  * @return the number of currently stored elements
  */
 cx_attr_nonnull
-static inline size_t cxListSize(const CxList *list) {
-    return list->collection.size;
-}
+CX_EXPORT size_t cxListSize(const CxList *list);
 
 /**
  * Adds an item to the end of the list.
@@ -411,13 +353,7 @@
  * @see cxListEmplace()
  */
 cx_attr_nonnull
-static inline int cxListAdd(
-        CxList *list,
-        const void *elem
-) {
-    list->collection.sorted = false;
-    return list->cl->insert_element(list, list->collection.size, elem) == NULL;
-}
+CX_EXPORT int cxListAdd(CxList *list, const void *elem);
 
 /**
  * Adds multiple items to the end of the list.
@@ -434,16 +370,10 @@
  * @param array a pointer to the elements to add
  * @param n the number of elements to add
  * @return the number of added elements
+ * @see cxListEmplaceArray()
  */
 cx_attr_nonnull
-static inline size_t cxListAddArray(
-        CxList *list,
-        const void *array,
-        size_t n
-) {
-    list->collection.sorted = false;
-    return list->cl->insert_array(list, list->collection.size, array, n);
-}
+CX_EXPORT size_t cxListAddArray(CxList *list, const void *array, size_t n);
 
 /**
  * Inserts an item at the specified index.
@@ -460,14 +390,7 @@
  * @see cxListEmplaceAt()
  */
 cx_attr_nonnull
-static inline int cxListInsert(
-        CxList *list,
-        size_t index,
-        const void *elem
-) {
-    list->collection.sorted = false;
-    return list->cl->insert_element(list, index, elem) == NULL;
-}
+CX_EXPORT int cxListInsert(CxList *list, size_t index, const void *elem);
 
 /**
  * Allocates memory for an element at the specified index and returns a pointer to that memory.
@@ -478,14 +401,11 @@
  * @param index the index where to emplace the element
  * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds
  * @see cxListEmplace()
+ * @see cxListEmplaceArrayAt()
  * @see cxListInsert()
  */
 cx_attr_nonnull
-static inline void *cxListEmplaceAt(CxList *list, size_t index) {
-    list->collection.sorted = false;
-    return list->cl->insert_element(list, index, NULL);
-}
-
+CX_EXPORT void *cxListEmplaceAt(CxList *list, size_t index);
 
 /**
  * Allocates memory for an element at the end of the list and returns a pointer to that memory.
@@ -498,10 +418,46 @@
  * @see cxListAdd()
  */
 cx_attr_nonnull
-static inline void *cxListEmplace(CxList *list) {
-    list->collection.sorted = false;
-    return list->cl->insert_element(list, list->collection.size, NULL);
-}
+CX_EXPORT void *cxListEmplace(CxList *list);
+
+/**
+ * Allocates memory for multiple elements and returns an iterator.
+ *
+ * The iterator will only iterate over the successfully allocated elements.
+ * The @c elem_count attribute is set to that number, and the @c index attribute
+ * will range from zero to @c elem_count minus one.
+ *
+ * @remark When the list is storing pointers, the iterator will iterate over
+ * the @c void** elements.
+ *
+ * @param list the list
+ * @param index the index where to insert the new data
+ * @param n the number of elements for which to allocate the memory
+ * @return an iterator, iterating over the new memory
+ * @see cxListEmplaceAt()
+ * @see cxListInsertArray()
+ */
+cx_attr_nonnull
+CX_EXPORT CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n);
+
+/**
+ * Allocates memory for multiple elements and returns an iterator.
+ *
+ * The iterator will only iterate over the successfully allocated elements.
+ * The @c elem_count attribute is set to that number, and the @c index attribute
+ * will range from zero to @c elem_count minus one.
+ *
+ * @remark When the list is storing pointers, the iterator will iterate over
+ * the @c void** elements.
+ *
+ * @param list the list
+ * @param n the number of elements for which to allocate the memory
+ * @return an iterator, iterating over the new memory
+ * @see cxListEmplace()
+ * @see cxListAddArray()
+ */
+cx_attr_nonnull
+CX_EXPORT CxIterator cxListEmplaceArray(CxList *list, size_t n);
 
 /**
  * Inserts an item into a sorted list.
@@ -514,20 +470,15 @@
  * @retval non-zero memory allocation failure
  */
 cx_attr_nonnull
-static inline int cxListInsertSorted(
-        CxList *list,
-        const void *elem
-) {
-    assert(list->collection.sorted || list->collection.size == 0);
-    list->collection.sorted = true;
-    const void *data = list->collection.store_pointer ? &elem : elem;
-    return list->cl->insert_sorted(list, data, 1) == 0;
-}
+CX_EXPORT int cxListInsertSorted(CxList *list, const void *elem);
 
 /**
- * Inserts an item into a sorted list if it does not exist.
+ * Inserts an item into a list if it does not exist.
  *
- * If the list is not sorted already, the behavior is undefined.
+ * If the list is not sorted already, this function will check all elements
+ * and append the new element when it was not found.
+ * It is strongly recommended to use this function only on sorted lists, where
+ * the element, if it is not contained, is inserted at the correct position.
  *
  * @param list the list
  * @param elem a pointer to the element to add
@@ -535,15 +486,7 @@
  * @retval non-zero memory allocation failure
  */
 cx_attr_nonnull
-static inline int cxListInsertUnique(
-        CxList *list,
-        const void *elem
-) {
-    assert(list->collection.sorted || list->collection.size == 0);
-    list->collection.sorted = true;
-    const void *data = list->collection.store_pointer ? &elem : elem;
-    return list->cl->insert_unique(list, data, 1) == 0;
-}
+CX_EXPORT int cxListInsertUnique(CxList *list, const void *elem);
 
 /**
  * Inserts multiple items to the list at the specified index.
@@ -563,17 +506,10 @@
  * @param array a pointer to the elements to add
  * @param n the number of elements to add
  * @return the number of added elements
+ * @see cxListEmplaceArrayAt()
  */
 cx_attr_nonnull
-static inline size_t cxListInsertArray(
-        CxList *list,
-        size_t index,
-        const void *array,
-        size_t n
-) {
-    list->collection.sorted = false;
-    return list->cl->insert_array(list, index, array, n);
-}
+CX_EXPORT size_t cxListInsertArray(CxList *list, size_t index, const void *array, size_t n);
 
 /**
  * Inserts a sorted array into a sorted list.
@@ -595,18 +531,17 @@
  * @return the number of added elements
  */
 cx_attr_nonnull
-static inline size_t cxListInsertSortedArray(
-        CxList *list,
-        const void *array,
-        size_t n
-) {
-    assert(list->collection.sorted || list->collection.size == 0);
-    list->collection.sorted = true;
-    return list->cl->insert_sorted(list, array, n);
-}
+CX_EXPORT size_t cxListInsertSortedArray(CxList *list, const void *array, size_t n);
 
 /**
- * Inserts a sorted array into a sorted list, skipping duplicates.
+ * Inserts an array into a list, skipping duplicates.
+ *
+ * The @p list does not need to be sorted (in contrast to cxListInsertSortedArray()).
+ * But it is strongly recommended to use this function only on sorted lists,
+ * because otherwise it will fall back to an inefficient algorithm which inserts
+ * all elements one by one.
+ * If the @p list is not sorted, the @p array also does not need to be sorted.
+ * But when the @p list is sorted, the @p array must also be sorted.
  *
  * This method is usually more efficient than inserting each element separately
  * because consecutive chunks of sorted data are inserted in one pass.
@@ -623,9 +558,6 @@
  * If this list is storing pointers instead of objects @p array is expected to
  * be an array of pointers.
  *
- * If the list is not sorted already, the behavior is undefined.
- * This is also the case when the @p array is not sorted.
- *
  * @param list the list
  * @param array a pointer to the elements to add
  * @param n the number of elements to add
@@ -634,15 +566,7 @@
  * @return the number of elements from the @p sorted_data that are definitely present in the list after this call
  */
 cx_attr_nonnull
-static inline size_t cxListInsertUniqueArray(
-        CxList *list,
-        const void *array,
-        size_t n
-) {
-    assert(list->collection.sorted || list->collection.size == 0);
-    list->collection.sorted = true;
-    return list->cl->insert_unique(list, array, n);
-}
+CX_EXPORT size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n);
 
 /**
  * Inserts an element after the current location of the specified iterator.
@@ -661,14 +585,7 @@
  * @see cxListInsertBefore()
  */
 cx_attr_nonnull
-static inline int cxListInsertAfter(
-        CxIterator *iter,
-        const void *elem
-) {
-    CxList* list = (CxList*)iter->src_handle.m;
-    list->collection.sorted = false;
-    return list->cl->insert_iter(iter, elem, 0);
-}
+CX_EXPORT int cxListInsertAfter(CxIterator *iter, const void *elem);
 
 /**
  * Inserts an element before the current location of the specified iterator.
@@ -687,14 +604,7 @@
  * @see cxListInsertAfter()
  */
 cx_attr_nonnull
-static inline int cxListInsertBefore(
-        CxIterator *iter,
-        const void *elem
-) {
-    CxList* list = (CxList*)iter->src_handle.m;
-    list->collection.sorted = false;
-    return list->cl->insert_iter(iter, elem, 1);
-}
+CX_EXPORT int cxListInsertBefore(CxIterator *iter, const void *elem);
 
 /**
  * Removes the element at the specified index.
@@ -708,12 +618,7 @@
  * @retval non-zero index out of bounds
  */
 cx_attr_nonnull
-static inline int cxListRemove(
-        CxList *list,
-        size_t index
-) {
-    return list->cl->remove(list, index, 1, NULL) == 0;
-}
+CX_EXPORT int cxListRemove(CxList *list, size_t index);
 
 /**
  * Removes and returns the element at the specified index.
@@ -728,15 +633,8 @@
  * @retval zero success
  * @retval non-zero index out of bounds
  */
-cx_attr_nonnull
-cx_attr_access_w(3)
-static inline int cxListRemoveAndGet(
-        CxList *list,
-        size_t index,
-        void *targetbuf
-) {
-    return list->cl->remove(list, index, 1, targetbuf) == 0;
-}
+cx_attr_nonnull cx_attr_access_w(3)
+CX_EXPORT int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf);
 
 /**
  * Removes and returns the first element of the list.
@@ -752,14 +650,8 @@
  * @see cxListPopFront()
  * @see cxListRemoveAndGetLast()
  */
-cx_attr_nonnull
-cx_attr_access_w(2)
-static inline int cxListRemoveAndGetFirst(
-    CxList *list,
-    void *targetbuf
-) {
-    return list->cl->remove(list, 0, 1, targetbuf) == 0;
-}
+cx_attr_nonnull cx_attr_access_w(2)
+CX_EXPORT int cxListRemoveAndGetFirst(CxList *list, void *targetbuf);
 
 /**
  * Removes and returns the first element of the list.
@@ -792,15 +684,8 @@
  * @retval zero success
  * @retval non-zero the list is empty
  */
-cx_attr_nonnull
-cx_attr_access_w(2)
-static inline int cxListRemoveAndGetLast(
-    CxList *list,
-    void *targetbuf
-) {
-    // note: index may wrap - member function will catch that
-    return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0;
-}
+cx_attr_nonnull cx_attr_access_w(2)
+CX_EXPORT int cxListRemoveAndGetLast(CxList *list, void *targetbuf);
 
 /**
  * Removes and returns the last element of the list.
@@ -836,13 +721,7 @@
  * @return the actual number of removed elements
  */
 cx_attr_nonnull
-static inline size_t cxListRemoveArray(
-        CxList *list,
-        size_t index,
-        size_t num
-) {
-    return list->cl->remove(list, index, num, NULL);
-}
+CX_EXPORT size_t cxListRemoveArray(CxList *list, size_t index, size_t num);
 
 /**
  * Removes and returns multiple elements starting at the specified index.
@@ -857,16 +736,8 @@
  * @param targetbuf a buffer where to copy the elements
  * @return the actual number of removed elements
  */
-cx_attr_nonnull
-cx_attr_access_w(4)
-static inline size_t cxListRemoveArrayAndGet(
-        CxList *list,
-        size_t index,
-        size_t num,
-        void *targetbuf
-) {
-    return list->cl->remove(list, index, num, targetbuf);
-}
+cx_attr_nonnull cx_attr_access_w(4)
+CX_EXPORT size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf);
 
 /**
  * Removes all elements from this list.
@@ -877,10 +748,7 @@
  * @param list the list
  */
 cx_attr_nonnull
-static inline void cxListClear(CxList *list) {
-    list->cl->clear(list);
-    list->collection.sorted = true; // empty lists are always sorted
-}
+CX_EXPORT void cxListClear(CxList *list);
 
 /**
  * Swaps two items in the list.
@@ -896,14 +764,7 @@
  * or the swap needed extra memory, but allocation failed
  */
 cx_attr_nonnull
-static inline int cxListSwap(
-        CxList *list,
-        size_t i,
-        size_t j
-) {
-    list->collection.sorted = false;
-    return list->cl->swap(list, i, j);
-}
+CX_EXPORT int cxListSwap(CxList *list, size_t i, size_t j);
 
 /**
  * Returns a pointer to the element at the specified index.
@@ -915,12 +776,7 @@
  * @return a pointer to the element or @c NULL if the index is out of bounds
  */
 cx_attr_nonnull
-static inline void *cxListAt(
-        const CxList *list,
-        size_t index
-) {
-    return list->cl->at(list, index);
-}
+CX_EXPORT void *cxListAt(const CxList *list, size_t index);
 
 /**
  * Returns a pointer to the first element.
@@ -931,9 +787,7 @@
  * @return a pointer to the first element or @c NULL if the list is empty
  */
 cx_attr_nonnull
-static inline void *cxListFirst(const CxList *list) {
-    return list->cl->at(list, 0);
-}
+CX_EXPORT void *cxListFirst(const CxList *list);
 
 /**
  * Returns a pointer to the last element.
@@ -944,12 +798,13 @@
  * @return a pointer to the last element or @c NULL if the list is empty
  */
 cx_attr_nonnull
-static inline void *cxListLast(const CxList *list) {
-    return list->cl->at(list, list->collection.size - 1);
-}
+CX_EXPORT void *cxListLast(const CxList *list);
 
 /**
- * Sets the element at the specified index in the list
+ * Sets the element at the specified index in the list.
+ *
+ * This overwrites the element in-place without calling any destructor
+ * on the overwritten element.
  *
  * @param list the list to set the element in
  * @param index the index to set the element at
@@ -958,12 +813,7 @@
  * @retval non-zero when index is out of bounds
  */
 cx_attr_nonnull
-cx_attr_export
-int cxListSet(
-        CxList *list,
-        size_t index,
-        const void *elem
-);
+CX_EXPORT int cxListSet(CxList *list, size_t index, const void *elem);
 
 /**
  * Returns an iterator pointing to the item at the specified index.
@@ -977,13 +827,7 @@
  * @return a new iterator
  */
 cx_attr_nodiscard
-static inline CxIterator cxListIteratorAt(
-        const CxList *list,
-        size_t index
-) {
-    if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, index, false);
-}
+CX_EXPORT CxIterator cxListIteratorAt(const CxList *list, size_t index);
 
 /**
  * Returns a backwards iterator pointing to the item at the specified index.
@@ -997,50 +841,7 @@
  * @return a new iterator
  */
 cx_attr_nodiscard
-static inline CxIterator cxListBackwardsIteratorAt(
-        const CxList *list,
-        size_t index
-) {
-    if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, index, true);
-}
-
-/**
- * Returns a mutating iterator pointing to the item at the specified index.
- *
- * The returned iterator is position-aware.
- *
- * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned.
- *
- * @param list the list
- * @param index the index where the iterator shall point at
- * @return a new iterator
- */
-cx_attr_nodiscard
-cx_attr_export
-CxIterator cxListMutIteratorAt(
-        CxList *list,
-        size_t index
-);
-
-/**
- * Returns a mutating backwards iterator pointing to the item at the
- * specified index.
- *
- * The returned iterator is position-aware.
- *
- * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned.
- *
- * @param list the list
- * @param index the index where the iterator shall point at
- * @return a new iterator
- */
-cx_attr_nodiscard
-cx_attr_export
-CxIterator cxListMutBackwardsIteratorAt(
-        CxList *list,
-        size_t index
-);
+CX_EXPORT CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index);
 
 /**
  * Returns an iterator pointing to the first item of the list.
@@ -1053,27 +854,7 @@
  * @return a new iterator
  */
 cx_attr_nodiscard
-static inline CxIterator cxListIterator(const CxList *list) {
-    if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, 0, false);
-}
-
-/**
- * Returns a mutating iterator pointing to the first item of the list.
- *
- * The returned iterator is position-aware.
- *
- * If the list is empty or @c NULL, a past-the-end iterator will be returned.
- *
- * @param list the list
- * @return a new iterator
- */
-cx_attr_nodiscard
-static inline CxIterator cxListMutIterator(CxList *list) {
-    if (list == NULL) list = cxEmptyList;
-    return cxListMutIteratorAt(list, 0);
-}
-
+CX_EXPORT CxIterator cxListIterator(const CxList *list);
 
 /**
  * Returns a backwards iterator pointing to the last item of the list.
@@ -1086,26 +867,7 @@
  * @return a new iterator
  */
 cx_attr_nodiscard
-static inline CxIterator cxListBackwardsIterator(const CxList *list) {
-    if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, list->collection.size - 1, true);
-}
-
-/**
- * Returns a mutating backwards iterator pointing to the last item of the list.
- *
- * The returned iterator is position-aware.
- *
- * If the list is empty or @c NULL, a past-the-end iterator will be returned.
- *
- * @param list the list
- * @return a new iterator
- */
-cx_attr_nodiscard
-static inline CxIterator cxListMutBackwardsIterator(CxList *list) {
-    if (list == NULL) list = cxEmptyList;
-    return cxListMutBackwardsIteratorAt(list, list->collection.size - 1);
-}
+CX_EXPORT CxIterator cxListBackwardsIterator(const CxList *list);
 
 /**
  * Returns the index of the first element that equals @p elem.
@@ -1118,14 +880,8 @@
  * @see cxListIndexValid()
  * @see cxListContains()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline size_t cxListFind(
-        const CxList *list,
-        const void *elem
-) {
-    return list->cl->find_remove((CxList*)list, elem, false);
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT size_t cxListFind(const CxList *list, const void *elem);
 
 /**
  * Checks if the list contains the specified element.
@@ -1138,14 +894,8 @@
  * @retval false if the element is not contained
  * @see cxListFind()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline bool cxListContains(
-    const CxList* list,
-    const void* elem
-) {
-    return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size;
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT bool cxListContains(const CxList* list, const void* elem);
 
 /**
  * Checks if the specified index is within bounds.
@@ -1155,11 +905,8 @@
  * @retval true if the index is within bounds
  * @retval false if the index is out of bounds
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline bool cxListIndexValid(const CxList *list, size_t index) {
-    return index < list->collection.size;
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT bool cxListIndexValid(const CxList *list, size_t index);
 
 /**
  * Removes and returns the index of the first element that equals @p elem.
@@ -1173,12 +920,7 @@
  * @see cxListIndexValid()
  */
 cx_attr_nonnull
-static inline size_t cxListFindRemove(
-        CxList *list,
-        const void *elem
-) {
-    return list->cl->find_remove(list, elem, true);
-}
+CX_EXPORT size_t cxListFindRemove(CxList *list, const void *elem);
 
 /**
  * Sorts the list.
@@ -1188,11 +930,7 @@
  * @param list the list
  */
 cx_attr_nonnull
-static inline void cxListSort(CxList *list) {
-    if (list->collection.sorted) return;
-    list->cl->sort(list);
-    list->collection.sorted = true;
-}
+CX_EXPORT void cxListSort(CxList *list);
 
 /**
  * Reverses the order of the items.
@@ -1200,11 +938,7 @@
  * @param list the list
  */
 cx_attr_nonnull
-static inline void cxListReverse(CxList *list) {
-    // still sorted, but not according to the cmp_func
-    list->collection.sorted = false;
-    list->cl->reverse(list);
-}
+CX_EXPORT void cxListReverse(CxList *list);
 
 /**
  * Compares a list to another list of the same type.
@@ -1215,18 +949,13 @@
  * @param list the list
  * @param other the list to compare to
  * @retval zero both lists are equal element wise
- * @retval negative the first list is smaller
+ * @retval negative the first list is smaller,
  * or the first non-equal element in the first list is smaller
  * @retval positive the first list is larger
  * or the first non-equal element in the first list is larger
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-int cxListCompare(
-        const CxList *list,
-        const CxList *other
-);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cxListCompare(const CxList *list, const CxList *other);
 
 /**
  * Deallocates the memory of the specified list structure.
@@ -1235,9 +964,228 @@
  *
  * @param list the list that shall be freed
  */
-cx_attr_export
-void cxListFree(CxList *list);
+CX_EXPORT void cxListFree(CxList *list);
+
+
+/**
+ * Performs a deep clone of one list into another.
+ *
+ * If the destination list already contains elements, the cloned elements
+ * are appended to that list.
+ *
+ * @attention If the cloned elements need to be destroyed by a destructor
+ * function, you must make sure that the destination list also uses this
+ * destructor function.
+ *
+ * @param dst the destination list
+ * @param src the source list
+ * @param clone_func the clone function for the elements
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when all elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListCloneSimple()
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+CX_EXPORT int cxListClone(CxList *dst, const CxList *src,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+/**
+ * Clones elements from a list only if they are not present in another list.
+ *
+ * If the @p minuend does not contain duplicates, this is equivalent to adding
+ * the set difference to the destination list.
+ *
+ * This function is optimized for the case when both the @p minuend and the
+ * @p subtrahend are sorted.
+ *
+ * @param dst the destination list
+ * @param minuend the list to subtract elements from
+ * @param subtrahend the elements that shall be subtracted
+ * @param clone_func the clone function for the elements
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListDifferenceSimple()
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+CX_EXPORT int cxListDifference(CxList *dst,
+        const CxList *minuend, const CxList *subtrahend,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+/**
+ * Clones elements from a list only if they are also present in another list.
+ *
+ * This function is optimized for the case when both the @p src and the
+ * @p other list are sorted.
+ *
+ * If the destination list already contains elements, the intersection is appended
+ * to that list.
+ *
+ * @param dst the destination list
+ * @param src the list to clone the elements from
+ * @param other the list to check the elements for existence
+ * @param clone_func the clone function for the elements
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListIntersectionSimple()
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+CX_EXPORT int cxListIntersection(CxList *dst, const CxList *src, const CxList *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+/**
+ * Performs a deep clone of one list into another, skipping duplicates.
+ *
+ * This function is optimized for the case when both the @p src and the
+ * @p other list are sorted.
+ * In that case, the union will also be sorted.
+ *
+ * If the destination list already contains elements, the union is appended
+ * to that list. In that case the destination is not necessarily sorted.
+ *
+ * @param dst the destination list
+ * @param src the primary source list
+ * @param other the other list, where elements are only cloned from
+ * when they are not in @p src
+ * @param clone_func the clone function for the elements
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListUnionSimple()
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+CX_EXPORT int cxListUnion(CxList *dst, const CxList *src, const CxList *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
 
+/**
+ * Performs a shallow clone of one list into another.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * If the destination list already contains elements, the cloned elements
+ * are appended to that list.
+ *
+ * @attention If the cloned elements need to be destroyed by a destructor
+ * function, you must make sure that the destination list also uses this
+ * destructor function.
+ *
+ * @param dst the destination list
+ * @param src the source list
+ * @retval zero when all elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListClone()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListCloneSimple(CxList *dst, const CxList *src);
+
+/**
+ * Clones elements from a list only if they are not present in another list.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * If the @p minuend does not contain duplicates, this is equivalent to adding
+ * the set difference to the destination list.
+ *
+ * This function is optimized for the case when both the @p minuend and the
+ * @p subtrahend are sorted.
+ *
+ * @param dst the destination list
+ * @param minuend the list to subtract elements from
+ * @param subtrahend the elements that shall be subtracted
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListDifference()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListDifferenceSimple(CxList *dst,
+        const CxList *minuend, const CxList *subtrahend);
+
+/**
+ * Clones elements from a list only if they are also present in another list.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * This function is optimized for the case when both the @p src and the
+ * @p other list are sorted.
+ *
+ * If the destination list already contains elements, the intersection is appended
+ * to that list.
+ *
+ * @param dst the destination list
+ * @param src the list to clone the elements from
+ * @param other the list to check the elements for existence
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListIntersection()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other);
+
+/**
+ * Performs a deep clone of one list into another, skipping duplicates.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * This function is optimized for the case when both the @p src and the
+ * @p other list are sorted.
+ * In that case, the union will also be sorted.
+ *
+ * If the destination list already contains elements, the union is appended
+ * to that list. In that case the destination is not necessarily sorted.
+ *
+ * @param dst the destination list
+ * @param src the primary source list
+ * @param other the other list, where elements are only cloned from
+ * when they are not in @p src
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxListUnion()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other);
+
+/**
+ * Asks the list to reserve enough memory for a given total number of elements.
+ *
+ * List implementations are free to choose if reserving memory upfront makes
+ * sense.
+ * For example, array-based implementations usually do support reserving memory
+ * for additional elements while linked lists usually don't.
+ *
+ * @note When the requested capacity is smaller than the current size,
+ * this function returns zero without performing any action.
+ *
+ * @param list the list
+ * @param capacity the expected total number of elements
+ * @retval zero on success or when overallocation is not supported
+ * @retval non-zero when an allocation error occurred
+ * @see cxListShrink()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListReserve(CxList *list, size_t capacity);
+
+/**
+ * Advises the list to free any overallocated memory.
+ *
+ * Lists that do not support overallocation simply return zero.
+ *
+ * This function usually returns zero, except for very special and custom
+ * list and/or allocator implementations where freeing memory can fail.
+ *
+ * @param list the list
+ * @return usually zero
+ */
+cx_attr_nonnull
+CX_EXPORT int cxListShrink(CxList *list);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/map.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/map.h	Mon Nov 10 21:52:51 2025 +0100
@@ -41,6 +41,11 @@
 #include "string.h"
 #include "hash_key.h"
 
+#ifndef UCX_LIST_H
+// forward-declare CxList
+typedef struct cx_list_s CxList;
+#endif
+
 #ifdef    __cplusplus
 extern "C" {
 #endif
@@ -111,16 +116,7 @@
     /**
      * Handle for the source map.
      */
-    union {
-        /**
-         * Access for mutating iterators.
-         */
-        CxMap *m;
-        /**
-         * Access for normal iterators.
-         */
-        const CxMap *c;
-    } map;
+    CxMap *map;
 
     /**
      * Handle for the current element.
@@ -189,19 +185,12 @@
      * shall only allocate memory instead of adding an existing value to the map.
      * Returns a pointer to the allocated memory or @c NULL if allocation fails.
      */
-    void *(*put)(
-            CxMap *map,
-            CxHashKey key,
-            void *value
-    );
+    void *(*put)(CxMap *map, CxHashKey key, void *value);
 
     /**
      * Returns an element.
      */
-    void *(*get)(
-            const CxMap *map,
-            CxHashKey key
-    );
+    void *(*get)(const CxMap *map, CxHashKey key);
 
     /**
      * Removes an element.
@@ -213,11 +202,7 @@
      * The function SHALL return zero when the @p key was found and
      * non-zero, otherwise. 
      */
-    int (*remove)(
-            CxMap *map,
-            CxHashKey key,
-            void *targetbuf
-    );
+    int (*remove)(CxMap *map, CxHashKey key, void *targetbuf);
 
     /**
      * Creates an iterator for this map.
@@ -233,8 +218,7 @@
  * You can use this as a placeholder for initializing CxMap pointers
  * for which you do not want to reserve memory right from the beginning.
  */
-cx_attr_export
-extern CxMap *const cxEmptyMap;
+CX_EXPORT extern CxMap *const cxEmptyMap;
 
 /**
  * Deallocates the memory of the specified map.
@@ -243,8 +227,7 @@
  *
  * @param map the map to be freed
  */
-cx_attr_export
-void cxMapFree(CxMap *map);
+CX_EXPORT void cxMapFree(CxMap *map);
 
 
 /**
@@ -255,9 +238,7 @@
  * @param map the map to be cleared
  */
 cx_attr_nonnull
-static inline void cxMapClear(CxMap *map) {
-    map->cl->clear(map);
-}
+CX_EXPORT void cxMapClear(CxMap *map);
 
 /**
  * Returns the number of elements in this map.
@@ -266,9 +247,7 @@
  * @return the number of stored elements
  */
 cx_attr_nonnull
-static inline size_t cxMapSize(const CxMap *map) {
-    return map->collection.size;
-}
+CX_EXPORT size_t cxMapSize(const CxMap *map);
 
 /**
  * Creates a value iterator for a map.
@@ -284,10 +263,7 @@
  * @return an iterator for the currently stored values
  */
 cx_attr_nodiscard
-static inline CxMapIterator cxMapIteratorValues(const CxMap *map) {
-    if (map == NULL) map = cxEmptyMap;
-    return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
-}
+CX_EXPORT CxMapIterator cxMapIteratorValues(const CxMap *map);
 
 /**
  * Creates a key iterator for a map.
@@ -302,10 +278,7 @@
  * @return an iterator for the currently stored keys
  */
 cx_attr_nodiscard
-static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) {
-    if (map == NULL) map = cxEmptyMap;
-    return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
-}
+CX_EXPORT CxMapIterator cxMapIteratorKeys(const CxMap *map);
 
 /**
  * Creates an iterator for a map.
@@ -322,62 +295,7 @@
  * @see cxMapIteratorValues()
  */
 cx_attr_nodiscard
-static inline CxMapIterator cxMapIterator(const CxMap *map) {
-    if (map == NULL) map = cxEmptyMap;
-    return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
-}
-
-
-/**
- * Creates a mutating iterator over the values of a map.
- *
- * When the map is storing pointers, those pointers are returned.
- * Otherwise, the iterator iterates over pointers to the memory within the map where the
- * respective elements are stored.
- *
- * @note An iterator iterates over all elements successively. Therefore, the order
- * highly depends on the map implementation and may change arbitrarily when the contents change.
- *
- * @param map the map to create the iterator for (can be @c NULL)
- * @return an iterator for the currently stored values
- */
-cx_attr_nodiscard
-cx_attr_export
-CxMapIterator cxMapMutIteratorValues(CxMap *map);
-
-/**
- * Creates a mutating iterator over the keys of a map.
- *
- * The elements of the iterator are keys of type CxHashKey, and the pointer returned
- * during iterator shall be treated as @c const @c CxHashKey* .
- *
- * @note An iterator iterates over all elements successively. Therefore, the order
- * highly depends on the map implementation and may change arbitrarily when the contents change.
- *
- * @param map the map to create the iterator for (can be @c NULL)
- * @return an iterator for the currently stored keys
- */
-cx_attr_nodiscard
-cx_attr_export
-CxMapIterator cxMapMutIteratorKeys(CxMap *map);
-
-/**
- * Creates a mutating iterator for a map.
- *
- * The elements of the iterator are key/value pairs of type CxMapEntry, and the pointer returned
- * during iterator shall be treated as @c const @c CxMapEntry* .
- *
- * @note An iterator iterates over all elements successively. Therefore, the order
- * highly depends on the map implementation and may change arbitrarily when the contents change.
- *
- * @param map the map to create the iterator for (can be @c NULL)
- * @return an iterator for the currently stored entries
- * @see cxMapMutIteratorKeys()
- * @see cxMapMutIteratorValues()
- */
-cx_attr_nodiscard
-cx_attr_export
-CxMapIterator cxMapMutIterator(CxMap *map);
+CX_EXPORT CxMapIterator cxMapIterator(const CxMap *map);
 
 /**
  * Puts a key/value-pair into the map.
@@ -400,13 +318,7 @@
  * @see cxMapPut()
  */
 cx_attr_nonnull
-static inline int cx_map_put(
-        CxMap *map,
-        CxHashKey key,
-        void *value
-) {
-    return map->cl->put(map, key, value) == NULL;
-}
+CX_EXPORT int cx_map_put(CxMap *map, CxHashKey key, void *value);
 
 /**
  * Puts a key/value-pair into the map.
@@ -450,12 +362,7 @@
  * @see cxMapEmplace()
  */
 cx_attr_nonnull
-static inline void *cx_map_emplace(
-        CxMap *map,
-        CxHashKey key
-) {
-    return map->cl->put(map, key, NULL);
-}
+CX_EXPORT void *cx_map_emplace(CxMap *map, CxHashKey key);
 
 /**
  * Allocates memory for a value in the map associated with the specified key.
@@ -490,14 +397,8 @@
  * @return the value
  * @see cxMapGet()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline void *cx_map_get(
-        const CxMap *map,
-        CxHashKey key
-) {
-    return map->cl->get(map, key);
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT void *cx_map_get(const CxMap *map, CxHashKey key);
 
 /**
  * Retrieves a value by using a key.
@@ -508,12 +409,23 @@
  *
  * @param map (@c CxMap*) the map
  * @param key (any supported key type) the key
- * @return (@c void*) the value
+ * @return (@c void*) the value or @c NULL when no value with that @p key exists
  * @see CX_HASH_KEY()
  */
 #define cxMapGet(map, key) cx_map_get(map, CX_HASH_KEY(key))
 
 /**
+ * Checks if a map contains a specific key.
+ *
+ * @param map (@c CxMap*) the map
+ * @param key (any supported key type) the key
+ * @retval true if the key exists in the map
+ * @retval false if the key does not exist in the map
+ * @see CX_HASH_KEY()
+ */
+#define cxMapContains(map, key) (cxMapGet(map, key) != NULL)
+
+/**
  * Removes a key/value-pair from the map by using the key.
  *
  * Invokes the destructor functions, if any, on the removed element if and only if the
@@ -529,13 +441,7 @@
  * @see cxMapRemoveAndGet()
  */
 cx_attr_nonnull_arg(1)
-static inline int cx_map_remove(
-        CxMap *map,
-        CxHashKey key,
-        void *targetbuf
-) {
-    return map->cl->remove(map, key, targetbuf);
-}
+CX_EXPORT int cx_map_remove(CxMap *map, CxHashKey key, void *targetbuf);
 
 /**
  * Removes a key/value-pair from the map by using the key.
@@ -574,6 +480,246 @@
  */
 #define cxMapRemoveAndGet(map, key, targetbuf) cx_map_remove(map, CX_HASH_KEY(key), targetbuf)
 
+
+/**
+ * Performs a deep clone of one map into another.
+ *
+ * If the destination map already contains entries, the cloned entries
+ * are added to that map, possibly overwriting existing elements when
+ * the keys already exist.
+ *
+ * When elements in the destination map need to be replaced, any destructor
+ * function is called on the replaced elements before replacing them.
+ *
+ * @attention If the cloned elements need to be destroyed by a destructor
+ * function, you must make sure that the destination map also uses this
+ * destructor function.
+ *
+ * @param dst the destination map
+ * @param src the source map
+ * @param clone_func the clone function for the values
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when all elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+CX_EXPORT int cxMapClone(CxMap *dst, const CxMap *src,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+
+/**
+ * Clones entries of a map if their key is not present in another map.
+ *
+ * @param dst the destination map
+ * @param minuend the map to subtract the entries from
+ * @param subtrahend the map containing the elements to be subtracted
+ * @param clone_func the clone function for the values
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+CX_EXPORT int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+/**
+ * Clones entries of a map if their key is not present in a list.
+ *
+ * Note that the list must contain keys of type @c CxKey
+ * (or pointers to such keys) and must use @c cx_hash_key_cmp
+ * as the compare function.
+ * Generic key types cannot be processed in this case.
+ *
+ * @param dst the destination map
+ * @param src the source map
+ * @param keys the list of @c CxKey items
+ * @param clone_func the clone function for the values
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+CX_EXPORT int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+
+/**
+ * Clones entries of a map only if their key is present in another map.
+ *
+ * @param dst the destination map
+ * @param src the map to clone the entries from
+ * @param other the map to check for existence of the keys
+ * @param clone_func the clone function for the values
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+CX_EXPORT int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+/**
+ * Clones entries of a map only if their key is present in a list.
+ *
+ * Note that the list must contain keys of type @c CxKey
+ * (or pointers to such keys) and must use @c cx_hash_key_cmp
+ * as the compare function.
+ * Generic key types cannot be processed in this case.
+ *
+ * @param dst the destination map
+ * @param src the source map
+ * @param keys the list of @c CxKey items
+ * @param clone_func the clone function for the values
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+CX_EXPORT int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+/**
+ * Clones entries into a map if their key does not exist yet.
+ *
+ * If you want to calculate the union of two maps into a fresh new map,
+ * you can proceed as follows:
+ * 1. Clone the first map into a fresh, empty map.
+ * 2. Use this function to clone the second map into the result from step 1.
+ *
+ * @param dst the destination map
+ * @param src the map to clone the entries from
+ * @param clone_func the clone function for the values
+ * @param clone_allocator the allocator that is passed to the clone function
+ * @param data optional additional data that is passed to the clone function
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+CX_EXPORT int cxMapUnion(CxMap *dst, const CxMap *src,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data);
+
+/**
+ * Performs a shallow clone of one map into another.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * If the destination map already contains entries, the cloned entries
+ * are added to that map, possibly overwriting existing elements when
+ * the keys already exist.
+ *
+ * When elements in the destination map need to be replaced, any destructor
+ * function is called on the replaced elements before replacing them.
+ *
+ * @attention If the cloned elements need to be destroyed by a destructor
+ * function, you must make sure that the destination map also uses this
+ * destructor function.
+ *
+ * @param dst the destination map
+ * @param src the source map
+ * @retval zero when all elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxMapClone()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxMapCloneSimple(CxMap *dst, const CxMap *src);
+
+/**
+ * Clones entries of a map if their key is not present in another map.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * @param dst the destination map
+ * @param minuend the map to subtract the entries from
+ * @param subtrahend the map containing the elements to be subtracted
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull
+CX_EXPORT int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend);
+
+/**
+ * Clones entries of a map if their key is not present in a list.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * Note that the list must contain keys of type @c CxKey
+ * (or pointers to such keys) and must use @c cx_hash_key_cmp
+ * as the compare function.
+ * Generic key types cannot be processed in this case.
+ *
+ * @param dst the destination map
+ * @param src the source map
+ * @param keys the list of @c CxKey items
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ * @see cxMapListDifference()
+ */
+cx_attr_nonnull
+CX_EXPORT int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys);
+
+
+/**
+ * Clones entries of a map only if their key is present in another map.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * @param dst the destination map
+ * @param src the map to clone the entries from
+ * @param other the map to check for existence of the keys
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull
+CX_EXPORT int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other);
+
+/**
+ * Clones entries of a map only if their key is present in a list.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * Note that the list must contain keys of type @c CxKey
+ * (or pointers to such keys) and must use @c cx_hash_key_cmp
+ * as the compare function.
+ * Generic key types cannot be processed in this case.
+ *
+ * @param dst the destination map
+ * @param src the source map
+ * @param keys the list of @c CxKey items
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull
+CX_EXPORT int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys);
+
+/**
+ * Clones entries into a map if their key does not exist yet.
+ *
+ * This function uses the default allocator, if needed, and performs
+ * shallow clones with @c memcpy().
+ *
+ * If you want to calculate the union of two maps into a fresh new map,
+ * you can proceed as follows:
+ * 1. Clone the first map into a fresh, empty map.
+ * 2. Use this function to clone the second map into the result from step 1.
+ *
+ * @param dst the destination map
+ * @param src the map to clone the entries from
+ * @retval zero when the elements were successfully cloned
+ * @retval non-zero when an allocation error occurred
+ */
+cx_attr_nonnull
+CX_EXPORT int cxMapUnionSimple(CxMap *dst, const CxMap *src);
+
 #ifdef    __cplusplus
 } // extern "C"
 #endif
--- a/ucx/cx/mempool.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/mempool.h	Mon Nov 10 21:52:51 2025 +0100
@@ -156,8 +156,7 @@
  *
  * @param pool the memory pool to free
  */
-cx_attr_export
-void cxMempoolFree(CxMempool *pool);
+CX_EXPORT void cxMempoolFree(CxMempool *pool);
 
 /**
  * Creates an array-based memory pool.
@@ -169,11 +168,8 @@
  * @param type the type of memory pool
  * @return the created memory pool or @c NULL if allocation failed
  */
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc(cxMempoolFree, 1)
-cx_attr_export
-CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type);
+cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMempoolFree, 1)
+CX_EXPORT CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type);
 
 /**
  * Creates a basic array-based memory pool.
@@ -212,8 +208,7 @@
  * @param fnc the destructor that shall be applied to all memory blocks
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc);
+CX_EXPORT void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc);
 
 /**
  * Sets the global destructor for all memory blocks within the specified pool.
@@ -223,8 +218,7 @@
  * @param data additional data for the destructor function
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data);
+CX_EXPORT void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data);
 
 /**
  * Sets the destructor function for a specific allocated memory object.
@@ -237,11 +231,7 @@
  * @param fnc the destructor function
  */
 cx_attr_nonnull
-cx_attr_export
-void cxMempoolSetDestructor(
-        void *memory,
-        cx_destructor_func fnc
-);
+CX_EXPORT void cxMempoolSetDestructor(void *memory, cx_destructor_func fnc);
 
 /**
  * Sets the destructor function for a specific allocated memory object.
@@ -255,12 +245,7 @@
  * @param data additional data for the destructor function
  */
 cx_attr_nonnull
-cx_attr_export
-void cxMempoolSetDestructor2(
-        void *memory,
-        cx_destructor_func2 fnc,
-        void *data
-);
+CX_EXPORT void cxMempoolSetDestructor2(void *memory, cx_destructor_func2 fnc, void *data);
 
 /**
  * Removes the destructor function for a specific allocated memory object.
@@ -271,8 +256,7 @@
  * @param memory the object allocated in the pool
  */
 cx_attr_nonnull
-cx_attr_export
-void cxMempoolRemoveDestructor(void *memory);
+CX_EXPORT void cxMempoolRemoveDestructor(void *memory);
 
 /**
  * Removes the destructor function for a specific allocated memory object.
@@ -283,8 +267,7 @@
  * @param memory the object allocated in the pool
  */
 cx_attr_nonnull
-cx_attr_export
-void cxMempoolRemoveDestructor2(void *memory);
+CX_EXPORT void cxMempoolRemoveDestructor2(void *memory);
 
 /**
  * Registers foreign memory with this pool.
@@ -302,12 +285,7 @@
  * @retval non-zero failure
  */
 cx_attr_nonnull
-cx_attr_export
-int cxMempoolRegister(
-        CxMempool *pool,
-        void *memory,
-        cx_destructor_func destr
-);
+CX_EXPORT int cxMempoolRegister(CxMempool *pool, void *memory, cx_destructor_func destr);
 
 
 /**
@@ -330,13 +308,7 @@
  * @retval non-zero failure
  */
 cx_attr_nonnull
-cx_attr_export
-int cxMempoolRegister2(
-        CxMempool *pool,
-        void *memory,
-        cx_destructor_func2 destr,
-        void *data
-);
+CX_EXPORT int cxMempoolRegister2(CxMempool *pool, void *memory, cx_destructor_func2 destr, void *data);
 
 /**
  * Transfers all the memory managed by one pool to another.
@@ -354,11 +326,7 @@
  * @retval non-zero allocation failure or incompatible pools
  */
 cx_attr_nonnull
-cx_attr_export
-int cxMempoolTransfer(
-        CxMempool *source,
-        CxMempool *dest
-);
+CX_EXPORT int cxMempoolTransfer(CxMempool *source, CxMempool *dest);
 
 /**
  * Transfers an object from one pool to another.
@@ -375,12 +343,7 @@
  * @retval non-zero failure, or the object was not found in the source pool, or the pools are incompatible
  */
 cx_attr_nonnull
-cx_attr_export
-int cxMempoolTransferObject(
-        CxMempool *source,
-        CxMempool *dest,
-        const void *obj
-);
+CX_EXPORT int cxMempoolTransferObject(CxMempool *source, CxMempool *dest, const void *obj);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/printf.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/printf.h	Mon Nov 10 21:52:51 2025 +0100
@@ -56,8 +56,7 @@
 /**
  * The maximum string length that fits into stack memory.
  */
-cx_attr_export
-extern const unsigned cx_printf_sbo_size;
+CX_EXPORT extern const unsigned cx_printf_sbo_size;
 
 /**
  * A @c fprintf like function which writes the output to a stream by
@@ -69,16 +68,8 @@
  * @param ... additional arguments
  * @return the total number of bytes written or an error code from stdlib printf implementation
  */
-cx_attr_nonnull_arg(1, 2, 3)
-cx_attr_printf(3, 4)
-cx_attr_cstr_arg(3)
-cx_attr_export
-int cx_fprintf(
-        void *stream,
-        cx_write_func wfc,
-        const char *fmt,
-        ...
-);
+cx_attr_nonnull_arg(1, 2, 3) cx_attr_printf(3, 4) cx_attr_cstr_arg(3)
+CX_EXPORT int cx_fprintf(void *stream, cx_write_func wfc, const char *fmt, ...);
 
 /**
  * A @c vfprintf like function which writes the output to a stream by
@@ -91,15 +82,8 @@
  * @return the total number of bytes written or an error code from stdlib printf implementation
  * @see cx_fprintf()
  */
-cx_attr_nonnull
-cx_attr_cstr_arg(3)
-cx_attr_export
-int cx_vfprintf(
-        void *stream,
-        cx_write_func wfc,
-        const char *fmt,
-        va_list ap
-);
+cx_attr_nonnull cx_attr_cstr_arg(3)
+CX_EXPORT int cx_vfprintf(void *stream, cx_write_func wfc, const char *fmt, va_list ap);
 
 /**
  * An @c asprintf like function which allocates space for a string
@@ -115,15 +99,8 @@
  * @return the formatted string
  * @see cx_strfree_a()
  */
-cx_attr_nonnull_arg(1, 2)
-cx_attr_printf(2, 3)
-cx_attr_cstr_arg(2)
-cx_attr_export
-cxmutstr cx_asprintf_a(
-        const CxAllocator *allocator,
-        const char *fmt,
-        ...
-);
+cx_attr_nonnull_arg(1, 2) cx_attr_printf(2, 3) cx_attr_cstr_arg(2)
+CX_EXPORT cxmutstr cx_asprintf_a(const CxAllocator *allocator, const char *fmt, ...);
 
 /**
  * An @c asprintf like function which allocates space for a string
@@ -138,8 +115,7 @@
  * @return (@c cxmutstr) the formatted string
  * @see cx_strfree()
  */
-#define cx_asprintf(fmt, ...) \
-    cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__)
+#define cx_asprintf(fmt, ...) cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__)
 
 /**
 * A @c vasprintf like function which allocates space for a string
@@ -155,14 +131,8 @@
  * @return the formatted string
  * @see cx_asprintf_a()
  */
-cx_attr_nonnull
-cx_attr_cstr_arg(2)
-cx_attr_export
-cxmutstr cx_vasprintf_a(
-        const CxAllocator *allocator,
-        const char *fmt,
-        va_list ap
-);
+cx_attr_nonnull cx_attr_cstr_arg(2)
+CX_EXPORT cxmutstr cx_vasprintf_a(const CxAllocator *allocator, const char *fmt, va_list ap);
 
 /**
 * A @c vasprintf like function which allocates space for a string
@@ -189,8 +159,7 @@
  * @see cx_fprintf()
  * @see cxBufferWrite()
  */
-#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \
-    cxBufferWriteFunc, fmt, __VA_ARGS__)
+#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, cxBufferWriteFunc, fmt, __VA_ARGS__)
 
 
 /**
@@ -224,17 +193,8 @@
  * @param ... additional arguments
  * @return the length of the produced string or an error code from stdlib printf implementation
  */
-cx_attr_nonnull_arg(1, 2, 3, 4)
-cx_attr_printf(4, 5)
-cx_attr_cstr_arg(4)
-cx_attr_export
-int cx_sprintf_a(
-        const CxAllocator *alloc,
-        char **str,
-        size_t *len,
-        const char *fmt,
-        ...
-);
+cx_attr_nonnull_arg(1, 2, 3, 4) cx_attr_printf(4, 5) cx_attr_cstr_arg(4)
+CX_EXPORT int cx_sprintf_a(const CxAllocator *alloc, char **str, size_t *len, const char *fmt, ...);
 
 
 /**
@@ -268,18 +228,8 @@
  * @param ap argument list
  * @return the length of the produced string or an error code from stdlib printf implementation
  */
-cx_attr_nonnull
-cx_attr_cstr_arg(4)
-cx_attr_access_rw(2)
-cx_attr_access_rw(3)
-cx_attr_export
-int cx_vsprintf_a(
-        const CxAllocator *alloc,
-        char **str,
-        size_t *len,
-        const char *fmt,
-        va_list ap
-);
+cx_attr_nonnull  cx_attr_cstr_arg(4) cx_attr_access_rw(2) cx_attr_access_rw(3)
+CX_EXPORT int cx_vsprintf_a(const CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap);
 
 
 /**
@@ -325,21 +275,9 @@
  * @param ... additional arguments
  * @return the length of the produced string or an error code from stdlib printf implementation
  */
-cx_attr_nonnull_arg(1, 2, 4, 5)
-cx_attr_printf(5, 6)
-cx_attr_cstr_arg(5)
-cx_attr_access_rw(2)
-cx_attr_access_rw(3)
-cx_attr_access_rw(4)
-cx_attr_export
-int cx_sprintf_sa(
-        const CxAllocator *alloc,
-        char *buf,
-        size_t *len,
-        char **str,
-        const char *fmt,
-        ...
-);
+cx_attr_nonnull_arg(1, 2, 4, 5) cx_attr_printf(5, 6) cx_attr_cstr_arg(5)
+cx_attr_access_rw(2) cx_attr_access_rw(3) cx_attr_access_rw(4)
+CX_EXPORT int cx_sprintf_sa(const CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ...);
 
 /**
  * An @c sprintf like function which allocates a new string when the buffer is not large enough.
@@ -384,17 +322,8 @@
  * @param ap argument list
  * @return the length of the produced string or an error code from stdlib printf implementation
  */
-cx_attr_nonnull
-cx_attr_cstr_arg(5)
-cx_attr_export
-int cx_vsprintf_sa(
-        const CxAllocator *alloc,
-        char *buf,
-        size_t *len,
-        char **str,
-        const char *fmt,
-        va_list ap
-);
+cx_attr_nonnull cx_attr_cstr_arg(5)
+CX_EXPORT int cx_vsprintf_sa(const CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap);
 
 
 #ifdef __cplusplus
--- a/ucx/cx/properties.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/properties.h	Mon Nov 10 21:52:51 2025 +0100
@@ -94,8 +94,7 @@
 /**
  * Default properties configuration.
  */
-cx_attr_export
-extern const CxPropertiesConfig cx_properties_config_default;
+CX_EXPORT extern const CxPropertiesConfig cx_properties_config_default;
 
 /**
  * Status codes for the properties interface.
@@ -210,7 +209,6 @@
  * @retval zero success
  * @retval non-zero sinking the k/v-pair failed
  */
-cx_attr_nonnull
 typedef int(*cx_properties_sink_func)(
         CxProperties *prop,
         CxPropertiesSink *sink,
@@ -257,7 +255,6 @@
  * @retval zero success
  * @retval non-zero reading the data failed
  */
-cx_attr_nonnull
 typedef int(*cx_properties_read_func)(
         CxProperties *prop,
         CxPropertiesSource *src,
@@ -272,7 +269,6 @@
  * @retval zero initialization was successful
  * @retval non-zero otherwise
  */
-cx_attr_nonnull
 typedef int(*cx_properties_read_init_func)(
         CxProperties *prop,
         CxPropertiesSource *src
@@ -284,7 +280,6 @@
  * @param prop the properties interface that wants to read from the source
  * @param src the source
  */
-cx_attr_nonnull
 typedef void(*cx_properties_read_clean_func)(
         CxProperties *prop,
         CxPropertiesSource *src
@@ -331,8 +326,7 @@
  * @see cxPropertiesInitDefault()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config);
+CX_EXPORT void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config);
 
 /**
  * Destroys the properties interface.
@@ -346,8 +340,7 @@
  * @param prop the properties interface
  */
 cx_attr_nonnull
-cx_attr_export
-void cxPropertiesDestroy(CxProperties *prop);
+CX_EXPORT void cxPropertiesDestroy(CxProperties *prop);
 
 /**
  * Destroys and re-initializes the properties interface.
@@ -358,11 +351,7 @@
  * @param prop the properties interface
  */
 cx_attr_nonnull
-static inline void cxPropertiesReset(CxProperties *prop) {
-    CxPropertiesConfig config = prop->config;
-    cxPropertiesDestroy(prop);
-    cxPropertiesInit(prop, config);
-}
+CX_EXPORT void cxPropertiesReset(CxProperties *prop);
 
 /**
  * Initialize a properties parser with the default configuration.
@@ -371,7 +360,7 @@
  * @see cxPropertiesInit()
  */
 #define cxPropertiesInitDefault(prop) \
-    cxPropertiesInit(prop, cx_properties_config_default)
+        cxPropertiesInit(prop, cx_properties_config_default)
 
 /**
  * Fills the input buffer with data.
@@ -394,14 +383,8 @@
  * @retval non-zero a memory allocation was necessary but failed
  * @see cxPropertiesFill()
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-int cxPropertiesFilln(
-        CxProperties *prop,
-        const char *buf,
-        size_t len
-);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT int cxPropertiesFilln(CxProperties *prop, const char *buf, size_t len);
 
 /**
  * Internal function, do not use.
@@ -412,10 +395,7 @@
  * @retval non-zero a memory allocation was necessary but failed
  */
 cx_attr_nonnull
-static inline int cx_properties_fill(
-        CxProperties *prop,
-        cxstring str
-) {
+CX_INLINE int cx_properties_fill(CxProperties *prop, cxstring str) {
     return cxPropertiesFilln(prop, str.ptr, str.length);
 }
 
@@ -449,12 +429,7 @@
  * @param capacity the capacity of the stack memory
  */
 cx_attr_nonnull
-cx_attr_export
-void cxPropertiesUseStack(
-        CxProperties *prop,
-        char *buf,
-        size_t capacity
-);
+CX_EXPORT void cxPropertiesUseStack(CxProperties *prop, char *buf, size_t capacity);
 
 /**
  * Retrieves the next key/value-pair.
@@ -486,14 +461,8 @@
  * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter
  * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-CxPropertiesStatus cxPropertiesNext(
-        CxProperties *prop,
-        cxstring *key,
-        cxstring *value
-);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value);
 
 /**
  * Creates a properties sink for an UCX map.
@@ -509,10 +478,8 @@
  * @return the sink
  * @see cxPropertiesLoad()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-CxPropertiesSink cxPropertiesMapSink(CxMap *map);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT CxPropertiesSink cxPropertiesMapSink(CxMap *map);
 
 /**
  * Creates a properties source based on an UCX string.
@@ -522,8 +489,7 @@
  * @see cxPropertiesLoad()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxPropertiesSource cxPropertiesStringSource(cxstring str);
+CX_EXPORT CxPropertiesSource cxPropertiesStringSource(cxstring str);
 
 /**
  * Creates a properties source based on C string with the specified length.
@@ -533,11 +499,8 @@
  * @return the properties source
  * @see cxPropertiesLoad()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_access_r(1, 2)
-cx_attr_export
-CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len);
+cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2)
+CX_EXPORT CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len);
 
 /**
  * Creates a properties source based on a C string.
@@ -549,11 +512,8 @@
  * @return the properties source
  * @see cxPropertiesLoad()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_cstr_arg(1)
-cx_attr_export
-CxPropertiesSource cxPropertiesCstrSource(const char *str);
+cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1)
+CX_EXPORT CxPropertiesSource cxPropertiesCstrSource(const char *str);
 
 /**
  * Creates a properties source based on an FILE.
@@ -564,11 +524,8 @@
  * @return the properties source
  * @see cxPropertiesLoad()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_access_r(1)
-cx_attr_export
-CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size);
+cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1)
+CX_EXPORT CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size);
 
 
 /**
@@ -600,12 +557,8 @@
  * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed
  */
 cx_attr_nonnull
-cx_attr_export
-CxPropertiesStatus cxPropertiesLoad(
-        CxProperties *prop,
-        CxPropertiesSink sink,
-        CxPropertiesSource source
-);
+CX_EXPORT CxPropertiesStatus cxPropertiesLoad(CxProperties *prop,
+        CxPropertiesSink sink, CxPropertiesSource source);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/streams.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/streams.h	Mon Nov 10 21:52:51 2025 +0100
@@ -62,19 +62,10 @@
  * @return the total number of bytes copied
  */
 cx_attr_nonnull_arg(1, 2, 3, 4)
-cx_attr_access_r(1)
-cx_attr_access_w(2)
-cx_attr_access_w(5)
-cx_attr_export
-size_t cx_stream_bncopy(
-        void *src,
-        void *dest,
-        cx_read_func rfnc,
-        cx_write_func wfnc,
-        char *buf,
-        size_t bufsize,
-        size_t n
-);
+cx_attr_access_r(1) cx_attr_access_w(2) cx_attr_access_w(5)
+CX_EXPORT size_t cx_stream_bncopy(void *src, void *dest,
+        cx_read_func rfnc, cx_write_func wfnc,
+        char *buf, size_t bufsize, size_t n);
 
 /**
  * Reads data from a stream and writes it to another stream.
@@ -104,17 +95,9 @@
  * @param n the maximum number of bytes that shall be copied.
  * @return total number of bytes copied
  */
-cx_attr_nonnull
-cx_attr_access_r(1)
-cx_attr_access_w(2)
-cx_attr_export
-size_t cx_stream_ncopy(
-        void *src,
-        void *dest,
-        cx_read_func rfnc,
-        cx_write_func wfnc,
-        size_t n
-);
+cx_attr_nonnull cx_attr_access_r(1) cx_attr_access_w(2)
+CX_EXPORT size_t cx_stream_ncopy(void *src, void *dest,
+        cx_read_func rfnc, cx_write_func wfnc, size_t n);
 
 /**
  * Reads data from a stream and writes it to another stream.
--- a/ucx/cx/string.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/string.h	Mon Nov 10 21:52:51 2025 +0100
@@ -48,8 +48,7 @@
 /**
  * The maximum length of the "needle" in cx_strstr() that can use SBO.
  */
-cx_attr_export
-extern const unsigned cx_strstr_sbo_size;
+CX_EXPORT extern const unsigned cx_strstr_sbo_size;
 
 /**
  * The UCX string structure.
@@ -179,10 +178,8 @@
  *
  * @see cx_mutstrn()
  */
-cx_attr_nodiscard
-cx_attr_cstr_arg(1)
-cx_attr_export
-cxmutstr cx_mutstr(char *cstring);
+cx_attr_nodiscard cx_attr_cstr_arg(1)
+CX_EXPORT cxmutstr cx_mutstr(char *cstring);
 
 /**
  * Wraps a string that does not need to be zero-terminated.
@@ -200,13 +197,8 @@
  *
  * @see cx_mutstr()
  */
-cx_attr_nodiscard
-cx_attr_access_rw(1, 2)
-cx_attr_export
-cxmutstr cx_mutstrn(
-        char *cstring,
-        size_t length
-);
+cx_attr_nodiscard cx_attr_access_rw(1, 2)
+CX_EXPORT cxmutstr cx_mutstrn(char *cstring, size_t length);
 
 /**
  * Wraps a string that must be zero-terminated.
@@ -225,10 +217,8 @@
  *
  * @see cx_strn()
  */
-cx_attr_nodiscard
-cx_attr_cstr_arg(1)
-cx_attr_export
-cxstring cx_str(const char *cstring);
+cx_attr_nodiscard cx_attr_cstr_arg(1)
+CX_EXPORT cxstring cx_str(const char *cstring);
 
 
 /**
@@ -247,28 +237,27 @@
  *
  * @see cx_str()
  */
-cx_attr_nodiscard
-cx_attr_access_r(1, 2)
-cx_attr_export
-cxstring cx_strn(
-        const char *cstring,
-        size_t length
-);
+cx_attr_nodiscard cx_attr_access_r(1, 2)
+CX_EXPORT cxstring cx_strn(const char *cstring, size_t length);
 
 #ifdef __cplusplus
 } // extern "C"
 cx_attr_nodiscard
-static inline cxstring cx_strcast(cxmutstr str) {
+CX_CPPDECL cxstring cx_strcast(cxmutstr str) {
     return cx_strn(str.ptr, str.length);
 }
 cx_attr_nodiscard
-static inline cxstring cx_strcast(cxstring str) {
+CX_CPPDECL cxstring cx_strcast(cxstring str) {
     return str;
 }
 cx_attr_nodiscard
-static inline cxstring cx_strcast(const char *str) {
+CX_CPPDECL cxstring cx_strcast(const char *str) {
     return cx_str(str);
 }
+cx_attr_nodiscard
+CX_CPPDECL cxstring cx_strcast(const unsigned char *str) {
+    return cx_str(static_cast<const char*>(str));
+}
 extern "C" {
 #else
 /**
@@ -278,7 +267,7 @@
  * @see cx_strcast()
  */
 cx_attr_nodiscard
-static inline cxstring cx_strcast_m(cxmutstr str) {
+CX_INLINE cxstring cx_strcast_m(cxmutstr str) {
     return (cxstring) {str.ptr, str.length};
 }
 /**
@@ -288,7 +277,7 @@
  * @see cx_strcast()
  */
 cx_attr_nodiscard
-static inline cxstring cx_strcast_c(cxstring str) {
+CX_INLINE cxstring cx_strcast_c(cxstring str) {
     return str;
 }
 
@@ -299,7 +288,18 @@
  * @see cx_strcast()
  */
 cx_attr_nodiscard
-static inline cxstring cx_strcast_z(const char *str) {
+CX_INLINE cxstring cx_strcast_u(const unsigned char *str) {
+    return cx_str((const char*)str);
+}
+
+/**
+ * Internal function, do not use.
+ * @param str
+ * @return
+ * @see cx_strcast()
+ */
+cx_attr_nodiscard
+CX_INLINE cxstring cx_strcast_z(const char *str) {
     return cx_str(str);
 }
 
@@ -312,8 +312,8 @@
 #define cx_strcast(str) _Generic((str), \
         cxmutstr: cx_strcast_m, \
         cxstring: cx_strcast_c, \
-        const unsigned char*: cx_strcast_z, \
-        unsigned char *: cx_strcast_z, \
+        const unsigned char*: cx_strcast_u, \
+        unsigned char *: cx_strcast_u, \
         const char*: cx_strcast_z, \
         char *: cx_strcast_z) (str)
 #endif
@@ -330,8 +330,7 @@
  *
  * @param str the string to free
  */
-cx_attr_export
-void cx_strfree(cxmutstr *str);
+CX_EXPORT void cx_strfree(cxmutstr *str);
 
 /**
  * Passes the pointer in this string to the allocator's free function.
@@ -347,11 +346,7 @@
  * @param str the string to free
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-void cx_strfree_a(
-        const CxAllocator *alloc,
-        cxmutstr *str
-);
+CX_EXPORT void cx_strfree_a(const CxAllocator *alloc, cxmutstr *str);
 
 /**
  * Copies a string.
@@ -369,12 +364,7 @@
  * @retval non-zero if re-allocation failed
  */
 cx_attr_nonnull_arg(1)
-cx_attr_export
-int cx_strcpy_a(
-        const CxAllocator *alloc,
-        cxmutstr *dest,
-        cxstring src
-);
+CX_EXPORT int cx_strcpy_a(const CxAllocator *alloc, cxmutstr *dest, cxstring src);
 
 
 /**
@@ -406,11 +396,7 @@
  * @return the accumulated length of all strings
  */
 cx_attr_nodiscard
-cx_attr_export
-size_t cx_strlen(
-        size_t count,
-        ...
-);
+CX_EXPORT size_t cx_strlen(size_t count, ...);
 
 /**
  * Concatenates strings.
@@ -434,15 +420,9 @@
  * @param ...   all other UCX strings
  * @return the concatenated string
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-cxmutstr cx_strcat_ma(
-        const CxAllocator *alloc,
-        cxmutstr str,
-        size_t count,
-        ...
-);
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT cxmutstr cx_strcat_ma(const CxAllocator *alloc,
+        cxmutstr str, size_t count, ...);
 
 /**
  * Concatenates strings and returns a new string.
@@ -463,7 +443,7 @@
  * @return (@c cxmutstr) the concatenated string
  */
 #define cx_strcat_a(alloc, count, ...) \
-cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+        cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
 
 /**
  * Concatenates strings and returns a new string.
@@ -483,7 +463,7 @@
  * @return (@c cxmutstr) the concatenated string
  */
 #define cx_strcat(count, ...) \
-cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+        cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
 
 /**
  * Concatenates strings.
@@ -507,7 +487,7 @@
  * @return (@c cxmutstr) the concatenated string
  */
 #define cx_strcat_m(str, count, ...) \
-cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
+        cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
 
 /**
  * Returns a substring starting at the specified location.
@@ -525,11 +505,7 @@
  * @see cx_strsubsl_m()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxstring cx_strsubs(
-        cxstring string,
-        size_t start
-);
+CX_EXPORT cxstring cx_strsubs(cxstring string, size_t start);
 
 /**
  * Returns a substring starting at the specified location.
@@ -551,12 +527,7 @@
  * @see cx_strsubsl_m()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxstring cx_strsubsl(
-        cxstring string,
-        size_t start,
-        size_t length
-);
+CX_EXPORT cxstring cx_strsubsl(cxstring string, size_t start, size_t length);
 
 /**
  * Returns a substring starting at the specified location.
@@ -574,11 +545,7 @@
  * @see cx_strsubsl()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxmutstr cx_strsubs_m(
-        cxmutstr string,
-        size_t start
-);
+CX_EXPORT cxmutstr cx_strsubs_m(cxmutstr string, size_t start);
 
 /**
  * Returns a substring starting at the specified location.
@@ -600,12 +567,7 @@
  * @see cx_strsubsl()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxmutstr cx_strsubsl_m(
-        cxmutstr string,
-        size_t start,
-        size_t length
-);
+CX_EXPORT cxmutstr cx_strsubsl_m(cxmutstr string, size_t start, size_t length);
 
 /**
  * Returns a substring starting at the location of the first occurrence of the
@@ -620,11 +582,7 @@
  * @see cx_strchr_m()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxstring cx_strchr(
-        cxstring string,
-        int chr
-);
+CX_EXPORT cxstring cx_strchr(cxstring string, int chr);
 
 /**
  * Returns a substring starting at the location of the first occurrence of the
@@ -639,11 +597,7 @@
  * @see cx_strchr()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxmutstr cx_strchr_m(
-        cxmutstr string,
-        int chr
-);
+CX_EXPORT cxmutstr cx_strchr_m(cxmutstr string, int chr);
 
 /**
  * Returns a substring starting at the location of the last occurrence of the
@@ -658,11 +612,7 @@
  * @see cx_strrchr_m()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxstring cx_strrchr(
-        cxstring string,
-        int chr
-);
+CX_EXPORT cxstring cx_strrchr(cxstring string, int chr);
 
 /**
  * Returns a substring starting at the location of the last occurrence of the
@@ -677,11 +627,7 @@
  * @see cx_strrchr()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxmutstr cx_strrchr_m(
-        cxmutstr string,
-        int chr
-);
+CX_EXPORT cxmutstr cx_strrchr_m(cxmutstr string, int chr);
 
 /**
  * Returns a substring starting at the location of the first occurrence of the
@@ -700,11 +646,7 @@
  * @see cx_strstr_m()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxstring cx_strstr(
-        cxstring haystack,
-        cxstring needle
-);
+CX_EXPORT cxstring cx_strstr(cxstring haystack, cxstring needle);
 
 /**
  * Returns a substring starting at the location of the first occurrence of the
@@ -723,11 +665,7 @@
  * @see cx_strstr()
  */
 cx_attr_nodiscard
-cx_attr_export
-cxmutstr cx_strstr_m(
-        cxmutstr haystack,
-        cxstring needle
-);
+CX_EXPORT cxmutstr cx_strstr_m(cxmutstr haystack, cxstring needle);
 
 /**
  * Splits a given string using a delimiter string.
@@ -741,16 +679,9 @@
  * @param output a preallocated array of at least @p limit length
  * @return the actual number of split items
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_access_w(4, 3)
-cx_attr_export
-size_t cx_strsplit(
-        cxstring string,
-        cxstring delim,
-        size_t limit,
-        cxstring *output
-);
+cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3)
+CX_EXPORT size_t cx_strsplit(cxstring string, cxstring delim,
+        size_t limit, cxstring *output);
 
 /**
  * Splits a given string using a delimiter string.
@@ -771,17 +702,10 @@
  * written to
  * @return the actual number of split items
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_access_w(5)
-cx_attr_export
-size_t cx_strsplit_a(
-        const CxAllocator *allocator,
-        cxstring string,
-        cxstring delim,
-        size_t limit,
-        cxstring **output
-);
+cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5)
+CX_EXPORT size_t cx_strsplit_a(const CxAllocator *allocator,
+        cxstring string, cxstring delim,
+        size_t limit, cxstring **output);
 
 
 /**
@@ -796,16 +720,9 @@
  * @param output a preallocated array of at least @p limit length
  * @return the actual number of split items
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_access_w(4, 3)
-cx_attr_export
-size_t cx_strsplit_m(
-        cxmutstr string,
-        cxstring delim,
-        size_t limit,
-        cxmutstr *output
-);
+cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3)
+CX_EXPORT size_t cx_strsplit_m(cxmutstr string, cxstring delim,
+        size_t limit, cxmutstr *output);
 
 /**
  * Splits a given string using a delimiter string.
@@ -826,17 +743,10 @@
  * written to
  * @return the actual number of split items
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_access_w(5)
-cx_attr_export
-size_t cx_strsplit_ma(
-        const CxAllocator *allocator,
-        cxmutstr string,
-        cxstring delim,
-        size_t limit,
-        cxmutstr **output
-);
+cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5)
+CX_EXPORT size_t cx_strsplit_ma(const CxAllocator *allocator,
+        cxmutstr string, cxstring delim, size_t limit,
+        cxmutstr **output);
 
 /**
  * Compares two strings.
@@ -847,11 +757,17 @@
  * than @p s2, zero if both strings equal
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_strcmp(
-        cxstring s1,
-        cxstring s2
-);
+CX_EXPORT int cx_strcmp_(cxstring s1, cxstring s2);
+
+/**
+ * Compares two strings.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal
+ */
+#define cx_strcmp(s1, s2) cx_strcmp_(cx_strcast(s1), cx_strcast(s2))
 
 /**
  * Compares two strings ignoring case.
@@ -862,29 +778,33 @@
  * than @p s2, zero if both strings equal ignoring case
  */
 cx_attr_nodiscard
-cx_attr_export
-int cx_strcasecmp(
-        cxstring s1,
-        cxstring s2
-);
+CX_EXPORT int cx_strcasecmp_(cxstring s1, cxstring s2);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal ignoring case
+ */
+#define cx_strcasecmp(s1, s2) cx_strcasecmp_(cx_strcast(s1), cx_strcast(s2))
 
 /**
  * Compares two strings.
  *
  * This function has a compatible signature for the use as a cx_compare_func.
  *
+ * @attention This function can @em only compare UCX strings. It is unsafe to
+ * pass normal C-strings to this function.
+ *
  * @param s1 the first string
  * @param s2 the second string
  * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
  * than @p s2, zero if both strings equal
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-int cx_strcmp_p(
-        const void *s1,
-        const void *s2
-);
+cx_attr_nodiscard  cx_attr_nonnull
+CX_EXPORT int cx_strcmp_p(const void *s1, const void *s2);
 
 /**
  * Compares two strings ignoring case.
@@ -896,13 +816,8 @@
  * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
  * than @p s2, zero if both strings equal ignoring case
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-int cx_strcasecmp_p(
-        const void *s1,
-        const void *s2
-);
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT int cx_strcasecmp_p(const void *s1, const void *s2);
 
 
 /**
@@ -917,13 +832,8 @@
  * @return a duplicate of the string
  * @see cx_strdup()
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-cxmutstr cx_strdup_a_(
-        const CxAllocator *allocator,
-        cxstring string
-);
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT cxmutstr cx_strdup_a_(const CxAllocator *allocator, cxstring string);
 
 /**
  * Creates a duplicate of the specified string.
@@ -938,8 +848,7 @@
  * @see cx_strdup()
  * @see cx_strfree_a()
  */
-#define cx_strdup_a(allocator, string) \
-    cx_strdup_a_((allocator), cx_strcast(string))
+#define cx_strdup_a(allocator, string) cx_strdup_a_((allocator), cx_strcast(string))
 
 /**
  * Creates a duplicate of the specified string.
@@ -966,8 +875,7 @@
  * @return the trimmed string
  */
 cx_attr_nodiscard
-cx_attr_export
-cxstring cx_strtrim(cxstring string);
+CX_EXPORT cxstring cx_strtrim(cxstring string);
 
 /**
  * Omits leading and trailing spaces.
@@ -979,8 +887,7 @@
  * @return the trimmed string
  */
 cx_attr_nodiscard
-cx_attr_export
-cxmutstr cx_strtrim_m(cxmutstr string);
+CX_EXPORT cxmutstr cx_strtrim_m(cxmutstr string);
 
 /**
  * Checks if a string has a specific prefix.
@@ -991,11 +898,17 @@
  * @c false otherwise
  */
 cx_attr_nodiscard
-cx_attr_export
-bool cx_strprefix(
-        cxstring string,
-        cxstring prefix
-);
+CX_EXPORT bool cx_strprefix_(cxstring string, cxstring prefix);
+
+/**
+ * Checks if a string has a specific prefix.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return @c true, if and only if the string has the specified prefix,
+ * @c false otherwise
+ */
+#define cx_strprefix(string, prefix) cx_strprefix_(cx_strcast(string), cx_strcast(prefix))
 
 /**
  * Checks if a string has a specific suffix.
@@ -1006,11 +919,17 @@
  * @c false otherwise
  */
 cx_attr_nodiscard
-cx_attr_export
-bool cx_strsuffix(
-        cxstring string,
-        cxstring suffix
-);
+CX_EXPORT bool cx_strsuffix_(cxstring string, cxstring suffix);
+
+/**
+ * Checks if a string has a specific suffix.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return @c true, if and only if the string has the specified suffix,
+ * @c false otherwise
+ */
+#define cx_strsuffix(string, suffix) cx_strsuffix_(cx_strcast(string), cx_strcast(suffix))
 
 /**
  * Checks if a string has a specific prefix, ignoring the case.
@@ -1021,11 +940,17 @@
  * @c false otherwise
  */
 cx_attr_nodiscard
-cx_attr_export
-bool cx_strcaseprefix(
-        cxstring string,
-        cxstring prefix
-);
+CX_EXPORT bool cx_strcaseprefix_(cxstring string, cxstring prefix);
+
+/**
+ * Checks if a string has a specific prefix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return @c true, if and only if the string has the specified prefix,
+ * @c false otherwise
+ */
+#define cx_strcaseprefix(string, prefix) cx_strcaseprefix_(cx_strcast(string), cx_strcast(prefix))
 
 /**
  * Checks, if a string has a specific suffix, ignoring the case.
@@ -1036,11 +961,17 @@
  * @c false otherwise
  */
 cx_attr_nodiscard
-cx_attr_export
-bool cx_strcasesuffix(
-        cxstring string,
-        cxstring suffix
-);
+CX_EXPORT bool cx_strcasesuffix_(cxstring string, cxstring suffix);
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return @c true, if and only if the string has the specified suffix,
+ * @c false otherwise
+ */
+#define cx_strcasesuffix(string, suffix) cx_strcasesuffix_(cx_strcast(string), cx_strcast(suffix))
 
 /**
  * Replaces a string with another string.
@@ -1060,16 +991,9 @@
  * @param replmax maximum number of replacements
  * @return the resulting string after applying the replacements
  */
-cx_attr_nodiscard
-cx_attr_nonnull
-cx_attr_export
-cxmutstr cx_strreplacen_a(
-        const CxAllocator *allocator,
-        cxstring str,
-        cxstring search,
-        cxstring replacement,
-        size_t replmax
-);
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT cxmutstr cx_strreplacen_a(const CxAllocator *allocator,
+        cxstring str, cxstring search, cxstring replacement, size_t replmax);
 
 /**
  * Replaces a string with another string.
@@ -1089,7 +1013,7 @@
  * @return (@c cxmutstr) the resulting string after applying the replacements
  */
 #define cx_strreplacen(str, search, replacement, replmax) \
-cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax)
+        cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax)
 
 /**
  * Replaces a string with another string.
@@ -1107,7 +1031,7 @@
  * @return (@c cxmutstr) the resulting string after applying the replacements
  */
 #define cx_strreplace_a(allocator, str, search, replacement) \
-cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX)
+        cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX)
 
 /**
  * Replaces a string with another string.
@@ -1124,7 +1048,7 @@
  * @return (@c cxmutstr) the resulting string after applying the replacements
  */
 #define cx_strreplace(str, search, replacement) \
-cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX)
+        cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX)
 
 /**
  * Creates a string tokenization context.
@@ -1135,12 +1059,7 @@
  * @return a new string tokenization context
  */
 cx_attr_nodiscard
-cx_attr_export
-CxStrtokCtx cx_strtok_(
-        cxstring str,
-        cxstring delim,
-        size_t limit
-);
+CX_EXPORT CxStrtokCtx cx_strtok_(cxstring str, cxstring delim, size_t limit);
 
 /**
  * Creates a string tokenization context.
@@ -1151,7 +1070,7 @@
  * @return (@c CxStrtokCtx) a new string tokenization context
  */
 #define cx_strtok(str, delim, limit) \
-    cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit))
+        cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit))
 
 /**
  * Returns the next token.
@@ -1163,14 +1082,8 @@
  * @return true if successful, false if the limit or the end of the string
  * has been reached
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_access_w(2)
-cx_attr_export
-bool cx_strtok_next(
-        CxStrtokCtx *ctx,
-        cxstring *token
-);
+cx_attr_nonnull  cx_attr_nodiscard  cx_attr_access_w(2)
+CX_EXPORT bool cx_strtok_next(CxStrtokCtx *ctx, cxstring *token);
 
 /**
  * Returns the next token of a mutable string.
@@ -1186,14 +1099,8 @@
  * @return true if successful, false if the limit or the end of the string
  * has been reached
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_access_w(2)
-cx_attr_export
-bool cx_strtok_next_m(
-        CxStrtokCtx *ctx,
-        cxmutstr *token
-);
+cx_attr_nonnull  cx_attr_nodiscard  cx_attr_access_w(2)
+CX_EXPORT bool cx_strtok_next_m(CxStrtokCtx *ctx, cxmutstr *token);
 
 /**
  * Defines an array of more delimiters for the specified tokenization context.
@@ -1202,14 +1109,8 @@
  * @param delim array of more delimiters
  * @param count number of elements in the array
  */
-cx_attr_nonnull
-cx_attr_access_r(2, 3)
-cx_attr_export
-void cx_strtok_delim(
-        CxStrtokCtx *ctx,
-        const cxstring *delim,
-        size_t count
-);
+cx_attr_nonnull cx_attr_access_r(2, 3)
+CX_EXPORT void cx_strtok_delim(CxStrtokCtx *ctx, const cxstring *delim, size_t count);
 
 /* ------------------------------------------------------------------------- *
  *                string to number conversion functions                      *
@@ -1229,8 +1130,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1246,8 +1147,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1263,8 +1164,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1280,8 +1181,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1297,8 +1198,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1314,8 +1215,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1331,8 +1232,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1348,8 +1249,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1365,8 +1266,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1382,8 +1283,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1399,8 +1300,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1416,8 +1317,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1433,8 +1334,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1450,8 +1351,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1467,8 +1368,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1484,8 +1385,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a number.
@@ -1501,8 +1402,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep);
 
 /**
  * Converts a string to a single precision floating-point number.
@@ -1518,8 +1419,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep);
 
 /**
  * Converts a string to a double precision floating-point number.
@@ -1535,8 +1436,8 @@
  * @retval zero success
  * @retval non-zero conversion was not possible
  */
-cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
-int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep);
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+CX_EXPORT int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep);
 
 /**
  * Converts a string to a number.
--- a/ucx/cx/test.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/test.h	Mon Nov 10 21:52:51 2025 +0100
@@ -136,10 +136,7 @@
  * @param name optional name of the suite
  * @return a new test suite
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_cstr_arg(1)
-cx_attr_malloc
+cx_attr_nonnull cx_attr_nodiscard  cx_attr_cstr_arg(1) cx_attr_malloc
 static inline CxTestSuite* cx_test_suite_new(const char *name) {
     CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite));
     if (suite != NULL) {
@@ -157,7 +154,7 @@
  *
  * @param suite the test suite to free
  */
-static inline void cx_test_suite_free(CxTestSuite* suite) {
+CX_INLINE void cx_test_suite_free(CxTestSuite* suite) {
     if (suite == NULL) return;
     CxTestSet *l = suite->tests;
     while (l != NULL) {
@@ -177,7 +174,7 @@
  * @retval non-zero failure
  */
 cx_attr_nonnull
-static inline int cx_test_register(CxTestSuite* suite, CxTest test) {
+CX_INLINE int cx_test_register(CxTestSuite* suite, CxTest test) {
     CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet));
     if (t) {
         t->test = test;
@@ -204,8 +201,7 @@
  * @param out_writer the write function writing to @p out_target
  */
 cx_attr_nonnull
-static inline void cx_test_run(CxTestSuite *suite,
-                               void *out_target, cx_write_func out_writer) {
+CX_INLINE void cx_test_run(CxTestSuite *suite, void *out_target, cx_write_func out_writer) {
     if (suite->name == NULL) {
         out_writer("*** Test Suite ***\n", 1, 19, out_target);
     } else {
--- a/ucx/cx/tree.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/cx/tree.h	Mon Nov 10 21:52:51 2025 +0100
@@ -212,24 +212,14 @@
  * @param iter the iterator
  */
 cx_attr_nonnull
-static inline void cxTreeIteratorDispose(CxTreeIterator *iter) {
-    cxFreeDefault(iter->stack);
-    iter->stack = NULL;
-}
+CX_EXPORT void cxTreeIteratorDispose(CxTreeIterator *iter);
 
 /**
  * Releases internal memory of the given tree visitor.
  * @param visitor the visitor
  */
 cx_attr_nonnull
-static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) {
-    struct cx_tree_visitor_queue_s *q = visitor->queue_next;
-    while (q != NULL) {
-        struct cx_tree_visitor_queue_s *next = q->next;
-        cxFreeDefault(q);
-        q = next;
-    }
-}
+CX_EXPORT void cxTreeVisitorDispose(CxTreeVisitor *visitor);
 
 /**
  * Advises the iterator to skip the subtree below the current node and
@@ -265,16 +255,9 @@
  * @see cx_tree_unlink()
  */
 cx_attr_nonnull
-cx_attr_export
-void cx_tree_link(
-        void *parent,
-        void *node,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+CX_EXPORT void cx_tree_link(void *parent, void *node,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Unlinks a node from its parent.
@@ -291,15 +274,9 @@
  * @see cx_tree_link()
  */
 cx_attr_nonnull
-cx_attr_export
-void cx_tree_unlink(
-        void *node,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+CX_EXPORT void cx_tree_unlink(void *node,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Macro that can be used instead of the magic value for infinite search depth.
@@ -332,7 +309,6 @@
  * positive if one of the children might contain the data,
  * negative if neither the node nor the children contains the data
  */
-cx_attr_nonnull
 typedef int (*cx_tree_search_data_func)(const void *node, const void *data);
 
 
@@ -362,7 +338,6 @@
  * positive if one of the children might contain the data,
  * negative if neither the node nor the children contains the data
  */
-cx_attr_nonnull
 typedef int (*cx_tree_search_func)(const void *node, const void *new_node);
 
 /**
@@ -389,18 +364,10 @@
  * could contain the node (but doesn't right now), negative if the tree does not
  * contain any node that might be related to the searched data
  */
-cx_attr_nonnull
-cx_attr_access_w(5)
-cx_attr_export
-int cx_tree_search_data(
-        const void *root,
-        size_t depth,
-        const void *data,
-        cx_tree_search_data_func sfunc,
-        void **result,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull cx_attr_access_w(5)
+CX_EXPORT int cx_tree_search_data(const void *root, size_t depth,
+        const void *data, cx_tree_search_data_func sfunc,
+        void **result, ptrdiff_t loc_children, ptrdiff_t loc_next);
 
 /**
  * Searches for a node in a tree.
@@ -426,18 +393,10 @@
  * could contain the node (but doesn't right now), negative if the tree does not
  * contain any node that might be related to the searched data
  */
-cx_attr_nonnull
-cx_attr_access_w(5)
-cx_attr_export
-int cx_tree_search(
-        const void *root,
-        size_t depth,
-        const void *node,
-        cx_tree_search_func sfunc,
-        void **result,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull cx_attr_access_w(5)
+CX_EXPORT int cx_tree_search(const void *root, size_t depth,
+        const void *node, cx_tree_search_func sfunc,
+        void **result, ptrdiff_t loc_children, ptrdiff_t loc_next);
 
 /**
  * Creates a depth-first iterator for a tree with the specified root node.
@@ -460,13 +419,8 @@
  * @see cxTreeIteratorDispose()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxTreeIterator cx_tree_iterator(
-        void *root,
-        bool visit_on_exit,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_next
-);
+CX_EXPORT CxTreeIterator cx_tree_iterator(void *root, bool visit_on_exit,
+        ptrdiff_t loc_children, ptrdiff_t loc_next);
 
 /**
  * Creates a breadth-first iterator for a tree with the specified root node.
@@ -487,12 +441,8 @@
  * @see cxTreeVisitorDispose()
  */
 cx_attr_nodiscard
-cx_attr_export
-CxTreeVisitor cx_tree_visitor(
-        void *root,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_next
-);
+CX_EXPORT CxTreeVisitor cx_tree_visitor(void *root,
+        ptrdiff_t loc_children, ptrdiff_t loc_next);
 
 /**
  * Describes a function that creates a tree node from the specified data.
@@ -504,7 +454,6 @@
  * @note the function may leave the node pointers in the struct uninitialized.
  * The caller is responsible to set them according to the intended use case.
  */
-cx_attr_nonnull_arg(1)
 typedef void *(*cx_tree_node_create_func)(const void *, void *);
 
 /**
@@ -513,8 +462,7 @@
  * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to
  * implement optimized insertion of multiple elements into a tree.
  */
-cx_attr_export
-extern unsigned int cx_tree_add_look_around_depth;
+CX_EXPORT extern unsigned int cx_tree_add_look_around_depth;
 
 /**
  * Adds multiple elements efficiently to a tree.
@@ -554,23 +502,12 @@
  * @return the number of nodes created and added
  * @see cx_tree_add()
  */
-cx_attr_nonnull_arg(1, 3, 4, 6, 7)
-cx_attr_access_w(6)
-cx_attr_export
-size_t cx_tree_add_iter(
-        struct cx_iterator_base_s *iter,
-        size_t num,
-        cx_tree_search_func sfunc,
-        cx_tree_node_create_func cfunc,
-        void *cdata,
-        void **failed,
-        void *root,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull_arg(1, 3, 4, 6, 7) cx_attr_access_w(6)
+CX_EXPORT size_t cx_tree_add_iter(struct cx_iterator_base_s *iter, size_t num,
+        cx_tree_search_func sfunc, cx_tree_node_create_func cfunc,
+        void *cdata, void **failed, void *root,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Adds multiple elements efficiently to a tree.
@@ -609,24 +546,12 @@
  * @return the number of array elements successfully processed
  * @see cx_tree_add()
  */
-cx_attr_nonnull_arg(1, 4, 5, 7, 8)
-cx_attr_access_w(7)
-cx_attr_export
-size_t cx_tree_add_array(
-        const void *src,
-        size_t num,
-        size_t elem_size,
-        cx_tree_search_func sfunc,
-        cx_tree_node_create_func cfunc,
-        void *cdata,
-        void **failed,
-        void *root,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull_arg(1, 4, 5, 7, 8) cx_attr_access_w(7)
+CX_EXPORT size_t cx_tree_add_array(const void *src, size_t num, size_t elem_size,
+        cx_tree_search_func sfunc, cx_tree_node_create_func cfunc,
+        void *cdata, void **failed, void *root,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Adds data to a tree.
@@ -673,22 +598,12 @@
  * @return zero when a new node was created and added to the tree,
  * non-zero otherwise
  */
-cx_attr_nonnull_arg(1, 2, 3, 5, 6)
-cx_attr_access_w(5)
-cx_attr_export
-int cx_tree_add(
-        const void *src,
-        cx_tree_search_func sfunc,
-        cx_tree_node_create_func cfunc,
-        void *cdata,
-        void **cnode,
-        void *root,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull_arg(1, 2, 3, 5, 6) cx_attr_access_w(5)
+CX_EXPORT int cx_tree_add(const void *src,
+        cx_tree_search_func sfunc, cx_tree_node_create_func cfunc,
+        void *cdata, void **cnode, void *root,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 
 /**
@@ -850,10 +765,7 @@
      * Implementations SHALL NOT simply invoke @p insert_many as this comes
      * with too much overhead.
      */
-    int (*insert_element)(
-            struct cx_tree_s *tree,
-            const void *data
-    );
+    int (*insert_element)(struct cx_tree_s *tree, const void *data);
 
     /**
      * Member function for inserting multiple elements.
@@ -861,21 +773,12 @@
      * Implementations SHALL avoid performing a full search in the tree for
      * every element even though the source data MAY be unsorted.
      */
-    size_t (*insert_many)(
-            struct cx_tree_s *tree,
-            struct cx_iterator_base_s *iter,
-            size_t n
-    );
+    size_t (*insert_many)(struct cx_tree_s *tree, struct cx_iterator_base_s *iter, size_t n);
 
     /**
      * Member function for finding a node.
      */
-    void *(*find)(
-            struct cx_tree_s *tree,
-            const void *subtree,
-            const void *data,
-            size_t depth
-    );
+    void *(*find)(struct cx_tree_s *tree, const void *subtree, const void *data, size_t depth);
 };
 
 /**
@@ -906,8 +809,7 @@
  * @see cxTreeFree()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxTreeDestroySubtree(CxTree *tree, void *node);
+CX_EXPORT void cxTreeDestroySubtree(CxTree *tree, void *node);
 
 
 /**
@@ -945,8 +847,7 @@
  *
  * @param tree the tree to free
  */
-cx_attr_export
-void cxTreeFree(CxTree *tree);
+CX_EXPORT void cxTreeFree(CxTree *tree);
 
 /**
  * Creates a new tree structure based on the specified layout.
@@ -972,22 +873,11 @@
  * @see cxTreeCreateSimple()
  * @see cxTreeCreateWrapped()
  */
-cx_attr_nonnull_arg(2, 3, 4)
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc(cxTreeFree, 1)
-cx_attr_export
-CxTree *cxTreeCreate(
-        const CxAllocator *allocator,
-        cx_tree_node_create_func create_func,
-        cx_tree_search_func search_func,
-        cx_tree_search_data_func search_data_func,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull_arg(2, 3, 4) cx_attr_nodiscard  cx_attr_malloc cx_attr_dealloc(cxTreeFree, 1)
+CX_EXPORT CxTree *cxTreeCreate(const CxAllocator *allocator, cx_tree_node_create_func create_func,
+        cx_tree_search_func search_func, cx_tree_search_data_func search_data_func,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Creates a new tree structure based on a default layout.
@@ -1006,10 +896,8 @@
  * @return (@c CxTree*) the new tree
  * @see cxTreeCreate()
  */
-#define cxTreeCreateSimple(\
-    allocator, create_func, search_func, search_data_func \
-) cxTreeCreate(allocator, create_func, search_func, search_data_func, \
-cx_tree_node_base_layout)
+#define cxTreeCreateSimple(allocator, create_func, search_func, search_data_func) \
+        cxTreeCreate(allocator, create_func, search_func, search_data_func, cx_tree_node_base_layout)
 
 /**
  * Creates a new tree structure based on an existing tree.
@@ -1033,20 +921,10 @@
  * @return the new tree
  * @see cxTreeCreate()
  */
-cx_attr_nonnull_arg(2)
-cx_attr_nodiscard
-cx_attr_malloc
-cx_attr_dealloc(cxTreeFree, 1)
-cx_attr_export
-CxTree *cxTreeCreateWrapped(
-        const CxAllocator *allocator,
-        void *root,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-);
+cx_attr_nonnull_arg(2) cx_attr_nodiscard  cx_attr_malloc  cx_attr_dealloc(cxTreeFree, 1)
+CX_EXPORT CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next);
 
 /**
  * Inserts data into the tree.
@@ -1061,12 +939,7 @@
  * @retval non-zero failure
  */
 cx_attr_nonnull
-static inline int cxTreeInsert(
-        CxTree *tree,
-        const void *data
-) {
-    return tree->cl->insert_element(tree, data);
-}
+CX_EXPORT int cxTreeInsert(CxTree *tree, const void *data);
 
 /**
  * Inserts elements provided by an iterator efficiently into the tree.
@@ -1081,13 +954,7 @@
  * @return the number of elements that could be successfully inserted
  */
 cx_attr_nonnull
-static inline size_t cxTreeInsertIter(
-        CxTree *tree,
-        CxIteratorBase *iter,
-        size_t n
-) {
-    return tree->cl->insert_many(tree, iter, n);
-}
+CX_EXPORT size_t cxTreeInsertIter(CxTree *tree, CxIteratorBase *iter, size_t n);
 
 /**
  * Inserts an array of data efficiently into the tree.
@@ -1103,17 +970,7 @@
  * @return the number of elements that could be successfully inserted
  */
 cx_attr_nonnull
-static inline size_t cxTreeInsertArray(
-        CxTree *tree,
-        const void *data,
-        size_t elem_size,
-        size_t n
-) {
-    if (n == 0) return 0;
-    if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0;
-    CxIterator iter = cxIterator(data, elem_size, n);
-    return cxTreeInsertIter(tree, cxIteratorRef(iter), n);
-}
+CX_EXPORT size_t cxTreeInsertArray(CxTree *tree, const void *data, size_t elem_size, size_t n);
 
 /**
  * Searches the data in the specified tree.
@@ -1126,14 +983,8 @@
  * @param data the data to search for
  * @return the first matching node, or @c NULL when the data cannot be found
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline void *cxTreeFind(
-        CxTree *tree,
-        const void *data
-) {
-    return tree->cl->find(tree, tree->root, data, 0);
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT void *cxTreeFind(CxTree *tree, const void *data);
 
 /**
  * Searches the data in the specified subtree.
@@ -1154,16 +1005,8 @@
  * @param max_depth the maximum search depth
  * @return the first matching node, or @c NULL when the data cannot be found
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline void *cxTreeFindInSubtree(
-        CxTree *tree,
-        const void *data,
-        void *subtree_root,
-        size_t max_depth
-) {
-    return tree->cl->find(tree, subtree_root, data, max_depth);
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT void *cxTreeFindInSubtree(CxTree *tree, const void *data, void *subtree_root, size_t max_depth);
 
 /**
  * Determines the size of the specified subtree.
@@ -1172,10 +1015,8 @@
  * @param subtree_root the root node of the subtree
  * @return the number of nodes in the specified subtree
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root);
 
 /**
  * Determines the depth of the specified subtree.
@@ -1184,10 +1025,8 @@
  * @param subtree_root the root node of the subtree
  * @return the tree depth including the @p subtree_root
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root);
+cx_attr_nonnull  cx_attr_nodiscard
+CX_EXPORT size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root);
 
 /**
  * Determines the size of the entire tree.
@@ -1195,11 +1034,8 @@
  * @param tree the tree
  * @return the tree size, counting the root as one
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline size_t cxTreeSize(CxTree *tree) {
-    return tree->size;
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT size_t cxTreeSize(CxTree *tree);
 
 /**
  * Determines the depth of the entire tree.
@@ -1207,10 +1043,8 @@
  * @param tree the tree
  * @return the tree depth, counting the root as one
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-cx_attr_export
-size_t cxTreeDepth(CxTree *tree);
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT size_t cxTreeDepth(CxTree *tree);
 
 /**
  * Creates a depth-first iterator for the specified tree starting in @p node.
@@ -1224,18 +1058,8 @@
  * @return a tree iterator (depth-first)
  * @see cxTreeVisit()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline CxTreeIterator cxTreeIterateSubtree(
-        CxTree *tree,
-        void *node,
-        bool visit_on_exit
-) {
-    return cx_tree_iterator(
-            node, visit_on_exit,
-            tree->loc_children, tree->loc_next
-    );
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit);
 
 /**
  * Creates a breadth-first iterator for the specified tree starting in @p node.
@@ -1247,13 +1071,8 @@
  * @return a tree visitor (a.k.a. breadth-first iterator)
  * @see cxTreeIterate()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) {
-    return cx_tree_visitor(
-            node, tree->loc_children, tree->loc_next
-    );
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node);
 
 /**
  * Creates a depth-first iterator for the specified tree.
@@ -1264,14 +1083,8 @@
  * @return a tree iterator (depth-first)
  * @see cxTreeVisit()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline CxTreeIterator cxTreeIterate(
-        CxTree *tree,
-        bool visit_on_exit
-) {
-    return cxTreeIterateSubtree(tree, tree->root, visit_on_exit);
-}
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT CxTreeIterator cxTreeIterate(CxTree *tree, bool visit_on_exit);
 
 /**
  * Creates a breadth-first iterator for the specified tree.
@@ -1280,11 +1093,8 @@
  * @return a tree visitor (a.k.a. breadth-first iterator)
  * @see cxTreeIterate()
  */
-cx_attr_nonnull
-cx_attr_nodiscard
-static inline CxTreeVisitor cxTreeVisit(CxTree *tree) {
-    return cxTreeVisitSubtree(tree, tree->root);
-}
+cx_attr_nonnull cx_attr_nodiscard
+CxTreeVisitor cxTreeVisit(CxTree *tree);
 
 /**
  * Sets the (new) parent of the specified child.
@@ -1298,12 +1108,7 @@
  * @see cxTreeAddChildNode()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxTreeSetParent(
-        CxTree *tree,
-        void *parent,
-        void *child
-);
+CX_EXPORT void cxTreeSetParent(CxTree *tree, void *parent, void *child);
 
 /**
  * Adds a new node to the tree.
@@ -1321,12 +1126,7 @@
  * @see cxTreeSetParent()
  */
 cx_attr_nonnull
-cx_attr_export
-void cxTreeAddChildNode(
-        CxTree *tree,
-        void *parent,
-        void *child
-);
+CX_EXPORT void cxTreeAddChildNode(CxTree *tree, void *parent, void *child);
 
 /**
  * Creates a new node and adds it to the tree.
@@ -1346,12 +1146,7 @@
  * @see cxTreeInsert()
  */
 cx_attr_nonnull
-cx_attr_export
-int cxTreeAddChild(
-        CxTree *tree,
-        void *parent,
-        const void *data
-);
+CX_EXPORT int cxTreeAddChild(CxTree *tree, void *parent, const void *data);
 
 /**
  * A function that is invoked when a node needs to be re-linked to a new parent.
@@ -1365,7 +1160,6 @@
  * @param old_parent the old parent of the node
  * @param new_parent the new parent of the node
  */
-cx_attr_nonnull
 typedef void (*cx_tree_relink_func)(
         void *node,
         const void *old_parent,
@@ -1387,12 +1181,7 @@
  * @return zero on success, non-zero if @p node is the root node of the tree
  */
 cx_attr_nonnull_arg(1, 2)
-cx_attr_export
-int cxTreeRemoveNode(
-        CxTree *tree,
-        void *node,
-        cx_tree_relink_func relink_func
-);
+CX_EXPORT int cxTreeRemoveNode(CxTree *tree, void *node, cx_tree_relink_func relink_func);
 
 /**
  * Removes a node and its subtree from the tree.
@@ -1406,8 +1195,7 @@
  * @param node the node to remove
  */
 cx_attr_nonnull
-cx_attr_export
-void cxTreeRemoveSubtree(CxTree *tree, void *node);
+CX_EXPORT void cxTreeRemoveSubtree(CxTree *tree, void *node);
 
 /**
  * Destroys a node and re-links its children to its former parent.
@@ -1428,12 +1216,7 @@
  * @return zero on success, non-zero if @p node is the root node of the tree
  */
 cx_attr_nonnull_arg(1, 2)
-cx_attr_export
-int cxTreeDestroyNode(
-        CxTree *tree,
-        void *node,
-        cx_tree_relink_func relink_func
-);
+CX_EXPORT int cxTreeDestroyNode(CxTree *tree, void *node, cx_tree_relink_func relink_func);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/utils.h	Sun Oct 19 21:20:08 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-/**
- * \file utils.h
- *
- * \brief General purpose utility functions.
- *
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
- */
-
-#ifndef UCX_UTILS_H
-#define UCX_UTILS_H
-
-#include "common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Convenience macro for a for loop that counts from zero to n-1.
- */
-#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++)
-
-/**
- * Convenience macro for swapping two pointers.
- */
-#ifdef __cplusplus
-#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
-#else
-#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
-#endif
-
-// cx_szmul() definition
-
-#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN)
-#define CX_SZMUL_BUILTIN
-
-/**
- * Alias for \c __builtin_mul_overflow.
- *
- * Performs a multiplication of size_t values and checks for overflow.
- *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t, where the result should
- * be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result)
-
-#else // no GNUC or clang bultin
-
-/**
- * Performs a multiplication of size_t values and checks for overflow.
-  *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t, where the result should
- * be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result)
-
-/**
- * Performs a multiplication of size_t values and checks for overflow.
- *
- * This is a custom implementation in case there is no compiler builtin
- * available.
- *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t where the result should be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-int cx_szmul_impl(size_t a, size_t b, size_t *result);
-
-#endif // cx_szmul
-
-
-/**
- * Reads data from a stream and writes it to another stream.
- *
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param buf a pointer to the copy buffer or \c NULL if a buffer
- * shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
- * set this to zero to let the implementation decide
- * @param n the maximum number of bytes that shall be copied.
- * If this is larger than \p bufsize, the content is copied over multiple
- * iterations.
- * @return the total number of bytes copied
- */
-__attribute__((__nonnull__(1, 2, 3, 4)))
-size_t cx_stream_bncopy(
-        void *src,
-        void *dest,
-        cx_read_func rfnc,
-        cx_write_func wfnc,
-        char *buf,
-        size_t bufsize,
-        size_t n
-);
-
-/**
- * Reads data from a stream and writes it to another stream.
- *
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param buf a pointer to the copy buffer or \c NULL if a buffer
- * shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
- * set this to zero to let the implementation decide
- * @return total number of bytes copied
- */
-#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \
-    cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX)
-
-/**
- * Reads data from a stream and writes it to another stream.
- *
- * The data is temporarily stored in a stack allocated buffer.
- *
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param n the maximum number of bytes that shall be copied.
- * @return total number of bytes copied
- */
-__attribute__((__nonnull__))
-size_t cx_stream_ncopy(
-        void *src,
-        void *dest,
-        cx_read_func rfnc,
-        cx_write_func wfnc,
-        size_t n
-);
-
-/**
- * Reads data from a stream and writes it to another stream.
- *
- * The data is temporarily stored in a stack allocated buffer.
- *
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @return total number of bytes copied
- */
-#define cx_stream_copy(src, dest, rfnc, wfnc) \
-    cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX)
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // UCX_UTILS_H
--- a/ucx/hash_key.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/hash_key.c	Mon Nov 10 21:52:51 2025 +0100
@@ -105,6 +105,22 @@
     return key;
 }
 
+CxHashKey cx_hash_key_ustr(unsigned const char *str) {
+    CxHashKey key;
+    key.data = str;
+    key.len = str == NULL ? 0 : strlen((const char*)str);
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key_cxstr(cxstring str) {
+    return cx_hash_key(str.ptr, str.length);
+}
+
+CxHashKey cx_hash_key_mutstr(cxmutstr str) {
+    return cx_hash_key(str.ptr, str.length);
+}
+
 CxHashKey cx_hash_key_bytes(
         const unsigned char *bytes,
         size_t len
@@ -143,7 +159,9 @@
     return key;
 }
 
-int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right) {
+int cx_hash_key_cmp(const void *l, const void *r) {
+    const CxHashKey *left = l;
+    const CxHashKey *right = r;
     int d;
     d = cx_vcmp_uint64(left->hash, right->hash);
     if (d != 0) return d;
--- a/ucx/hash_map.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/hash_map.c	Mon Nov 10 21:52:51 2025 +0100
@@ -86,7 +86,7 @@
     struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
     const CxAllocator *allocator = map->collection.allocator;
 
-    unsigned hash = key.hash;
+    uint64_t hash = key.hash;
     if (hash == 0) {
         cx_hash_murmur(&key);
         hash = key.hash;
@@ -203,7 +203,7 @@
 ) {
     struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
 
-    unsigned hash = key.hash;
+    uint64_t hash = key.hash;
     if (hash == 0) {
         cx_hash_murmur(&key);
         hash = key.hash;
@@ -281,7 +281,7 @@
 
 static void cx_hash_map_iter_next(void *it) {
     CxMapIterator *iter = it;
-    CxMap *map = iter->map.m;
+    CxMap *map = iter->map;
     struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map;
     struct cx_hash_map_element_s *elm = iter->elem;
 
@@ -329,7 +329,7 @@
     // must not modify the iterator (the parameter is const)
     if (elm != NULL) {
         iter->entry.key = &elm->key;
-        if (iter->map.c->collection.store_pointer) {
+        if (map->collection.store_pointer) {
             iter->entry.value = *(void **) elm->data;
         } else {
             iter->entry.value = elm->data;
@@ -343,7 +343,7 @@
 ) {
     CxMapIterator iter;
 
-    iter.map.c = map;
+    iter.map = (CxMap*) map;
     iter.elem_count = map->collection.size;
 
     switch (type) {
@@ -366,7 +366,7 @@
     iter.base.valid = cx_hash_map_iter_valid;
     iter.base.next = cx_hash_map_iter_next;
     iter.base.remove = false;
-    iter.base.mutating = false;
+    iter.base.allow_remove = true;
 
     iter.slot = 0;
     iter.index = 0;
--- a/ucx/iterator.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/iterator.c	Mon Nov 10 21:52:51 2025 +0100
@@ -53,7 +53,7 @@
         // only move the last element when we are not currently aiming
         // at the last element already
         if (iter->index < iter->elem_count) {
-            void *last = ((char *) iter->src_handle.m)
+            void *last = ((char *) iter->src_handle)
                          + iter->elem_count * iter->elem_size;
             memcpy(iter->elem_handle, last, iter->elem_size);
         }
@@ -84,8 +84,8 @@
     }
 }
 
-CxIterator cxMutIterator(
-        void *array,
+CxIterator cxIterator(
+        const void *array,
         size_t elem_size,
         size_t elem_count,
         bool remove_keeps_order
@@ -93,44 +93,25 @@
     CxIterator iter;
 
     iter.index = 0;
-    iter.src_handle.m = array;
-    iter.elem_handle = array;
+    iter.src_handle = (void*) array;
+    iter.elem_handle = (void*) array;
     iter.elem_size = elem_size;
     iter.elem_count = array == NULL ? 0 : elem_count;
     iter.base.valid = cx_iter_valid;
     iter.base.current = cx_iter_current;
     iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast;
     iter.base.remove = false;
-    iter.base.mutating = true;
-
-    return iter;
-}
+    iter.base.allow_remove = true;
 
-CxIterator cxIterator(
-        const void *array,
-        size_t elem_size,
-        size_t elem_count
-) {
-    CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false);
-    iter.base.mutating = false;
-    return iter;
-}
-
-CxIterator cxMutIteratorPtr(
-        void *array,
-        size_t elem_count,
-        bool remove_keeps_order
-) {
-    CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order);
-    iter.base.current = cx_iter_current_ptr;
     return iter;
 }
 
 CxIterator cxIteratorPtr(
         const void *array,
-        size_t elem_count
+        size_t elem_count,
+        bool remove_keeps_order
 ) {
-    CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false);
-    iter.base.mutating = false;
+    CxIterator iter = cxIterator(array, sizeof(void*), elem_count, remove_keeps_order);
+    iter.base.current = cx_iter_current_ptr;
     return iter;
 }
--- a/ucx/json.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/json.c	Mon Nov 10 21:52:51 2025 +0100
@@ -630,6 +630,12 @@
     }
 }
 
+void cxJsonReset(CxJson *json) {
+    const CxAllocator *allocator = json->allocator;
+    cxJsonDestroy(json);
+    cxJsonInit(json, allocator);
+}
+
 int cxJsonFilln(CxJson *json, const char *buf, size_t size) {
     if (cxBufferEof(&json->buffer)) {
         // reinitialize the buffer
@@ -1126,10 +1132,39 @@
     return ret;
 }
 
+char *cxJsonAsString(const CxJsonValue *value) {
+    return value->value.string.ptr;
+}
+
+cxstring cxJsonAsCxString(const CxJsonValue *value) {
+    return cx_strcast(value->value.string);
+}
+
+cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) {
+    return value->value.string;
+}
+
+double cxJsonAsDouble(const CxJsonValue *value) {
+    if (value->type == CX_JSON_INTEGER) {
+        return (double) value->value.integer;
+    } else {
+        return value->value.number;
+    }
+}
+
+int64_t cxJsonAsInteger(const CxJsonValue *value) {
+    if (value->type == CX_JSON_INTEGER) {
+        return value->value.integer;
+    } else {
+        return (int64_t) value->value.number;
+    }
+}
+
 CxIterator cxJsonArrIter(const CxJsonValue *value) {
     return cxIteratorPtr(
         value->value.array.array,
-        value->value.array.array_size
+        value->value.array.array_size,
+        true // arrays need to keep order
     );
 }
 
@@ -1137,7 +1172,8 @@
     return cxIterator(
         value->value.object.values,
         sizeof(CxJsonObjValue),
-        value->value.object.values_size
+        value->value.object.values_size,
+        true // TODO: objects do not always need to keep order
     );
 }
 
@@ -1157,7 +1193,7 @@
     } else {
         CxJsonObjValue kv = value->value.object.values[index];
         cx_strfree_a(value->allocator, &kv.name);
-        // TODO: replace with cx_array_remove()
+        // TODO: replace with cx_array_remove() / cx_array_remove_fast()
         value->value.object.values_size--;
         memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue));
         return kv.value;
--- a/ucx/kv_list.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/kv_list.c	Mon Nov 10 21:52:51 2025 +0100
@@ -152,7 +152,7 @@
         const void *elem,
         int prepend
 ) {
-    cx_kv_list *kv_list = iter->src_handle.m;
+    cx_kv_list *kv_list = iter->src_handle;
     return kv_list->list_methods->insert_iter(iter, elem, prepend);
 }
 
@@ -259,7 +259,7 @@
     struct cx_iterator_s *iter = it;
     if (iter->base.remove) {
         // remove the assigned key from the map before calling the actual function
-        cx_kv_list *kv_list = iter->src_handle.m;
+        cx_kv_list *kv_list = iter->src_handle;
         cx_kv_list_update_destructors(kv_list);
         char *node = iter->elem_handle;
         CxHashKey *key = cx_kv_list_loc_key(kv_list, node + kv_list->list.loc_data);
@@ -267,6 +267,7 @@
             kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL);
         }
     }
+    // note that we do not clear the remove flag, because the next_impl will do that
     iter->base.next_impl(it);
 }
 
@@ -397,7 +398,7 @@
 
 static void cx_kvl_iter_next(void *it) {
     CxMapIterator *iter = it;
-    cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)iter->map.m)->list;
+    cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)iter->map)->list;
 
     // find the next list entry that has a key assigned
     CxHashKey *key = NULL;
@@ -458,7 +459,7 @@
     CxMapIterator iter = {0};
 
     iter.type = type;
-    iter.map.c = map;
+    iter.map = (CxMap*)map;
     // although we iterate over the list, we only report that many elements that have a key in the map
     iter.elem_count = map->collection.size;
 
@@ -479,6 +480,7 @@
             assert(false); // LCOV_EXCL_LINE
     }
 
+    iter.base.allow_remove = true;
     iter.base.next = cx_kvl_iter_next;
     iter.base.valid = cx_kvl_iter_valid;
 
@@ -507,6 +509,15 @@
     return iter;
 }
 
+static int cx_kvl_change_capacity(struct cx_list_s *list,
+        cx_attr_unused size_t cap) {
+    // since our backing list is a linked list, we don't need to do much here,
+    // but rehashing the map is quite useful
+    cx_kv_list *kv_list = (cx_kv_list*)list;
+    cxMapRehash(&kv_list->map->map_base.base);
+    return 0;
+}
+
 static cx_list_class cx_kv_list_class = {
     cx_kvl_deallocate,
     cx_kvl_insert_element,
@@ -522,6 +533,7 @@
     cx_kvl_sort,
     NULL,
     cx_kvl_reverse,
+    cx_kvl_change_capacity,
     cx_kvl_iterator,
 };
 
--- a/ucx/linked_list.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/linked_list.c	Mon Nov 10 21:52:51 2025 +0100
@@ -30,7 +30,6 @@
 #include "cx/compare.h"
 #include <string.h>
 #include <assert.h>
-#include <unistd.h>
 
 // LOW LEVEL LINKED LIST FUNCTIONS
 
@@ -475,6 +474,16 @@
     return removed;
 }
 
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+) {
+    cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1);
+}
+
 size_t cx_linked_list_size(
         const void *node,
         ptrdiff_t loc_next
@@ -742,7 +751,9 @@
     // we can add the remaining nodes and immediately advance to the inserted node
     const char *source = array;
     for (size_t i = 1; i < n; i++) {
-        source += list->collection.elem_size;
+        if (source != NULL) {
+            source += list->collection.elem_size;
+        }
         if (0 != cx_ll_insert_at(list, node, source)) return i;
         node = CX_LL_PTR(node, ll->loc_next);
     }
@@ -1116,10 +1127,10 @@
 
 static void cx_ll_iter_next(void *it) {
     struct cx_iterator_s *iter = it;
+    cx_linked_list *ll = iter->src_handle;
     if (iter->base.remove) {
         iter->base.remove = false;
-        struct cx_list_s *list = iter->src_handle.m;
-        cx_linked_list *ll = iter->src_handle.m;
+        struct cx_list_s *list = iter->src_handle;
         char *node = iter->elem_handle;
         iter->elem_handle = CX_LL_PTR(node, ll->loc_next);
         cx_invoke_destructor(list, node + ll->loc_data);
@@ -1129,7 +1140,6 @@
         iter->elem_count--;
         cxFree(list->collection.allocator, node);
     } else {
-        const cx_linked_list *ll = iter->src_handle.c;
         iter->index++;
         void *node = iter->elem_handle;
         iter->elem_handle = CX_LL_PTR(node, ll->loc_next);
@@ -1138,10 +1148,10 @@
 
 static void cx_ll_iter_prev(void *it) {
     struct cx_iterator_s *iter = it;
+    cx_linked_list *ll = iter->src_handle;
     if (iter->base.remove) {
         iter->base.remove = false;
-        struct cx_list_s *list = iter->src_handle.m;
-        cx_linked_list *ll = iter->src_handle.m;
+        struct cx_list_s *list = iter->src_handle;
         char *node = iter->elem_handle;
         iter->elem_handle = CX_LL_PTR(node, ll->loc_prev);
         iter->index--;
@@ -1152,7 +1162,6 @@
         iter->elem_count--;
         cxFree(list->collection.allocator, node);
     } else {
-        const cx_linked_list *ll = iter->src_handle.c;
         iter->index--;
         char *node = iter->elem_handle;
         iter->elem_handle = CX_LL_PTR(node, ll->loc_prev);
@@ -1161,7 +1170,7 @@
 
 static void *cx_ll_iter_current(const void *it) {
     const struct cx_iterator_s *iter = it;
-    const cx_linked_list *ll = iter->src_handle.c;
+    const cx_linked_list *ll = iter->src_handle;
     char *node = iter->elem_handle;
     return node + ll->loc_data;
 }
@@ -1173,14 +1182,14 @@
 ) {
     CxIterator iter;
     iter.index = index;
-    iter.src_handle.c = list;
+    iter.src_handle = (void*)list;
     iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index);
     iter.elem_size = list->collection.elem_size;
     iter.elem_count = list->collection.size;
     iter.base.valid = cx_ll_iter_valid;
     iter.base.current = cx_ll_iter_current;
     iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
-    iter.base.mutating = false;
+    iter.base.allow_remove = true;
     iter.base.remove = false;
     return iter;
 }
@@ -1190,8 +1199,8 @@
         const void *elem,
         int prepend
 ) {
-    struct cx_list_s *list = iter->src_handle.m;
-    cx_linked_list *ll = iter->src_handle.m;
+    struct cx_list_s *list = iter->src_handle;
+    cx_linked_list *ll = iter->src_handle;
     void *node = iter->elem_handle;
     if (node != NULL) {
         assert(prepend >= 0 && prepend <= 1);
@@ -1243,6 +1252,7 @@
         cx_ll_sort,
         cx_ll_compare,
         cx_ll_reverse,
+        NULL, // no overallocation supported
         cx_ll_iterator,
 };
 
--- a/ucx/list.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/list.c	Mon Nov 10 21:52:51 2025 +0100
@@ -29,6 +29,7 @@
 #include "cx/list.h"
 
 #include <string.h>
+#include <assert.h>
 
 // <editor-fold desc="Store Pointers Functionality">
 
@@ -114,7 +115,7 @@
         const void *elem,
         int prepend
 ) {
-    struct cx_list_s *list = iter->src_handle.m;
+    struct cx_list_s *list = iter->src_handle;
     return list->climpl->insert_iter(iter, &elem, prepend);
 }
 
@@ -184,6 +185,10 @@
     return ptr == NULL ? NULL : *ptr;
 }
 
+static int cx_pl_change_capacity(struct cx_list_s *list, size_t cap) {
+    return list->climpl->change_capacity(list, cap);
+}
+
 static struct cx_iterator_s cx_pl_iterator(
         const struct cx_list_s *list,
         size_t index,
@@ -210,6 +215,7 @@
         cx_pl_sort,
         cx_pl_compare,
         cx_pl_reverse,
+        cx_pl_change_capacity,
         cx_pl_iterator,
 };
 // </editor-fold>
@@ -245,7 +251,7 @@
         cx_attr_unused bool backwards
 ) {
     CxIterator iter = {0};
-    iter.src_handle.c = list;
+    iter.src_handle = (void*) list;
     iter.index = index;
     iter.base.valid = cx_emptyl_iter_valid;
     return iter;
@@ -266,6 +272,7 @@
         cx_emptyl_noop,
         NULL,
         cx_emptyl_noop,
+        NULL,
         cx_emptyl_iterator,
 };
 
@@ -299,16 +306,17 @@
         const void *data,
         size_t n
 ) {
-    size_t elem_size = list->collection.elem_size;
     const char *src = data;
     size_t i = 0;
     for (; i < n; i++) {
         if (NULL == invoke_list_func(
-            insert_element, list, index + i,
-            src + i * elem_size)
+            insert_element, list, index + i, src)
         ) {
             return i; // LCOV_EXCL_LINE
         }
+        if (src != NULL) {
+            src += list->collection.elem_size;
+        }
     }
     return i;
 }
@@ -566,36 +574,174 @@
     }
 }
 
-CxIterator cxListMutIteratorAt(
-        CxList *list,
-        size_t index
-) {
-    if (list == NULL) list = cxEmptyList;
-    CxIterator it = list->cl->iterator(list, index, false);
-    it.base.mutating = true;
-    return it;
+size_t cxListSize(const CxList *list) {
+    return list->collection.size;
+}
+
+int cxListAdd(CxList *list, const void *elem) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, list->collection.size, elem) == NULL;
+}
+
+size_t cxListAddArray(CxList *list, const void *array, size_t n) {
+    list->collection.sorted = false;
+    return list->cl->insert_array(list, list->collection.size, array, n);
+}
+
+int cxListInsert(CxList *list, size_t index, const void *elem) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, index, elem) == NULL;
+}
+
+void *cxListEmplaceAt(CxList *list, size_t index) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, index, NULL);
+}
+
+void *cxListEmplace(CxList *list) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, list->collection.size, NULL);
+}
+
+static bool cx_list_emplace_iterator_valid(const void *it) {
+    const CxIterator *iter = it;
+    return iter->index < iter->elem_count;
+}
+
+CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n) {
+    list->collection.sorted = false;
+    size_t c = list->cl->insert_array(list, index, NULL, n);
+    CxIterator iter = list->cl->iterator(list, index, false);
+    // tweak the fields of this iterator
+    iter.elem_count = c;
+    iter.index = 0;
+    // replace the valid function to abort iteration when c is reached
+    iter.base.valid = cx_list_emplace_iterator_valid;
+    // if we are storing pointers, we want to return the pure pointers.
+    // therefore, we must unwrap the "current" method
+    if (list->collection.store_pointer) {
+        iter.base.current = iter.base.current_impl;
+    }
+    return iter;
+}
+
+CxIterator cxListEmplaceArray(CxList *list, size_t n) {
+    return cxListEmplaceArrayAt(list, list->collection.size, n);
+}
+
+int cxListInsertSorted(CxList *list, const void *elem) {
+    assert(cxCollectionSorted(list));
+    list->collection.sorted = true;
+    const void *data = list->collection.store_pointer ? &elem : elem;
+    return list->cl->insert_sorted(list, data, 1) == 0;
+}
+
+int cxListInsertUnique(CxList *list, const void *elem) {
+    if (cxCollectionSorted(list)) {
+        list->collection.sorted = true;
+        const void *data = list->collection.store_pointer ? &elem : elem;
+        return list->cl->insert_unique(list, data, 1) == 0;
+    } else {
+        if (cxListContains(list, elem)) {
+            return 0;
+        } else {
+            return cxListAdd(list, elem);
+        }
+    }
+}
+
+size_t cxListInsertArray(CxList *list, size_t index, const void *array, size_t n) {
+    list->collection.sorted = false;
+    return list->cl->insert_array(list, index, array, n);
 }
 
-CxIterator cxListMutBackwardsIteratorAt(
-        CxList *list,
-        size_t index
-) {
-    if (list == NULL) list = cxEmptyList;
-    CxIterator it = list->cl->iterator(list, index, true);
-    it.base.mutating = true;
-    return it;
+size_t cxListInsertSortedArray(CxList *list, const void *array, size_t n) {
+    assert(cxCollectionSorted(list));
+    list->collection.sorted = true;
+    return list->cl->insert_sorted(list, array, n);
+}
+
+size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n) {
+    if (cxCollectionSorted(list)) {
+        list->collection.sorted = true;
+        return list->cl->insert_unique(list, array, n);
+    } else {
+        const char *source = array;
+        for (size_t i = 0 ; i < n; i++) {
+            // note: this also checks elements added in a previous iteration
+            const void *data = list->collection.store_pointer ?
+                    *((const void**)source) : source;
+            if (!cxListContains(list, data)) {
+                if (cxListAdd(list, data)) {
+                    return i; // LCOV_EXCL_LINE
+                }
+            }
+            source += list->collection.elem_size;
+        }
+        return n;
+    }
+}
+
+int cxListInsertAfter(CxIterator *iter, const void *elem) {
+    CxList* list = (CxList*)iter->src_handle;
+    list->collection.sorted = false;
+    return list->cl->insert_iter(iter, elem, 0);
+}
+
+int cxListInsertBefore(CxIterator *iter, const void *elem) {
+    CxList* list = (CxList*)iter->src_handle;
+    list->collection.sorted = false;
+    return list->cl->insert_iter(iter, elem, 1);
+}
+
+int cxListRemove(CxList *list, size_t index) {
+    return list->cl->remove(list, index, 1, NULL) == 0;
 }
 
-void cxListFree(CxList *list) {
-    if (list == NULL) return;
-    list->cl->deallocate(list);
+int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf) {
+    return list->cl->remove(list, index, 1, targetbuf) == 0;
+}
+
+int cxListRemoveAndGetFirst(CxList *list, void *targetbuf) {
+    return list->cl->remove(list, 0, 1, targetbuf) == 0;
+}
+
+int cxListRemoveAndGetLast(CxList *list, void *targetbuf) {
+    // note: index may wrap - member function will catch that
+    return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0;
+}
+
+size_t cxListRemoveArray(CxList *list, size_t index, size_t num) {
+    return list->cl->remove(list, index, num, NULL);
+}
+
+size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf) {
+    return list->cl->remove(list, index, num, targetbuf);
 }
 
-int cxListSet(
-        CxList *list,
-        size_t index,
-        const void *elem
-) {
+void cxListClear(CxList *list) {
+    list->cl->clear(list);
+    list->collection.sorted = true; // empty lists are always sorted
+}
+
+int cxListSwap(CxList *list, size_t i, size_t j) {
+    list->collection.sorted = false;
+    return list->cl->swap(list, i, j);
+}
+
+void *cxListAt(const CxList *list, size_t index) {
+    return list->cl->at(list, index);
+}
+
+void *cxListFirst(const CxList *list) {
+    return list->cl->at(list, 0);
+}
+
+void *cxListLast(const CxList *list) {
+    return list->cl->at(list, list->collection.size - 1);
+}
+
+int cxListSet(CxList *list, size_t index, const void *elem) {
     if (index >= list->collection.size) {
         return 1;
     }
@@ -611,3 +757,371 @@
 
     return 0;
 }
+
+CxIterator cxListIteratorAt(const CxList *list, size_t index) {
+    if (list == NULL) list = cxEmptyList;
+    return list->cl->iterator(list, index, false);
+}
+
+CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index) {
+    if (list == NULL) list = cxEmptyList;
+    return list->cl->iterator(list, index, true);
+}
+
+CxIterator cxListIterator(const CxList *list) {
+    if (list == NULL) list = cxEmptyList;
+    return list->cl->iterator(list, 0, false);
+}
+
+CxIterator cxListBackwardsIterator(const CxList *list) {
+    if (list == NULL) list = cxEmptyList;
+    return list->cl->iterator(list, list->collection.size - 1, true);
+}
+
+size_t cxListFind(const CxList *list, const void *elem) {
+    return list->cl->find_remove((CxList*)list, elem, false);
+}
+
+bool cxListContains(const CxList* list, const void* elem) {
+    return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size;
+}
+
+bool cxListIndexValid(const CxList *list, size_t index) {
+    return index < list->collection.size;
+}
+
+size_t cxListFindRemove(CxList *list, const void *elem) {
+    return list->cl->find_remove(list, elem, true);
+}
+
+void cxListSort(CxList *list) {
+    if (list->collection.sorted) return;
+    list->cl->sort(list);
+    list->collection.sorted = true;
+}
+
+void cxListReverse(CxList *list) {
+    // still sorted, but not according to the cmp_func
+    list->collection.sorted = false;
+    list->cl->reverse(list);
+}
+
+void cxListFree(CxList *list) {
+    if (list == NULL) return;
+    list->cl->deallocate(list);
+}
+
+static void cx_list_pop_uninitialized_elements(CxList *list, size_t n) {
+    cx_destructor_func destr_bak = list->collection.simple_destructor;
+    cx_destructor_func2 destr2_bak = list->collection.advanced_destructor;
+    list->collection.simple_destructor = NULL;
+    list->collection.advanced_destructor = NULL;
+    if (n == 1) {
+        cxListRemove(list, list->collection.size - 1);
+    } else {
+        cxListRemoveArray(list,list->collection.size - n, n);
+    }
+    list->collection.simple_destructor = destr_bak;
+    list->collection.advanced_destructor = destr2_bak;
+}
+
+static void* cx_list_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) {
+    size_t elem_size = *(size_t*)data;
+    if (dst == NULL) dst = cxMalloc(al, elem_size);
+    if (dst != NULL) memcpy(dst, src, elem_size);
+    return dst;
+}
+
+#define use_simple_clone_func(list) cx_list_simple_clone_func, NULL, (void*)&((list)->collection.elem_size)
+
+int cxListClone(CxList *dst, const CxList *src, cx_clone_func clone_func,
+        const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // remember the original size
+    size_t orig_size = dst->collection.size;
+
+    // first, try to allocate the memory in the new list
+    CxIterator empl_iter = cxListEmplaceArray(dst, src->collection.size);
+
+    // get an iterator over the source elements
+    CxIterator src_iter = cxListIterator(src);
+
+    // now clone the elements
+    size_t cloned = empl_iter.elem_count;
+    for (size_t i = 0 ; i < empl_iter.elem_count; i++) {
+        void *src_elem = cxIteratorCurrent(src_iter);
+        void **dest_memory = cxIteratorCurrent(empl_iter);
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dest_memory;
+        void *dest_ptr = clone_func(target, src_elem, clone_allocator, data);
+        if (dest_ptr == NULL) {
+            cloned = i;
+            break;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dest_memory = dest_ptr;
+        }
+        cxIteratorNext(src_iter);
+        cxIteratorNext(empl_iter);
+    }
+
+    // if we could not clone everything, free the allocated memory
+    // (disable the destructors!)
+    if (cloned < src->collection.size) {
+        cx_list_pop_uninitialized_elements(dst,
+            dst->collection.size - cloned - orig_size);
+        return 1;
+    }
+
+    // set the sorted flag when we know it's sorted
+    if (orig_size == 0 && src->collection.sorted) {
+        dst->collection.sorted = true;
+    }
+
+    return 0;
+}
+
+int cxListDifference(CxList *dst,
+        const CxList *minuend, const CxList *subtrahend,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // optimize for sorted collections
+    if (cxCollectionSorted(minuend) && cxCollectionSorted(subtrahend)) {
+        bool dst_was_empty = cxCollectionSize(dst) == 0;
+
+        CxIterator min_iter = cxListIterator(minuend);
+        CxIterator sub_iter = cxListIterator(subtrahend);
+        while (cxIteratorValid(min_iter)) {
+            void *min_elem = cxIteratorCurrent(min_iter);
+            void *sub_elem;
+            int d;
+            if (cxIteratorValid(sub_iter)) {
+                sub_elem = cxIteratorCurrent(sub_iter);
+                cx_compare_func cmp = subtrahend->collection.cmpfunc;
+                d = cmp(sub_elem, min_elem);
+            } else {
+                // no more elements in the subtrahend,
+                // i.e., the min_elem is larger than any elem of the subtrahend
+                d = 1;
+            }
+            if (d == 0) {
+                // is contained, so skip it
+                cxIteratorNext(min_iter);
+            } else if (d < 0) {
+                // subtrahend is smaller than minuend,
+                // check the next element
+                cxIteratorNext(sub_iter);
+            } else {
+                // subtrahend is larger than the dst element,
+                // clone the minuend and advance
+                void **dst_mem = cxListEmplace(dst);
+                void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+                void* dst_ptr = clone_func(target, min_elem, clone_allocator, data);
+                if (dst_ptr == NULL) {
+                    cx_list_pop_uninitialized_elements(dst, 1);
+                    return 1;
+                }
+                if (cxCollectionStoresPointers(dst)) {
+                    *dst_mem = dst_ptr;
+                }
+                cxIteratorNext(min_iter);
+            }
+        }
+
+        // if dst was empty, it is now guaranteed to be sorted
+        dst->collection.sorted = dst_was_empty;
+    } else {
+        CxIterator min_iter = cxListIterator(minuend);
+        cx_foreach(void *, elem, min_iter) {
+            if (cxListContains(subtrahend, elem)) {
+                continue;
+            }
+            void **dst_mem = cxListEmplace(dst);
+            void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+            void* dst_ptr = clone_func(target, elem, clone_allocator, data);
+            if (dst_ptr == NULL) {
+                cx_list_pop_uninitialized_elements(dst, 1);
+                return 1;
+            }
+            if (cxCollectionStoresPointers(dst)) {
+                *dst_mem = dst_ptr;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int cxListIntersection(CxList *dst,
+        const CxList *src, const CxList *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // optimize for sorted collections
+    if (cxCollectionSorted(src) && cxCollectionSorted(other)) {
+        bool dst_was_empty = cxCollectionSize(dst) == 0;
+
+        CxIterator src_iter = cxListIterator(src);
+        CxIterator other_iter = cxListIterator(other);
+        while (cxIteratorValid(src_iter) && cxIteratorValid(other_iter)) {
+            void *src_elem = cxIteratorCurrent(src_iter);
+            void *other_elem = cxIteratorCurrent(other_iter);
+            int d = src->collection.cmpfunc(src_elem, other_elem);
+            if (d == 0) {
+                // is contained, clone it
+                void **dst_mem = cxListEmplace(dst);
+                void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+                void* dst_ptr = clone_func(target, src_elem, clone_allocator, data);
+                if (dst_ptr == NULL) {
+                    cx_list_pop_uninitialized_elements(dst, 1);
+                    return 1;
+                }
+                if (cxCollectionStoresPointers(dst)) {
+                    *dst_mem = dst_ptr;
+                }
+                cxIteratorNext(src_iter);
+            } else if (d < 0) {
+                // the other element is larger, skip the source element
+                cxIteratorNext(src_iter);
+            } else {
+                // the source element is larger, try to find it in the other list
+                cxIteratorNext(other_iter);
+            }
+        }
+
+        // if dst was empty, it is now guaranteed to be sorted
+        dst->collection.sorted = dst_was_empty;
+    } else {
+        CxIterator src_iter = cxListIterator(src);
+        cx_foreach(void *, elem, src_iter) {
+            if (!cxListContains(other, elem)) {
+                continue;
+            }
+            void **dst_mem = cxListEmplace(dst);
+            void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+            void* dst_ptr = clone_func(target, elem, clone_allocator, data);
+            if (dst_ptr == NULL) {
+                cx_list_pop_uninitialized_elements(dst, 1);
+                return 1;
+            }
+            if (cxCollectionStoresPointers(dst)) {
+                *dst_mem = dst_ptr;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int cxListUnion(CxList *dst,
+        const CxList *src, const CxList *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // optimize for sorted collections
+    if (cxCollectionSorted(src) && cxCollectionSorted(other)) {
+        bool dst_was_empty = cxCollectionSize(dst) == 0;
+
+        CxIterator src_iter = cxListIterator(src);
+        CxIterator other_iter = cxListIterator(other);
+        while (cxIteratorValid(src_iter) || cxIteratorValid(other_iter)) {
+            void *src_elem, *other_elem;
+            int d;
+            if (!cxIteratorValid(src_iter)) {
+                other_elem = cxIteratorCurrent(other_iter);
+                d = 1;
+            } else if (!cxIteratorValid(other_iter)) {
+                src_elem = cxIteratorCurrent(src_iter);
+                d = -1;
+            } else {
+                src_elem = cxIteratorCurrent(src_iter);
+                other_elem = cxIteratorCurrent(other_iter);
+                d = src->collection.cmpfunc(src_elem, other_elem);
+            }
+            void *clone_from;
+            if (d < 0) {
+                // source element is smaller clone it
+                clone_from = src_elem;
+                cxIteratorNext(src_iter);
+            } else if (d == 0) {
+                // both elements are equal, clone from the source, skip other
+                clone_from = src_elem;
+                cxIteratorNext(src_iter);
+                cxIteratorNext(other_iter);
+            } else {
+                // the other element is smaller, clone it
+                clone_from = other_elem;
+                cxIteratorNext(other_iter);
+            }
+            void **dst_mem = cxListEmplace(dst);
+            void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+            void* dst_ptr = clone_func(target, clone_from, clone_allocator, data);
+            if (dst_ptr == NULL) {
+                cx_list_pop_uninitialized_elements(dst, 1);
+                return 1;
+            }
+            if (cxCollectionStoresPointers(dst)) {
+                *dst_mem = dst_ptr;
+            }
+        }
+
+        // if dst was empty, it is now guaranteed to be sorted
+        dst->collection.sorted = dst_was_empty;
+    } else {
+        if (cxListClone(dst, src, clone_func, clone_allocator, data)) {
+            return 1;
+        }
+        CxIterator other_iter = cxListIterator(other);
+        cx_foreach(void *, elem, other_iter) {
+            if (cxListContains(src, elem)) {
+                continue;
+            }
+            void **dst_mem = cxListEmplace(dst);
+            void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+            void* dst_ptr = clone_func(target, elem, clone_allocator, data);
+            if (dst_ptr == NULL) {
+                cx_list_pop_uninitialized_elements(dst, 1);
+                return 1;
+            }
+            if (cxCollectionStoresPointers(dst)) {
+                *dst_mem = dst_ptr;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int cxListCloneSimple(CxList *dst, const CxList *src) {
+    return cxListClone(dst, src, use_simple_clone_func(src));
+}
+
+int cxListDifferenceSimple(CxList *dst, const CxList *minuend, const CxList *subtrahend) {
+    return cxListDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend));
+}
+
+int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other) {
+    return cxListIntersection(dst, src, other, use_simple_clone_func(src));
+}
+
+int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other) {
+    return cxListUnion(dst, src, other, use_simple_clone_func(src));
+}
+
+int cxListReserve(CxList *list, size_t capacity) {
+    if (list->cl->change_capacity == NULL) {
+        return 0;
+    }
+    if (capacity <= cxCollectionSize(list)) {
+        return 0;
+    }
+    return list->cl->change_capacity(list, capacity);
+}
+
+int cxListShrink(CxList *list) {
+    if (list->cl->change_capacity == NULL) {
+        return 0;
+    }
+    return list->cl->change_capacity(list, cxCollectionSize(list));
+}
\ No newline at end of file
--- a/ucx/map.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/map.c	Mon Nov 10 21:52:51 2025 +0100
@@ -29,6 +29,8 @@
 #include "cx/map.h"
 #include <string.h>
 
+#include "cx/list.h"
+
 // <editor-fold desc="empty map implementation">
 
 static void cx_empty_map_noop(cx_attr_unused CxMap *map) {
@@ -51,7 +53,7 @@
         cx_attr_unused enum cx_map_iterator_type type
 ) {
     CxMapIterator iter = {0};
-    iter.map.c = map;
+    iter.map = (CxMap*) map;
     iter.base.valid = cx_empty_map_iter_valid;
     return iter;
 }
@@ -84,28 +86,243 @@
 
 // </editor-fold>
 
-CxMapIterator cxMapMutIteratorValues(CxMap *map) {
+void cxMapClear(CxMap *map) {
+    map->cl->clear(map);
+}
+
+size_t cxMapSize(const CxMap *map) {
+    return map->collection.size;
+}
+
+CxMapIterator cxMapIteratorValues(const CxMap *map) {
     if (map == NULL) map = cxEmptyMap;
-    CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
-    it.base.mutating = true;
-    return it;
+    return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+}
+
+CxMapIterator cxMapIteratorKeys(const CxMap *map) {
+    if (map == NULL) map = cxEmptyMap;
+    return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
 }
 
-CxMapIterator cxMapMutIteratorKeys(CxMap *map) {
+CxMapIterator cxMapIterator(const CxMap *map) {
     if (map == NULL) map = cxEmptyMap;
-    CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
-    it.base.mutating = true;
-    return it;
+    return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+}
+
+int cx_map_put(CxMap *map, CxHashKey key, void *value) {
+    return map->cl->put(map, key, value) == NULL;
 }
 
-CxMapIterator cxMapMutIterator(CxMap *map) {
-    if (map == NULL) map = cxEmptyMap;
-    CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
-    it.base.mutating = true;
-    return it;
+void *cx_map_emplace(CxMap *map, CxHashKey key) {
+    return map->cl->put(map, key, NULL);
+}
+
+void *cx_map_get(const CxMap *map, CxHashKey key) {
+    return map->cl->get(map, key);
+}
+
+int cx_map_remove(CxMap *map, CxHashKey key, void *targetbuf) {
+    return map->cl->remove(map, key, targetbuf);
 }
 
 void cxMapFree(CxMap *map) {
     if (map == NULL) return;
     map->cl->deallocate(map);
 }
+
+static void cx_map_remove_uninitialized_entry(CxMap *map, CxHashKey key) {
+    cx_destructor_func destr_bak = map->collection.simple_destructor;
+    cx_destructor_func2 destr2_bak = map->collection.advanced_destructor;
+    map->collection.simple_destructor = NULL;
+    map->collection.advanced_destructor = NULL;
+    cxMapRemove(map, key);
+    map->collection.simple_destructor = destr_bak;
+    map->collection.advanced_destructor = destr2_bak;
+}
+
+static void* cx_map_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) {
+    size_t elem_size = *(size_t*)data;
+    if (dst == NULL) dst = cxMalloc(al, elem_size);
+    if (dst != NULL) memcpy(dst, src, elem_size);
+    return dst;
+}
+
+#define use_simple_clone_func(map) cx_map_simple_clone_func, NULL, (void*)&((map)->collection.elem_size)
+
+int cxMapClone(CxMap *dst, const CxMap *src, cx_clone_func clone_func,
+        const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+    CxMapIterator src_iter = cxMapIterator(src);
+    for (size_t i = 0; i < cxMapSize(src); i++) {
+        const CxMapEntry *entry = cxIteratorCurrent(src_iter);
+        void **dst_mem = cxMapEmplace(dst, *(entry->key));
+        if (dst_mem == NULL) {
+            return 1; // LCOV_EXCL_LINE
+        }
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+        void *dst_ptr = clone_func(target, entry->value, clone_allocator, data);
+        if (dst_ptr == NULL) {
+            cx_map_remove_uninitialized_entry(dst, *(entry->key));
+            return 1;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dst_mem = dst_ptr;
+        }
+        cxIteratorNext(src_iter);
+    }
+    return 0;
+}
+
+int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    CxMapIterator src_iter = cxMapIterator(minuend);
+    cx_foreach(const CxMapEntry *, entry, src_iter) {
+        if (cxMapContains(subtrahend, *entry->key)) {
+            continue;
+        }
+        void** dst_mem = cxMapEmplace(dst, *entry->key);
+        if (dst_mem == NULL) {
+            return 1; // LCOV_EXCL_LINE
+        }
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+        void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);
+        if (dst_ptr == NULL) {
+            cx_map_remove_uninitialized_entry(dst, *(entry->key));
+            return 1;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dst_mem = dst_ptr;
+        }
+    }
+    return 0;
+}
+
+int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    CxMapIterator src_iter = cxMapIterator(src);
+    cx_foreach(const CxMapEntry *, entry, src_iter) {
+        if (cxListContains(keys, entry->key)) {
+            continue;
+        }
+        void** dst_mem = cxMapEmplace(dst, *entry->key);
+        if (dst_mem == NULL) {
+            return 1; // LCOV_EXCL_LINE
+        }
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+        void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);
+        if (dst_ptr == NULL) {
+            cx_map_remove_uninitialized_entry(dst, *(entry->key));
+            return 1;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dst_mem = dst_ptr;
+        }
+    }
+    return 0;
+}
+
+int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    CxMapIterator src_iter = cxMapIterator(src);
+    cx_foreach(const CxMapEntry *, entry, src_iter) {
+        if (!cxMapContains(other, *entry->key)) {
+            continue;
+        }
+        void** dst_mem = cxMapEmplace(dst, *entry->key);
+        if (dst_mem == NULL) {
+            return 1; // LCOV_EXCL_LINE
+        }
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+        void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);
+        if (dst_ptr == NULL) {
+            cx_map_remove_uninitialized_entry(dst, *(entry->key));
+            return 1;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dst_mem = dst_ptr;
+        }
+    }
+    return 0;
+}
+
+int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    CxMapIterator src_iter = cxMapIterator(src);
+    cx_foreach(const CxMapEntry *, entry, src_iter) {
+        if (!cxListContains(keys, entry->key)) {
+            continue;
+        }
+        void** dst_mem = cxMapEmplace(dst, *entry->key);
+        if (dst_mem == NULL) {
+            return 1; // LCOV_EXCL_LINE
+        }
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+        void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);
+        if (dst_ptr == NULL) {
+            cx_map_remove_uninitialized_entry(dst, *(entry->key));
+            return 1;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dst_mem = dst_ptr;
+        }
+    }
+    return 0;
+}
+
+int cxMapUnion(CxMap *dst, const CxMap *src,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    CxMapIterator src_iter = cxMapIterator(src);
+    cx_foreach(const CxMapEntry *, entry, src_iter) {
+        if (cxMapContains(dst, *entry->key)) {
+            continue;
+        }
+        void** dst_mem = cxMapEmplace(dst, *entry->key);
+        if (dst_mem == NULL) {
+            return 1; // LCOV_EXCL_LINE
+        }
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+        void* dst_ptr = clone_func(target, entry->value, clone_allocator, data);
+        if (dst_ptr == NULL) {
+            cx_map_remove_uninitialized_entry(dst, *(entry->key));
+            return 1;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dst_mem = dst_ptr;
+        }
+    }
+    return 0;
+}
+
+int cxMapCloneSimple(CxMap *dst, const CxMap *src) {
+    return cxMapClone(dst, src, use_simple_clone_func(src));
+}
+
+int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) {
+    return cxMapDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend));
+}
+
+int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys) {
+    return cxMapListDifference(dst, src, keys, use_simple_clone_func(src));
+}
+
+int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other) {
+    return cxMapIntersection(dst, src, other, use_simple_clone_func(src));
+}
+
+int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys) {
+    return cxMapListIntersection(dst, src, keys, use_simple_clone_func(src));
+}
+
+int cxMapUnionSimple(CxMap *dst, const CxMap *src) {
+    return cxMapUnion(dst, src, use_simple_clone_func(src));
+}
--- a/ucx/mempool.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/mempool.c	Mon Nov 10 21:52:51 2025 +0100
@@ -116,6 +116,9 @@
     if (!ptr) return;
     struct cx_mempool_s *pool = p;
 
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+
     struct cx_mempool_memory_s *mem =
         (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s));
 
@@ -124,11 +127,11 @@
             if (mem->destructor) {
                 mem->destructor(mem->c);
             }
-            if (pool->destr) {
-                pool->destr(mem->c);
+            if (destr != NULL) {
+                destr(mem->c);
             }
-            if (pool->destr2) {
-                pool->destr2(pool->destr2_data, mem->c);
+            if (destr2 != NULL) {
+                destr2(pool->destr2_data, mem->c);
             }
             cxFree(pool->base_allocator, mem);
             size_t last_index = pool->size - 1;
@@ -179,18 +182,18 @@
 }
 
 static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) {
-    const bool has_destr = pool->destr;
-    const bool has_destr2 = pool->destr2;
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
     for (size_t i = 0; i < pool->size; i++) {
         struct cx_mempool_memory_s *mem = pool->data[i];
         if (mem->destructor) {
             mem->destructor(mem->c);
         }
-        if (has_destr) {
-            pool->destr(mem->c);
+        if (destr != NULL) {
+            destr(mem->c);
         }
-        if (has_destr2) {
-            pool->destr2(pool->destr2_data, mem->c);
+        if (destr2 != NULL) {
+            destr2(pool->destr2_data, mem->c);
         }
         cxFree(pool->base_allocator, mem);
     }
@@ -247,6 +250,9 @@
     if (!ptr) return;
     struct cx_mempool_s *pool = p;
 
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+
     struct cx_mempool_memory2_s *mem =
         (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s));
 
@@ -255,11 +261,11 @@
             if (mem->destructor) {
                 mem->destructor(mem->data, mem->c);
             }
-            if (pool->destr) {
-                pool->destr(mem->c);
+            if (destr != NULL) {
+                destr(mem->c);
             }
-            if (pool->destr2) {
-                pool->destr2(pool->destr2_data, mem->c);
+            if (destr2 != NULL) {
+                destr2(pool->destr2_data, mem->c);
             }
             cxFree(pool->base_allocator, mem);
             size_t last_index = pool->size - 1;
@@ -310,18 +316,18 @@
 }
 
 static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) {
-    const bool has_destr = pool->destr;
-    const bool has_destr2 = pool->destr2;
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
     for (size_t i = 0; i < pool->size; i++) {
         struct cx_mempool_memory2_s *mem = pool->data[i];
         if (mem->destructor) {
             mem->destructor(mem->data, mem->c);
         }
-        if (has_destr) {
-            pool->destr(mem->c);
+        if (destr != NULL) {
+            destr(mem->c);
         }
-        if (has_destr2) {
-            pool->destr2(pool->destr2_data, mem->c);
+        if (destr2 != NULL) {
+            destr2(pool->destr2_data, mem->c);
         }
         cxFree(pool->base_allocator, mem);
     }
@@ -376,13 +382,16 @@
     if (!ptr) return;
     struct cx_mempool_s *pool = p;
 
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+
     for (size_t i = 0; i < pool->size; i++) {
         if (ptr == pool->data[i]) {
-            if (pool->destr) {
-                pool->destr(ptr);
+            if (destr != NULL) {
+                destr(ptr);
             }
-            if (pool->destr2) {
-                pool->destr2(pool->destr2_data, ptr);
+            if (destr2 != NULL) {
+                destr2(pool->destr2_data, ptr);
             }
             cxFree(pool->base_allocator, ptr);
             size_t last_index = pool->size - 1;
@@ -427,15 +436,15 @@
 }
 
 static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) {
-    const bool has_destr = pool->destr;
-    const bool has_destr2 = pool->destr2;
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
     for (size_t i = 0; i < pool->size; i++) {
         void *mem = pool->data[i];
-        if (has_destr) {
-            pool->destr(mem);
+        if (destr != NULL) {
+            destr(mem);
         }
-        if (has_destr2) {
-            pool->destr2(pool->destr2_data, mem);
+        if (destr2 != NULL) {
+            destr2(pool->destr2_data, mem);
         }
         cxFree(pool->base_allocator, mem);
     }
--- a/ucx/properties.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/properties.c	Mon Nov 10 21:52:51 2025 +0100
@@ -51,6 +51,12 @@
     cxBufferDestroy(&prop->buffer);
 }
 
+void cxPropertiesReset(CxProperties *prop) {
+    CxPropertiesConfig config = prop->config;
+    cxPropertiesDestroy(prop);
+    cxPropertiesInit(prop, config);
+}
+
 int cxPropertiesFilln(
         CxProperties *prop,
         const char *buf,
--- a/ucx/string.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/string.c	Mon Nov 10 21:52:51 2025 +0100
@@ -461,7 +461,7 @@
                          delim, limit, (cxstring **) output);
 }
 
-int cx_strcmp(
+int cx_strcmp_(
         cxstring s1,
         cxstring s2
 ) {
@@ -478,7 +478,7 @@
     }
 }
 
-int cx_strcasecmp(
+int cx_strcasecmp_(
         cxstring s1,
         cxstring s2
 ) {
@@ -547,7 +547,7 @@
     return (cxmutstr) {(char *) result.ptr, result.length};
 }
 
-bool cx_strprefix(
+bool cx_strprefix_(
         cxstring string,
         cxstring prefix
 ) {
@@ -555,7 +555,7 @@
     return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
 }
 
-bool cx_strsuffix(
+bool cx_strsuffix_(
         cxstring string,
         cxstring suffix
 ) {
@@ -564,7 +564,7 @@
                   suffix.ptr, suffix.length) == 0;
 }
 
-bool cx_strcaseprefix(
+bool cx_strcaseprefix_(
         cxstring string,
         cxstring prefix
 ) {
@@ -576,7 +576,7 @@
 #endif
 }
 
-bool cx_strcasesuffix(
+bool cx_strcasesuffix_(
         cxstring string,
         cxstring suffix
 ) {
--- a/ucx/tree.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ucx/tree.c	Mon Nov 10 21:52:51 2025 +0100
@@ -375,7 +375,7 @@
     iter.skip = false;
 
     // assign base iterator functions
-    iter.base.mutating = false;
+    iter.base.allow_remove = false;
     iter.base.remove = false;
     iter.base.current_impl = NULL;
     iter.base.valid = cx_tree_iter_valid;
@@ -496,7 +496,7 @@
     iter.queue_last = NULL;
 
     // assign base iterator functions
-    iter.base.mutating = false;
+    iter.base.allow_remove = false;
     iter.base.remove = false;
     iter.base.current_impl = NULL;
     iter.base.valid = cx_tree_visitor_valid;
@@ -717,7 +717,7 @@
     }
 
     // otherwise, create iterator and hand over to other function
-    CxIterator iter = cxIterator(src, elem_size, num);
+    CxIterator iter = cxIterator(src, elem_size, num, false);
     return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc,
                             cfunc, cdata, failed, root,
                             loc_parent, loc_children, loc_last_child,
@@ -804,16 +804,12 @@
         cx_tree_default_find
 };
 
-CxTree *cxTreeCreate(
-        const CxAllocator *allocator,
+CxTree *cxTreeCreate(const CxAllocator *allocator,
         cx_tree_node_create_func create_func,
         cx_tree_search_func search_func,
         cx_tree_search_data_func search_data_func,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next
 ) {
     if (allocator == NULL) {
         allocator = cxDefaultAllocator;
@@ -852,15 +848,9 @@
     cxFree(tree->allocator, tree);
 }
 
-CxTree *cxTreeCreateWrapped(
-        const CxAllocator *allocator,
-        void *root,
-        ptrdiff_t loc_parent,
-        ptrdiff_t loc_children,
-        ptrdiff_t loc_last_child,
-        ptrdiff_t loc_prev,
-        ptrdiff_t loc_next
-) {
+CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root,
+        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next) {
     if (allocator == NULL) {
         allocator = cxDefaultAllocator;
     }
@@ -888,11 +878,7 @@
     return tree;
 }
 
-void cxTreeSetParent(
-        CxTree *tree,
-        void *parent,
-        void *child
-) {
+void cxTreeSetParent(CxTree *tree, void *parent, void *child) {
     size_t loc_parent = tree->loc_parent;
     if (tree_parent(child) == NULL) {
         tree->size++;
@@ -900,19 +886,12 @@
     cx_tree_link(parent, child, cx_tree_node_layout(tree));
 }
 
-void cxTreeAddChildNode(
-        CxTree *tree,
-        void *parent,
-        void *child
-) {
+void cxTreeAddChildNode(CxTree *tree, void *parent, void *child) {
     cx_tree_link(parent, child, cx_tree_node_layout(tree));
     tree->size++;
 }
 
-int cxTreeAddChild(
-        CxTree *tree,
-        void *parent,
-        const void *data) {
+int cxTreeAddChild(CxTree *tree, void *parent, const void *data) {
     void *node = tree->node_create(data, tree);
     if (node == NULL) return 1;
     cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
@@ -921,6 +900,29 @@
     return 0;
 }
 
+int cxTreeInsert(CxTree *tree, const void *data) {
+    return tree->cl->insert_element(tree, data);
+}
+
+size_t cxTreeInsertIter(CxTree *tree, CxIteratorBase *iter, size_t n) {
+    return tree->cl->insert_many(tree, iter, n);
+}
+
+size_t cxTreeInsertArray(CxTree *tree, const void *data, size_t elem_size, size_t n) {
+    if (n == 0) return 0;
+    if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0;
+    CxIterator iter = cxIterator(data, elem_size, n, false);
+    return cxTreeInsertIter(tree, cxIteratorRef(iter), n);
+}
+
+void *cxTreeFind( CxTree *tree, const void *data) {
+    return tree->cl->find(tree, tree->root, data, 0);
+}
+
+void *cxTreeFindInSubtree(CxTree *tree, const void *data, void *subtree_root, size_t max_depth) {
+    return tree->cl->find(tree, subtree_root, data, max_depth);
+}
+
 size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) {
     CxTreeVisitor visitor = cx_tree_visitor(
             subtree_root,
@@ -945,6 +947,10 @@
     return visitor.depth;
 }
 
+size_t cxTreeSize(CxTree *tree) {
+    return tree->size;
+}
+
 size_t cxTreeDepth(CxTree *tree) {
     CxTreeVisitor visitor = cx_tree_visitor(
             tree->root, tree->loc_children, tree->loc_next
@@ -1052,3 +1058,38 @@
         tree->root = NULL;
     }
 }
+
+void cxTreeIteratorDispose(CxTreeIterator *iter) {
+    cxFreeDefault(iter->stack);
+    iter->stack = NULL;
+}
+
+void cxTreeVisitorDispose(CxTreeVisitor *visitor) {
+    struct cx_tree_visitor_queue_s *q = visitor->queue_next;
+    while (q != NULL) {
+        struct cx_tree_visitor_queue_s *next = q->next;
+        cxFreeDefault(q);
+        q = next;
+    }
+}
+
+CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit) {
+    return cx_tree_iterator(
+            node, visit_on_exit,
+            tree->loc_children, tree->loc_next
+    );
+}
+
+CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) {
+    return cx_tree_visitor(
+            node, tree->loc_children, tree->loc_next
+    );
+}
+
+CxTreeIterator cxTreeIterate(CxTree *tree, bool visit_on_exit) {
+    return cxTreeIterateSubtree(tree, tree->root, visit_on_exit);
+}
+
+CxTreeVisitor cxTreeVisit(CxTree *tree) {
+    return cxTreeVisitSubtree(tree, tree->root);
+}
--- a/ui/cocoa/ListDataSource.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/ListDataSource.m	Mon Nov 10 21:52:51 2025 +0100
@@ -98,6 +98,12 @@
             case UI_ICON_TEXT_FREE: {
                 break;
             }
+            case UI_STRING_EDITABLE: {
+                break;
+            }
+            case UI_BOOL_EDITABLE: {
+                break;
+            }
         }
         
         if(freeResult) {
--- a/ui/cocoa/MainWindow.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/MainWindow.h	Mon Nov 10 21:52:51 2025 +0100
@@ -31,6 +31,8 @@
 
 @interface MainWindow : NSWindow<UiToplevelObject>
 
+@property UiObject *obj;
+@property (strong) NSSplitView *splitview;
 @property (strong) NSView *sidebar;
 @property (strong) NSView *leftPanel;
 @property (strong) NSView *rightPanel;
--- a/ui/cocoa/MainWindow.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/MainWindow.m	Mon Nov 10 21:52:51 2025 +0100
@@ -50,12 +50,8 @@
             NSWindowStyleMaskMiniaturizable
                              backing:NSBackingStoreBuffered
                                defer:false];
-    
+    _obj = obj;
     
-    if(uic_toolbar_isenabled()) {
-        UiToolbar *toolbar = [[UiToolbar alloc]initWithObject:obj];
-        [self setToolbar:toolbar];
-    }
     
     int top = 4;
     NSView *content = self.contentView;
@@ -72,6 +68,7 @@
         splitview.dividerStyle = NSSplitViewDividerStyleThin;
         splitview.translatesAutoresizingMaskIntoConstraints = false;
         [self.contentView addSubview:splitview];
+        _splitview = splitview;
         
         [NSLayoutConstraint activateConstraints:@[
             [splitview.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0],
@@ -135,6 +132,12 @@
     }
     _topOffset = top;
     
+    if(uic_toolbar_isenabled()) {
+        UiToolbar *toolbar = [[UiToolbar alloc]initWithWindow:self];
+        [self setToolbar:toolbar];
+    }
+    
+    
     return self;
 }
 
--- a/ui/cocoa/Toolbar.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/Toolbar.h	Mon Nov 10 21:52:51 2025 +0100
@@ -28,6 +28,7 @@
 
 #import "toolkit.h"
 #import "../common/toolbar.h"
+#import "MainWindow.h"
 
 /*
  * UiToolbarDelegate
@@ -49,9 +50,10 @@
     NSMutableArray<NSString*> *defaultItems;
 }
 
+@property MainWindow *window;
 @property UiObject *obj;
 
-- (UiToolbar*) initWithObject:(UiObject*)object;
+- (UiToolbar*) initWithWindow:(MainWindow*)window;
 
 @end
 
--- a/ui/cocoa/Toolbar.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/Toolbar.m	Mon Nov 10 21:52:51 2025 +0100
@@ -45,13 +45,21 @@
 
 @implementation UiToolbar
 
-- (UiToolbar*) initWithObject:(UiObject*)object {
+- (UiToolbar*) initWithWindow:(MainWindow*)window {
     self = [super initWithIdentifier:@"UiToolbar"];
-    _obj = object;
+    _window = window;
+    _obj = window.obj;
     
     allowedItems = [[NSMutableArray alloc]initWithCapacity:16];
     defaultItems = [[NSMutableArray alloc]initWithCapacity:16];
     
+    if(window.sidebar) {
+        [allowedItems addObject:@"sidebar_separator"];
+    }
+    if(window.leftPanel) {
+        [allowedItems addObject:@"splitview_separator"];
+    }
+    
     CxMap *toolbarItems = uic_get_toolbar_items();
     CxMapIterator i = cxMapIteratorKeys(toolbarItems);
     cx_foreach(CxHashKey *, key, i) {
@@ -61,18 +69,59 @@
     [allowedItems addObject: NSToolbarFlexibleSpaceItemIdentifier];
     [allowedItems addObject: NSToolbarSpaceItemIdentifier];
     
-    CxList *tbitems[3];
-    tbitems[0] =  uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
-    tbitems[1] =  uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
-    tbitems[2] =  uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
-    for(int t=0;t<3;t++) {
-        CxIterator iter = cxListIterator(tbitems[t]);
+    // UI_TOOLBAR_LEFT = 0,
+    // UI_TOOLBAR_CENTER,
+    // UI_TOOLBAR_RIGHT,
+    // UI_TOOLBAR_SIDEBAR_LEFT,
+    // UI_TOOLBAR_SIDEBAR_RIGHT,
+    // UI_TOOLBAR_RIGHTPANEL_LEFT,
+    // UI_TOOLBAR_RIGHTPANEL_CENTER,
+    // UI_TOOLBAR_RIGHTPANEL_RIGHT
+    CxList *tbitems[8];
+    for(int i=0;i<8;i++) {
+        tbitems[i] = uic_get_toolbar_defaults(i);
+    }
+    
+    if(window.sidebar) {
+        CxIterator iter = cxListIterator(tbitems[UI_TOOLBAR_SIDEBAR_LEFT]);
         cx_foreach(char *, name, iter) {
             NSString *s = [[NSString alloc] initWithUTF8String:name];
             [defaultItems addObject:s];
         }
+        
+        CxList *sidebarRight = tbitems[UI_TOOLBAR_SIDEBAR_RIGHT];
+        if(cxListSize(sidebarRight) > 0) {
+            [defaultItems addObject:NSToolbarFlexibleSpaceItemIdentifier];
+            iter = cxListIterator(sidebarRight);
+            cx_foreach(char *, name, iter) {
+                NSString *s = [[NSString alloc] initWithUTF8String:name];
+                [defaultItems addObject:s];
+            }
+        }
+        
+        [defaultItems addObject:@"sidebar_separator"];
     }
     
+    int start_pos = UI_TOOLBAR_LEFT;
+    for(int x=0;x<2;x++) {
+        for(int t=start_pos;t<start_pos+3;t++) {
+            CxIterator iter = cxListIterator(tbitems[t]);
+            cx_foreach(char *, name, iter) {
+                NSString *s = [[NSString alloc] initWithUTF8String:name];
+                [defaultItems addObject:s];
+            }
+            if(t < start_pos+2 && cxListSize(tbitems[t+1]) > 0) {
+                [defaultItems addObject:NSToolbarFlexibleSpaceItemIdentifier];
+            }
+        }
+        
+        if(x == 0 && window.rightPanel) {
+            [defaultItems addObject:@"splitview_separator"];
+        }
+        start_pos = UI_TOOLBAR_RIGHTPANEL_LEFT;
+    }
+    
+    
     [self setDelegate:self];
     [self setAllowsUserCustomization:YES];
     return self;
@@ -94,6 +143,18 @@
     CxMap *items = uic_get_toolbar_items();
     UiToolbarItemI *item = cxMapGet(items, itemIdentifier.UTF8String);
     if(!item) {
+        if([itemIdentifier isEqualToString:@"sidebar_separator"]) {
+            NSTrackingSeparatorToolbarItem *sep = [NSTrackingSeparatorToolbarItem trackingSeparatorToolbarItemWithIdentifier:itemIdentifier
+                                                                                                                   splitView:_window.splitview
+                                                                                                                dividerIndex:0];
+            return sep;
+        } else if([itemIdentifier isEqualToString:@"splitview_separator"]) {
+            int dividerIndex = _window.sidebar != nil ? 1 : 0;
+            NSTrackingSeparatorToolbarItem *sep = [NSTrackingSeparatorToolbarItem trackingSeparatorToolbarItemWithIdentifier:itemIdentifier
+                                                                                                                   splitView:_window.splitview
+                                                                                                                dividerIndex:dividerIndex];
+            return sep;
+        }
         return nil;
     }
     
--- a/ui/cocoa/appdelegate.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/appdelegate.m	Mon Nov 10 21:52:51 2025 +0100
@@ -35,6 +35,7 @@
 
 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
     ui_menu_init();
+    NSLog(@"toolkit applicationDidFinishLaunching");
     ui_cocoa_onstartup();
 }
 
--- a/ui/cocoa/button.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/button.m	Mon Nov 10 21:52:51 2025 +0100
@@ -29,6 +29,7 @@
 #import "button.h"
 #import "EventData.h"
 #import "Container.h"
+#import "image.h"
 #import <objc/runtime.h>
 
 #import <cx/buffer.h>
@@ -41,6 +42,9 @@
         NSString *label = [[NSString alloc] initWithUTF8String:args->label];
         button.title = label;
     }
+    if(args->icon) {
+        button.image = ui_cocoa_named_icon(args->icon);;
+    }
     
     if(args->onclick) {
         EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata];
@@ -72,6 +76,9 @@
         NSString *label = [[NSString alloc] initWithUTF8String:args->label];
         button.title = label;
     }
+    if(args->icon) {
+        button.image = ui_cocoa_named_icon(args->icon);
+    }
     
     UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER);
     if(var) {
--- a/ui/cocoa/container.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/container.m	Mon Nov 10 21:52:51 2025 +0100
@@ -185,6 +185,22 @@
 }
 
 
+UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs *args) {
+    return NULL; // TODO
+}
+
+UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args) {
+    return NULL; // TODO
+}
+
+UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) {
+    return NULL; // TODO
+}
+
+UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) {
+    return NULL; // TODO
+}
+
 
 
 void ui_container_begin_close(UiObject *obj) {
--- a/ui/cocoa/image.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/image.m	Mon Nov 10 21:52:51 2025 +0100
@@ -30,21 +30,71 @@
 
 static NSDictionary *standardIconNames;
 
+#define UI_ICON_ENTRY(x) @#x : x
+
 void ui_icon_init(void) {
     standardIconNames = @{
-        @"NSImageNameActionTemplate": NSImageNameActionTemplate,
-        @"NSImageNameAddTemplate": NSImageNameAddTemplate,
-        @"NSImageNameAdvanced": NSImageNameAdvanced,
-        @"NSImageNameApplicationIcon": NSImageNameApplicationIcon,
-        @"NSImageNameBluetoothTemplate": NSImageNameBluetoothTemplate,
-        @"NSImageNameBonjour": NSImageNameBonjour,
-        @"NSImageNameBookmarksTemplate": NSImageNameBookmarksTemplate,
-        @"NSImageNameCaution": NSImageNameCaution,
-        // TODO
-        @"NSImageNameRefreshTemplate": NSImageNameRefreshTemplate,
-        @"NSImageNameFolder": NSImageNameFolder,
-        @"NSImageNameGoForwardTemplate": NSImageNameGoForwardTemplate,
-        @"NSImageNameGoBackTemplate": NSImageNameGoBackTemplate
+        UI_ICON_ENTRY(NSImageNameAddTemplate),
+        UI_ICON_ENTRY(NSImageNameBluetoothTemplate),
+        UI_ICON_ENTRY(NSImageNameBonjour),
+        UI_ICON_ENTRY(NSImageNameBookmarksTemplate),
+        UI_ICON_ENTRY(NSImageNameCaution),
+        UI_ICON_ENTRY(NSImageNameComputer),
+        UI_ICON_ENTRY(NSImageNameEnterFullScreenTemplate),
+        UI_ICON_ENTRY(NSImageNameExitFullScreenTemplate),
+        UI_ICON_ENTRY(NSImageNameFolder),
+        UI_ICON_ENTRY(NSImageNameFolderBurnable),
+        UI_ICON_ENTRY(NSImageNameFolderSmart),
+        UI_ICON_ENTRY(NSImageNameFollowLinkFreestandingTemplate),
+        UI_ICON_ENTRY(NSImageNameHomeTemplate),
+        UI_ICON_ENTRY(NSImageNameIChatTheaterTemplate),
+        UI_ICON_ENTRY(NSImageNameLockLockedTemplate),
+        UI_ICON_ENTRY(NSImageNameLockUnlockedTemplate),
+        UI_ICON_ENTRY(NSImageNameNetwork),
+        UI_ICON_ENTRY(NSImageNamePathTemplate),
+        UI_ICON_ENTRY(NSImageNameQuickLookTemplate),
+        UI_ICON_ENTRY(NSImageNameRefreshFreestandingTemplate),
+        UI_ICON_ENTRY(NSImageNameRefreshTemplate),
+        UI_ICON_ENTRY(NSImageNameRemoveTemplate),
+        UI_ICON_ENTRY(NSImageNameRevealFreestandingTemplate),
+        UI_ICON_ENTRY(NSImageNameShareTemplate),
+        UI_ICON_ENTRY(NSImageNameSlideshowTemplate),
+        UI_ICON_ENTRY(NSImageNameStatusAvailable),
+        UI_ICON_ENTRY(NSImageNameStatusNone),
+        UI_ICON_ENTRY(NSImageNameStatusPartiallyAvailable),
+        UI_ICON_ENTRY(NSImageNameStatusUnavailable),
+        UI_ICON_ENTRY(NSImageNameStopProgressFreestandingTemplate),
+        UI_ICON_ENTRY(NSImageNameStopProgressTemplate),
+        UI_ICON_ENTRY(NSImageNameTrashEmpty),
+        UI_ICON_ENTRY(NSImageNameTrashFull),
+        UI_ICON_ENTRY(NSImageNameActionTemplate),
+        UI_ICON_ENTRY(NSImageNameSmartBadgeTemplate),
+        UI_ICON_ENTRY(NSImageNameIconViewTemplate),
+        UI_ICON_ENTRY(NSImageNameListViewTemplate),
+        UI_ICON_ENTRY(NSImageNameColumnViewTemplate),
+        UI_ICON_ENTRY(NSImageNameFlowViewTemplate),
+        UI_ICON_ENTRY(NSImageNameInvalidDataFreestandingTemplate),
+        UI_ICON_ENTRY(NSImageNameGoForwardTemplate),
+        UI_ICON_ENTRY(NSImageNameGoBackTemplate),
+        UI_ICON_ENTRY(NSImageNameGoRightTemplate),
+        UI_ICON_ENTRY(NSImageNameGoLeftTemplate),
+        UI_ICON_ENTRY(NSImageNameRightFacingTriangleTemplate),
+        UI_ICON_ENTRY(NSImageNameLeftFacingTriangleTemplate),
+        UI_ICON_ENTRY(NSImageNameMobileMe),
+        UI_ICON_ENTRY(NSImageNameMultipleDocuments),
+        UI_ICON_ENTRY(NSImageNameUserAccounts),
+        UI_ICON_ENTRY(NSImageNamePreferencesGeneral),
+        UI_ICON_ENTRY(NSImageNameAdvanced),
+        UI_ICON_ENTRY(NSImageNameInfo),
+        UI_ICON_ENTRY(NSImageNameFontPanel),
+        UI_ICON_ENTRY(NSImageNameColorPanel),
+        UI_ICON_ENTRY(NSImageNameUser),
+        UI_ICON_ENTRY(NSImageNameUserGroup),
+        UI_ICON_ENTRY(NSImageNameEveryone),
+        UI_ICON_ENTRY(NSImageNameUserGuest),
+        UI_ICON_ENTRY(NSImageNameMenuOnStateTemplate),
+        UI_ICON_ENTRY(NSImageNameMenuMixedStateTemplate),
+        UI_ICON_ENTRY(NSImageNameApplicationIcon)
     };
 }
 
--- a/ui/cocoa/label.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/label.m	Mon Nov 10 21:52:51 2025 +0100
@@ -27,7 +27,7 @@
  */
 
 #import "label.h"
-#import "container.h"
+#import "Container.h"
 
 #import <string.h>
 
--- a/ui/cocoa/list.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/list.m	Mon Nov 10 21:52:51 2025 +0100
@@ -363,6 +363,18 @@
 
 /* --------------------------- SourceList --------------------------- */
 
+static ui_sourcelist_update_func sclist_update_callback = NULL;
+
+void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) {
+    sclist_update_callback = cb;
+}
+
+void ui_sourcelist_updated(void) {
+    if(sclist_update_callback) {
+        sclist_update_callback();
+    }
+}
+
 static void sublist_free(const CxAllocator *a, UiSubList *sl) {
     cxFree(a, (char*)sl->varname);
     cxFree(a, (char*)sl->header);
--- a/ui/cocoa/objs.mk	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/objs.mk	Mon Nov 10 21:52:51 2025 +0100
@@ -50,6 +50,7 @@
 COCOAOBJ += widget.o
 COCOAOBJ += image.o
 COCOAOBJ += entry.o
+COCOAOBJ += TabView.o
 
 TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
 TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/toolkit.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/toolkit.h	Mon Nov 10 21:52:51 2025 +0100
@@ -31,10 +31,10 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
-@interface UiAppCallback : NSObject {
-    ui_threadfunc callback;
-    void          *userdata;
-}
+@interface UiAppCallback : NSObject
+
+@property    ui_threadfunc callback;
+@property    void          *userdata;
 
 - (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata;
 
--- a/ui/cocoa/toolkit.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/toolkit.m	Mon Nov 10 21:52:51 2025 +0100
@@ -57,6 +57,7 @@
 
 /* ------------------- App Init / Event Loop functions ------------------- */
 
+
 void ui_init(const char *appname, int argc, char **argv) {
     application_name = appname;
     app_argc = argc;
@@ -69,7 +70,9 @@
 
     uic_load_app_properties();
 
-    [NSApplication sharedApplication];
+    NSApplication *app = [NSApplication sharedApplication];
+    //[app setActivationPolicy:NSApplicationActivationPolicyRegular];
+    
     //[NSBundle loadNibNamed:@"MainMenu" owner:NSApp ];
     //[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&topLevelObjects];
     
@@ -178,8 +181,8 @@
 @implementation UiAppCallback
 
 - (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata {
-    self->callback = func;
-    self->userdata = userdata;
+    _callback = func;
+    _userdata = userdata;
     return self;
 }
 
@@ -190,7 +193,9 @@
 }
 
 - (void) mainThread:(id)n {
-    callback(userdata);
+    if(_callback) {
+        _callback(_userdata);
+    }
 }
 
 @end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/webview.h	Mon Nov 10 21:52:51 2025 +0100
@@ -0,0 +1,58 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+#import "../ui/webview.h"
+
+#import <WebKit/WebKit.h>
+
+
+enum WebViewDataType {
+    WEBVIEW_CONTENT_URL,
+    WEBVIEW_CONTENT_CONTENT
+};
+    
+struct UiWebViewData {
+    void *webview;
+    char *uri;
+    char *mimetype;
+    char *encoding;
+    char *content;
+    size_t contentlength;
+    enum WebViewDataType type;
+    
+    double zoom;
+    UiBool javascript;
+};
+
+UiWebViewData* ui_webview_data_clone(UiWebViewData *data);
+
+void* ui_webview_get(UiGeneric *g);
+const char* ui_webview_get_type(UiGeneric *g);
+int ui_webview_set(UiGeneric *g, void *data, const char *type);
+void ui_webview_destroy(UiGeneric *g);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/webview.m	Mon Nov 10 21:52:51 2025 +0100
@@ -0,0 +1,273 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "webview.h"
+#import "Container.h"
+
+UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) {
+    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC);
+    
+    WKWebView *webview = [[WKWebView alloc]init];
+    
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+    ui_container_add(obj, webview, &layout);
+    
+    if(var) {
+        UiGeneric *value = var->value;
+        value->get = ui_webview_get;
+        value->get_type = ui_webview_get_type;
+        value->set = ui_webview_set;
+        value->destroy = ui_webview_destroy;
+        value->obj = (__bridge void*)webview;
+        if(value->value) {
+            ui_webview_set(value, value->value, UI_WEBVIEW_OBJECT_TYPE);
+        } else {
+            UiWebViewData *data = malloc(sizeof(UiWebViewData));
+            memset(data, 0, sizeof(UiWebViewData));
+            data->webview = (__bridge void*)webview;
+            data->javascript = TRUE;
+            data->zoom = 1;
+            value->value = value;
+        }
+    }
+    
+    return (__bridge void*)webview;
+}
+
+UiWebViewData* ui_webview_data_clone(UiWebViewData *data) {
+    UiWebViewData *newdata = malloc(sizeof(UiWebViewData));
+    memset(newdata, 0, sizeof(UiWebViewData));
+    newdata->zoom = 1;
+    newdata->javascript = TRUE;
+    
+    if(data) {
+        newdata->uri = data->uri ? strdup(data->uri) : NULL;
+        newdata->mimetype = data->mimetype ? strdup(data->mimetype) : NULL;
+        newdata->encoding = data->encoding ? strdup(data->encoding) : NULL;
+        newdata->type = data->type;
+        newdata->javascript = data->javascript;
+        newdata->zoom = data->zoom;
+        if(data->content && data->contentlength > 0) {
+            newdata->content = malloc(data->contentlength + 1);
+            memcpy(newdata->content, data->content, data->contentlength);
+            newdata->content[data->contentlength] = 0;
+            newdata->contentlength = data->contentlength;
+        }
+    }
+    
+    return newdata;
+}
+
+void ui_webview_data_free(UiWebViewData *data) {
+    if(!data) {
+        return;
+    }
+    free(data->uri);
+    free(data->mimetype);
+    free(data->encoding);
+    free(data->content);
+    free(data);
+}
+
+void* ui_webview_get(UiGeneric *g) {
+    UiWebViewData *data = g->value;
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    
+    if(data->type == WEBVIEW_CONTENT_URL) {
+        (void)ui_webview_get_uri(g); // this updates data->uri
+    }
+    data->zoom = webview.pageZoom;
+    
+    return ui_webview_data_clone(g->value);
+}
+
+const char* ui_webview_get_type(UiGeneric *g) {
+    return UI_WEBVIEW_OBJECT_TYPE;
+}
+
+int ui_webview_set(UiGeneric *g, void *data, const char *type) {
+    if(!data || !type) {
+        return 1;
+    }
+    if(strcmp(type, UI_WEBVIEW_OBJECT_TYPE)) {
+        return 1;
+    }
+    ui_webview_data_free(g->value);
+    g->value = ui_webview_data_clone(data);
+    
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    UiWebViewData *webd = data;
+    if(webd->type == WEBVIEW_CONTENT_URL) {
+        const char *uri = webd->uri ? webd->uri : "about:blank";
+        NSURL *url = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:uri]];
+        if(url) {
+            NSURLRequest *req = [NSURLRequest requestWithURL:url];
+            if(req) {
+                [webview loadRequest:req];
+            }
+        }
+    } else {
+        NSString *mimetype = [[NSString alloc]initWithUTF8String: webd->mimetype ? webd->mimetype : "text/plain"];
+        NSString *encoding = [[NSString alloc]initWithUTF8String: webd->encoding ? webd->encoding : "UTF-8"];
+        NSString *urlStr = [[NSString alloc]initWithUTF8String: webd->uri ? webd->uri : "file:///"];
+        NSURL *url = [NSURL URLWithString:urlStr];
+        NSData *content = [NSData dataWithBytes:webd->content length:webd->contentlength];
+        if(!url) {
+            url = [NSURL URLWithString:@"about:blank"];
+        }
+        [webview loadData:content MIMEType:mimetype characterEncodingName:encoding baseURL:url];
+    }
+    
+    webview.pageZoom = webd->zoom;
+    
+    return 1;
+}
+
+void ui_webview_destroy(UiGeneric *g) {
+    ui_webview_data_free(g->value);
+    g->value = NULL;
+}
+
+
+void ui_webview_load_url(UiGeneric *g, const char *url) {
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    UiWebViewData *data = g->value;
+    data->type = WEBVIEW_CONTENT_URL;
+    
+    if(!url) {
+        url = "about:blank";
+    }
+    
+    NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];
+    if(nsurl) {
+        NSURLRequest *req = [NSURLRequest requestWithURL:nsurl];
+        if(req) {
+            [webview loadRequest:req];
+        }
+    }
+}
+
+void ui_webview_load_content(
+        UiGeneric *g,
+        const char *uri,
+        const char *content,
+        size_t contentlength,
+        const char *mimetype,
+        const char *encoding)
+{
+    UiWebViewData *data = g->value;
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    
+    data->type = WEBVIEW_CONTENT_CONTENT;
+    
+    free(data->uri);
+    data->uri = NULL;
+    free(data->mimetype);
+    free(data->encoding);
+    free(data->content);
+    data->type = WEBVIEW_CONTENT_URL;
+    
+    data->content = malloc(contentlength+1);
+    memcpy(data->content, content, contentlength);
+    data->content[contentlength] = 0;
+    
+    if(!mimetype) {
+        mimetype = "text/plain";
+    }
+    if(!encoding) {
+        encoding = "UTF-8";
+    }
+    
+    data->mimetype = strdup(mimetype);
+    data->encoding = strdup(encoding);
+    
+    NSString *mtype = [[NSString alloc]initWithUTF8String:mimetype];
+    NSString *enc = [[NSString alloc]initWithUTF8String:encoding];
+    NSData *ct = [NSData dataWithBytes:content length:contentlength];
+    NSURL *url;
+    if(uri) {
+        NSString *uriStr = [[NSString alloc]initWithUTF8String:uri];
+        url = [NSURL URLWithString:uriStr];
+    } else {
+        url = [NSURL URLWithString:@"file:///"];
+    }
+    [webview loadData:ct MIMEType:mtype characterEncodingName:enc baseURL:url];
+}
+
+
+void ui_webview_reload(UiGeneric *g) {
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    [webview reload];
+}
+
+UiBool ui_webview_can_go_back(UiGeneric *g) {
+    return FALSE;
+}
+
+UiBool ui_webview_can_go_forward(UiGeneric *g) {
+    return FALSE;
+}
+
+void ui_webview_go_back(UiGeneric *g) {
+    
+}
+
+void ui_webview_go_forward(UiGeneric *g) {
+    
+}
+
+const char * ui_webview_get_uri(UiGeneric *g) {
+    UiWebViewData *data = g->value;
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    
+    free(data->uri);
+    data->uri = NULL;
+    
+    NSURL *url = webview.URL;
+    if(url) {
+        NSString *s = [url absoluteString];
+        if(s) {
+            data->uri = strdup(s.UTF8String);
+        }
+    }
+    return data->uri;
+}
+
+void ui_webview_enable_javascript(UiGeneric *g, UiBool enable) {
+    // unsupported
+}
+
+void ui_webview_set_zoom(UiGeneric *g, double zoom) {
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    webview.pageZoom = zoom;
+}
+
+double ui_webview_get_zoom(UiGeneric *g) {
+    WKWebView *webview = (__bridge WKWebView*)g->obj;
+    return webview.pageZoom;
+}
--- a/ui/cocoa/widget.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/widget.h	Mon Nov 10 21:52:51 2025 +0100
@@ -27,5 +27,5 @@
  */
 
 #import "toolkit.h"
-#import "container.h"
+#import "Container.h"
 #import "../ui/widget.h"
--- a/ui/cocoa/widget.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/widget.m	Mon Nov 10 21:52:51 2025 +0100
@@ -45,6 +45,11 @@
 }
 
 
+UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) {
+    // TODO
+    return NULL;
+}
+
 
 /* custom widget */
 
--- a/ui/cocoa/window.m	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/cocoa/window.m	Mon Nov 10 21:52:51 2025 +0100
@@ -44,6 +44,11 @@
 
 #include <cx/mempool.h>
 
+static int window_default_width = 650;
+static int window_default_height = 550;
+
+static int splitview_window_default_pos = -1;
+static UiBool splitview_window_use_prop = TRUE;
 
 static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar, BOOL splitview) {
     UiObject *obj = uic_object_new_toplevel();
@@ -81,9 +86,19 @@
 }
 
 UiObject* ui_splitview_window(const char *title, UiBool sidebar) {
+    sleep(1);
     return create_window(title, FALSE, sidebar, TRUE);
 }
 
+void ui_window_size(UiObject *obj, int width, int height) {
+    // TODO
+}
+
+void ui_window_default_size(int width, int height) {
+    window_default_width = width;
+    window_default_height = height;
+}
+
 /* --------------------------------- File Dialogs --------------------------------- */
 
 void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
--- a/ui/common/context.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/context.c	Mon Nov 10 21:52:51 2025 +0100
@@ -549,7 +549,7 @@
     cxListFree(groups);
 }
 
-void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups) {
+void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups) {
     if(enable == NULL) {
         enable = (ui_enablefunc)ui_set_enabled;
     }
@@ -561,7 +561,7 @@
     cxListFree(ls);
 }
 
-void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, int *states, int nstates) {
+void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, const int *states, int nstates) {
     ui_widget_set_groups2(ctx, widget, (ui_enablefunc)ui_set_visible, states, nstates);
 }
 
@@ -622,7 +622,7 @@
     return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL;
 }
 
-UIEXPORT char* ui_strdup(UiContext *ctx, const char *str) {
+char* ui_strdup(UiContext *ctx, const char *str) {
     if(!ctx) {
         return NULL;
     }
@@ -630,3 +630,11 @@
     cxmutstr d = cx_strdup_a(ctx->allocator, s);
     return d.ptr;
 }
+
+void  ui_reg_destructor(UiContext *ctx, void *data, ui_destructor_func destr) {
+    cxMempoolRegister(ctx->mp, data, (cx_destructor_func)destr);
+}
+
+void  ui_set_destructor(void *mem, ui_destructor_func destr) {
+    cxMempoolSetDestructor(mem, (cx_destructor_func)destr);
+}
--- a/ui/common/menu.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/menu.c	Mon Nov 10 21:52:51 2025 +0100
@@ -41,6 +41,22 @@
 
 static int menu_item_counter = 0;
 
+static void *tmp_eventdata;
+static int tmp_eventdata_type;
+
+void uic_set_tmp_eventdata(void *eventdata, int type) {
+    tmp_eventdata = eventdata;
+    tmp_eventdata_type = type;
+}
+
+void* uic_get_tmp_eventdata(void) {
+    return tmp_eventdata;
+}
+
+int uic_get_tmp_eventdata_type(void) {
+    return tmp_eventdata_type;
+}
+
 void uic_menu_init(void) {
     global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
     current_builder = &global_builder;
--- a/ui/common/menu.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/menu.h	Mon Nov 10 21:52:51 2025 +0100
@@ -131,6 +131,10 @@
 
 int* uic_copy_groups(const int* groups, size_t *ngroups);
 
+void uic_set_tmp_eventdata(void *eventdata, int type);
+void* uic_get_tmp_eventdata(void);
+int uic_get_tmp_eventdata_type(void);
+
 #ifdef __cplusplus
 }
 #endif
--- a/ui/common/object.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/object.c	Mon Nov 10 21:52:51 2025 +0100
@@ -28,7 +28,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-
 #include "object.h"
 #include "context.h"
 
@@ -107,11 +106,15 @@
 }
 
 UiObject* uic_object_new_toplevel(void) {
+    fflush(stdout);
     CxMempool *mp = cxMempoolCreateSimple(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
+    fflush(stdout);
     obj->ctx = uic_context(obj, mp);
     obj->ctx->parent = ui_global_context();
+    fflush(stdout);
     uic_object_created(obj);
+    fflush(stdout);
     return obj;
 }
 
@@ -141,4 +144,29 @@
     } else {
         toplevel->container_begin = NULL;
     }
+    
+    // TODO: free container?
 }
+
+/*
+ * This might look like a weird function, but in case a container creates a
+ * sub-container, 2 container objects are added to the list, however we want
+ * only one container, otherwise ui_container_finish() would not work
+ */
+void uic_object_remove_second_last_container(UiObject *toplevel) {
+    if(toplevel->container_end && toplevel->container_end->prev) {
+        UiContainerX *end = toplevel->container_end;
+        UiContainerX *rm = toplevel->container_end->prev;
+        
+        end->prev = rm->prev;
+        if(rm->prev) {
+            rm->prev->next = end;
+        } else {
+            toplevel->container_begin = end;
+        }
+        
+        // TODO: free container?
+    } else {
+        fprintf(stderr, "Error: uic_object_remove_second_last_container expected at least 2 containers\n");
+    }
+}
--- a/ui/common/object.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/object.h	Mon Nov 10 21:52:51 2025 +0100
@@ -51,6 +51,7 @@
 
 void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer);
 void uic_object_pop_container(UiObject *toplevel);
+void uic_object_remove_second_last_container(UiObject *toplevel);
 
 
 
--- a/ui/common/toolbar.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/toolbar.c	Mon Nov 10 21:52:51 2025 +0100
@@ -50,7 +50,7 @@
     return str ? strdup(str) : NULL;
 }
 
-static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs *args, size_t *ngroups) {
+static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs *args, size_t *ngroups, size_t *nvstates) {
     UiToolbarItemArgs newargs;
     newargs.label = nl_strdup(args->label);
     newargs.icon = nl_strdup(args->icon);
@@ -58,18 +58,19 @@
     newargs.onclick = args->onclick;
     newargs.onclickdata = args->onclickdata;
     newargs.groups = uic_copy_groups(args->groups, ngroups);
+    newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates);
     return newargs;
 }
 
 void ui_toolbar_item_create(const char* name, UiToolbarItemArgs *args) {
     UiToolbarItem* item = malloc(sizeof(UiToolbarItem));
     item->item.type = UI_TOOLBAR_ITEM;
-    item->args = itemargs_copy(args, &item->ngroups);
+    item->args = itemargs_copy(args, &item->ngroups, &item->nvstates);
     cxMapPut(toolbar_items, name, item);
 }
 
 
-static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs *args, size_t *ngroups) {
+static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs *args, size_t *ngroups, size_t *nvstates) {
     UiToolbarToggleItemArgs newargs;
     newargs.label = nl_strdup(args->label);
     newargs.icon = nl_strdup(args->icon);
@@ -78,21 +79,23 @@
     newargs.onchange = args->onchange;
     newargs.onchangedata = args->onchangedata;
     newargs.groups = uic_copy_groups(args->groups, ngroups);
+    newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates);
     return newargs;
 }
 
 void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs *args) {
     UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem));
     item->item.type = UI_TOOLBAR_TOGGLEITEM;
-    item->args = toggleitemargs_copy(args, &item->ngroups);
+    item->args = toggleitemargs_copy(args, &item->ngroups, &item->nvstates);
     cxMapPut(toolbar_items, name, item);
 }
 
-static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs *args) {
+static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs *args, size_t *nvstates) {
     UiToolbarMenuArgs newargs;
     newargs.label = nl_strdup(args->label);
     newargs.icon = nl_strdup(args->icon);
     newargs.tooltip = nl_strdup(args->tooltip);
+    newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates);
     return newargs;
 }
 
@@ -100,7 +103,7 @@
     UiToolbarMenuItem* item = malloc(sizeof(UiToolbarMenuItem));
     item->item.type = UI_TOOLBAR_MENU;
     memset(&item->menu, 0, sizeof(UiMenu));
-    item->args = menuargs_copy(args);
+    item->args = menuargs_copy(args, &item->nvstates);
 
     item->end = 0;
 
--- a/ui/common/toolbar.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/toolbar.h	Mon Nov 10 21:52:51 2025 +0100
@@ -63,18 +63,21 @@
     UiToolbarItemI item;
     UiToolbarItemArgs args;
     size_t ngroups;
+    size_t nvstates;
 };
 
 struct UiToolbarToggleItem {
     UiToolbarItemI item;
     UiToolbarToggleItemArgs args;
     size_t ngroups;
+    size_t nvstates;
 };
 
 struct UiToolbarMenuItem {
     UiToolbarItemI item;
     UiMenu menu;
     UiToolbarMenuArgs args;
+    size_t nvstates;
     int end;
 };
 
--- a/ui/common/types.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/common/types.c	Mon Nov 10 21:52:51 2025 +0100
@@ -313,18 +313,32 @@
     return d;
 }
 
+static void string_destroy(UiString *s) {
+    if(s->value.free && s->value.ptr) {
+        s->value.free(s->value.ptr);
+    }
+}
+
 UiString* ui_string_new(UiContext *ctx, const char *name) {
     UiString *s = ui_malloc(ctx, sizeof(UiString));
     memset(s, 0, sizeof(UiString));
+    ui_set_destructor(s, (ui_destructor_func)string_destroy);
     if(name) {
         uic_reg_var(ctx, name, UI_VAR_STRING, s);
     }
     return s;
 }
 
+static void text_destroy(UiText *t) {
+    if(t->destroy) {
+        t->destroy(t);
+    }
+}
+
 UiText* ui_text_new(UiContext *ctx, const char *name) {
     UiText *t = ui_malloc(ctx, sizeof(UiText));
     memset(t, 0, sizeof(UiText));
+    ui_set_destructor(t, (ui_destructor_func)text_destroy);
     if(name) {
         uic_reg_var(ctx, name, UI_VAR_TEXT, t);
     }
@@ -340,9 +354,16 @@
     return r;
 }
 
-UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, const char *name) {
+static void generic_destroy(UiGeneric *g) {
+    if(g->destroy) {
+        g->destroy(g);
+    }
+}
+
+UiGeneric* ui_generic_new(UiContext *ctx, const char *name) {
     UiGeneric *g = ui_malloc(ctx, sizeof(UiGeneric));
     memset(g, 0, sizeof(UiGeneric));
+    ui_set_destructor(g, (ui_destructor_func)generic_destroy);
     if(name) {
         uic_reg_var(ctx, name, UI_VAR_GENERIC, g);
     }
--- a/ui/gtk/headerbar.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/gtk/headerbar.c	Mon Nov 10 21:52:51 2025 +0100
@@ -165,6 +165,7 @@
 {
     GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.tooltip, item->args.onclick, item->args.onclickdata, 0, FALSE);
     ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states);
     WIDGET_ADD_CSS_CLASS(button, "flat");
     headerbar_add(headerbar, box, button, pos);
 }
@@ -178,6 +179,7 @@
 {
     GtkWidget *button = gtk_toggle_button_new();
     ui_set_widget_groups(obj->ctx, button, item->args.groups);
+    ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states);
     WIDGET_ADD_CSS_CLASS(button, "flat");
     ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.tooltip, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0);
     headerbar_add(headerbar, box, button, pos);
@@ -194,6 +196,7 @@
     
 #if GTK_MAJOR_VERSION >= 4
     GtkWidget *menubutton = gtk_menu_button_new();
+    ui_set_widget_visibility_states(obj->ctx, menubutton, item->args.visibility_states);
     if(item->args.label) {
         gtk_menu_button_set_label(GTK_MENU_BUTTON(menubutton), item->args.label);
     }
--- a/ui/gtk/list.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/gtk/list.c	Mon Nov 10 21:52:51 2025 +0100
@@ -2318,19 +2318,19 @@
 static void listbox_button_clicked(GtkWidget *button, UiEventDataExt *data) {
     UiListBoxSubList *sublist = data->customdata0;
     
-    UiSubListEventData eventdata;
-    eventdata.list = sublist->var->value;
-    eventdata.sublist_index = sublist->index;
-    eventdata.row_index = data->value0;
-    eventdata.sublist_userdata = sublist->userdata;
-    eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index);
-    eventdata.event_data = data->customdata2;
+    UiSubListEventData *eventdata = &sublist->listbox->current_eventdata;
+    eventdata->list = sublist->var->value;
+    eventdata->sublist_index = sublist->index;
+    eventdata->row_index = data->value0;
+    eventdata->sublist_userdata = sublist->userdata;
+    eventdata->row_data = eventdata->list->get(eventdata->list, eventdata->row_index);
+    eventdata->event_data = data->customdata2;
     
     UiEvent event;
     event.obj = data->obj;
     event.window = event.obj->window;
     event.document = event.obj->ctx->document;
-    event.eventdata = &eventdata;
+    event.eventdata = eventdata;
     event.eventdatatype = UI_EVENT_DATA_SUBLIST;
     event.intval = data->value0;
     event.set = ui_get_setop();
@@ -2340,13 +2340,15 @@
     }
     
     if(data->customdata3) {
+        uic_set_tmp_eventdata(eventdata, UI_EVENT_DATA_SUBLIST);
+        
         UIMENU menu = data->customdata3;
         g_object_set_data(G_OBJECT(button), "ui-button-popup", menu);
         gtk_popover_popup(GTK_POPOVER(menu));
     }
 }
 
-#if GTK_CHECK_VERSION(3, 0, 0)
+#if GTK_CHECK_VERSION(4, 0, 0)
 static void button_popover_closed(GtkPopover *popover, GtkWidget *button) {
     g_object_set_data(G_OBJECT(button), "ui-button-popup", NULL);
     if(g_object_get_data(G_OBJECT(button), "ui-button-invisible")) {
@@ -2354,6 +2356,14 @@
         gtk_widget_set_visible(button, FALSE);
     }
 }
+#else
+static void popup_hide(GtkWidget *self, GtkWidget *button) {
+    g_object_set_data(G_OBJECT(button), "ui-button-popup", NULL);
+    if(g_object_get_data(G_OBJECT(button), "ui-button-invisible")) {
+        g_object_set_data(G_OBJECT(button), "ui-button-invisible", NULL);
+        gtk_widget_set_visible(button, FALSE);
+    }
+}
 #endif
 
 static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) {
@@ -2433,7 +2443,11 @@
         if(item->button_menu) {
             UIMENU menu = ui_contextmenu_create(item->button_menu, listbox->obj, button);
             event->customdata3 = menu;
-            g_signal_connect(menu, "closed", G_CALLBACK(button_popover_closed), button);
+#if GTK_CHECK_VERSION(4, 0, 0)
+            g_signal_connect(menu, "closed", G_CALLBACK(button_popover_closed), button);        
+#else
+            g_signal_connect(menu, "hide", G_CALLBACK(popup_hide), button);        
+#endif
             ui_menubuilder_unref(item->button_menu);
         }
     }
--- a/ui/gtk/list.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/gtk/list.h	Mon Nov 10 21:52:51 2025 +0100
@@ -127,6 +127,7 @@
     void                     *onbuttonclickdata;
     GtkListBoxRow            *first_row;
     UiBool                   header_is_item;
+    UiSubListEventData       current_eventdata;
 };
 
 
--- a/ui/gtk/menu.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/gtk/menu.c	Mon Nov 10 21:52:51 2025 +0100
@@ -284,6 +284,7 @@
             event->callback = list->callback;
             event->value = i - 1;
             event->customdata = elm;
+            event->customint = UI_EVENT_DATA_LIST_ELM;
 
             g_signal_connect(
                 widget,
@@ -309,9 +310,17 @@
     evt.obj = event->obj;
     evt.window = event->obj->window;
     evt.document = event->obj->ctx->document;
+    if(event->customdata) {
+        evt.eventdata = event->customdata;
+        evt.eventdatatype = event->customint;
+    } else {
+        evt.eventdata = uic_get_tmp_eventdata();
+        evt.eventdatatype = uic_get_tmp_eventdata_type();
+    }
     evt.eventdata = event->customdata;
     evt.intval = event->value;
-    event->callback(&evt, event->userdata);    
+    event->callback(&evt, event->userdata);   
+    uic_set_tmp_eventdata(NULL, 0);
 }
 
 void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event) {
@@ -747,10 +756,16 @@
     evt.obj = event->obj;
     evt.window = event->obj->window;
     evt.document = event->obj->ctx->document;
-    evt.eventdata = event->customdata;
-    evt.eventdatatype = event->customint;
+    if(event->customdata) {
+        evt.eventdata = event->customdata;
+        evt.eventdatatype = event->customint;
+    } else {
+        evt.eventdata = uic_get_tmp_eventdata();
+        evt.eventdatatype = uic_get_tmp_eventdata_type();
+    }
     evt.intval = intval;
-    event->callback(&evt, event->userdata);    
+    event->callback(&evt, event->userdata);
+    uic_set_tmp_eventdata(NULL, 0);
 }
 
 void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
--- a/ui/gtk/toolkit.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/gtk/toolkit.c	Mon Nov 10 21:52:51 2025 +0100
@@ -40,7 +40,6 @@
 #include "../common/toolbar.h"
 #include "../common/threadpool.h"
 
-#include <cx/utils.h>
 #include <cx/string.h>
 #include <cx/printf.h>
 
@@ -544,3 +543,19 @@
         ui_set_enabled(widget, FALSE);
     }
 }
+
+void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const int *states) {
+    if(!states) {
+        return;
+    }
+    size_t nstates = uic_group_array_size(states);
+    ui_set_widget_nvisibility_states(ctx, widget, states, nstates);
+}
+
+
+void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups) {
+    if(ngroups > 0) {
+        uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups);
+        ui_set_visible(widget, FALSE);
+    }
+}
--- a/ui/gtk/toolkit.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/gtk/toolkit.h	Mon Nov 10 21:52:51 2025 +0100
@@ -180,6 +180,8 @@
 void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style);
 void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups);
 void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups);
+void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const int *states);
+void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups);
 
 void ui_destroy_userdata(GtkWidget *object, void *userdata);
 void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
--- a/ui/motif/container.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/container.c	Mon Nov 10 21:52:51 2025 +0100
@@ -227,6 +227,96 @@
     grid->container.container.newline = FALSE;
 }
 
+/* -------------------------- Frame Container -------------------------- */
+
+UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+    
+    Widget parent = ui_container_prepare(ctn, &layout, xargs, &n);
+    
+    char *name = args->name ? (char*)args->name : "frame";
+    Widget frame = XmCreateFrame(parent, name, xargs, 6);
+    XtManageChild(frame);
+    ui_container_add(ctn, frame);
+    
+    if(args->label) {
+        XmString s = XmStringCreateLocalized((char*)args->label);
+        n = 0;
+        XtSetArg(xargs[n], XmNlabelString, s); n++;
+        XtSetArg(xargs[n], XmNchildType, XmFRAME_TITLE_CHILD); n++;
+        Widget label = XmCreateLabel(frame, "frame_label", xargs, n);
+        XtManageChild(label);
+        XmStringFree(s);
+    }
+    
+    UiContainerX *container = ui_frame_container(obj, frame);
+    uic_object_push_container(obj, container);
+    
+    UiContainerArgs sub_args = {
+        .spacing = args->spacing,
+        .columnspacing = args->columnspacing,
+        .rowspacing = args->rowspacing
+    };
+    switch(args->subcontainer) {
+        default: break;
+        case UI_CONTAINER_VBOX: {
+            ui_vbox_create(obj, &sub_args);
+            uic_object_remove_second_last_container(obj);
+            break;
+        }
+        case UI_CONTAINER_HBOX: {
+            ui_hbox_create(obj, &sub_args);
+            uic_object_remove_second_last_container(obj);
+            break;
+        }
+        case UI_CONTAINER_GRID: {
+            ui_grid_create(obj, &sub_args);
+            uic_object_remove_second_last_container(obj);
+            break;
+        }
+    }
+    
+    
+    return frame;
+}
+
+UiContainerX* ui_frame_container(UiObject *obj, Widget frame) {
+    UiContainerPrivate *ctn = ui_malloc(obj->ctx, sizeof(UiContainerPrivate));
+    memset(ctn, 0, sizeof(UiContainerPrivate));
+    ctn->prepare = ui_frame_container_prepare;
+    ctn->add = ui_frame_container_add;
+    ctn->widget = frame;
+    return (UiContainerX*)ctn;
+}
+
+Widget ui_frame_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) {
+    int a = *n;
+    XtSetArg(args[a], XmNchildType, XmFRAME_WORKAREA_CHILD);
+    *n = a+1;
+    return ctn->widget;
+}
+
+void ui_frame_container_add(UiContainerPrivate *ctn, Widget widget) {
+    // NOOP
+}
+
+/* -------------------------- SplitPane -------------------------- */
+
+UIWIDGET ui_splitpane_create(UiObject *obj, UiSplitPaneArgs *args, int orientation) {
+    return NULL; // TODO
+}
+
+UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) {
+    return ui_splitpane_create(obj, args, XmHORIZONTAL);
+}
+
+UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) {
+    return ui_splitpane_create(obj, args, XmVERTICAL);
+}
 
 /* -------------------------- TabView Container -------------------------- */
 
--- a/ui/motif/container.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/container.h	Mon Nov 10 21:52:51 2025 +0100
@@ -156,6 +156,10 @@
 Widget ui_grid_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n);
 void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget);
 
+UiContainerX* ui_frame_container(UiObject *obj, Widget frame);
+Widget ui_frame_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n);
+void ui_frame_container_add(UiContainerPrivate *ctn, Widget widget);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/motif/graphics.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/graphics.c	Mon Nov 10 21:52:51 2025 +0100
@@ -34,3 +34,87 @@
 #include "graphics.h"
 
 #include "container.h"
+
+UIWIDGET ui_drawingarea_create(UiObject *obj, UiDrawingAreaArgs *args) {
+    Arg xargs[16];
+    int n = 0;
+    
+    UiContainerPrivate *ctn = ui_obj_container(obj);
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+    
+    Widget parent = ui_container_prepare(ctn, &layout, xargs, &n);
+    char *name = args->name ? (char*)args->name : "drawingarea";
+    
+    Widget widget = XmCreateDrawingArea(parent, name, xargs, n);
+    XtManageChild(widget);
+    ui_container_add(ctn, widget);
+    
+    UiDrawingArea *drawingarea = malloc(sizeof(UiDrawingArea));
+    drawingarea->obj = obj;
+    drawingarea->draw = args->draw;
+    drawingarea->drawdata = args->drawdata;
+    drawingarea->onclick = args->onclick;
+    drawingarea->onclickdata = args->onclickdata;
+    drawingarea->onmotion = args->onmotion;
+    drawingarea->onmotiondata = args->onmotiondata;
+    drawingarea->gc = NULL;
+    
+    XtAddCallback(
+                widget,
+                XmNdestroyCallback,
+                (XtCallbackProc)ui_drawingarea_destroy,
+                drawingarea);
+    XtAddCallback(
+                widget,
+                XmNexposeCallback,
+                (XtCallbackProc)ui_drawingarea_expose,
+                drawingarea);
+    
+    return widget;
+}
+
+void ui_drawingarea_destroy(Widget w, UiDrawingArea *drawingarea, XtPointer d) {
+    if(drawingarea->gc) {
+        XFreeGC(XtDisplay(w), drawingarea->gc);
+    }
+    free(drawingarea);
+}
+
+void ui_drawingarea_expose(Widget w, UiDrawingArea *drawingarea, XtPointer d) {
+    Display *dp = XtDisplay(w);
+    
+    if(!drawingarea->gc) {
+        XGCValues gcvals;
+        gcvals.foreground = BlackPixelOfScreen(XtScreen(w));
+        drawingarea->gc = XCreateGC(dp, XtWindow(w), (GCForeground), &gcvals);
+    }
+    
+    if(drawingarea->draw) {
+        UiEvent event;
+        event.obj = drawingarea->obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = NULL;
+        event.eventdatatype = 0;
+        event.intval = 0;
+        event.set = 0;
+        
+        UiXlibGraphics g;
+        g.g.width = w->core.width;
+        g.g.height = w->core.height;
+        g.widget = w;
+        g.display = dp;
+        g.colormap = w->core.colormap;
+        g.gc = drawingarea->gc;
+        
+        drawingarea->draw(&event, (UiGraphics*)&g, drawingarea->drawdata);
+    }
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+    
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+    
+}
--- a/ui/motif/graphics.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/graphics.h	Mon Nov 10 21:52:51 2025 +0100
@@ -35,7 +35,30 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
+    
+typedef struct UiDrawingArea {
+    UiObject *obj;
+    ui_drawfunc draw;
+    void *drawdata;
+    ui_callback onclick;
+    void *onclickdata;
+    ui_callback onmotion;
+    void *onmotiondata;
+    
+    GC gc;
+} UiDrawingArea;
 
+typedef struct UiXlibGraphics {
+    UiGraphics g;
+    Display    *display;
+    Widget     widget;
+    Colormap   colormap;
+    GC         gc;
+} UiXlibGraphics;
+
+void ui_drawingarea_destroy(Widget w, UiDrawingArea *drawingarea, XtPointer d);
+
+void ui_drawingarea_expose(Widget w, UiDrawingArea *drawingarea, XtPointer d);
 
 
 #ifdef	__cplusplus
--- a/ui/motif/label.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/label.c	Mon Nov 10 21:52:51 2025 +0100
@@ -47,17 +47,34 @@
     
     XtSetArg(xargs[n], XmNalignment, align); n++;
     XmString label = NULL;
-    if(args->label) {
-        label = XmStringCreateLocalized((char*)args->label);
-        XtSetArg(xargs[n], XmNlabelString, label); n++;
+    char *lbl = (char*)args->label;
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING);
+    if(var) {
+        UiString *s = var->value;
+        lbl = s->value.ptr;    
     }
     
+    if(lbl) {
+        label = XmStringCreateLocalized(lbl); 
+    } else {
+        label = XmStringCreateLocalized("");
+    }
+    
+    XtSetArg(xargs[n], XmNlabelString, label); n++;
     char *name = args->name ? (char*)args->name : "label";
     Widget w = XmCreateLabel(parent, name, xargs, n);
     XtManageChild(w);
     ui_container_add(ctn, w);
       
     XmStringFree(label);
+    
+    if(var) {
+        UiString *s = var->value;
+        s->obj = w;
+        s->get = ui_label_get;
+        s->set = ui_label_set;
+    }
+    
     return w;
 } 
 
@@ -73,6 +90,36 @@
     return label_create(obj, args, XmALIGNMENT_END);
 }
 
+char* ui_label_get(UiString *s) {
+    if(s->value.free) {
+        s->value.free(s->value.ptr);
+        s->value.free = NULL;
+        s->value.ptr = NULL;
+    }
+    Widget w = s->obj;
+    XmString s1 = NULL;
+    XtVaGetValues(w, XmNlabelString, &s1, NULL);
+    if(s1) {
+        char *value;
+        if(XmStringGetLtoR(s1, XmFONTLIST_DEFAULT_TAG, &value)) {
+            s->value.ptr = value;
+            s->value.free = (cx_destructor_func)XtFree;
+        }
+    }
+    return s->value.ptr;
+}
+
+void ui_label_set(UiString *s, const char *str) {
+    if(s->value.free) {
+        s->value.free(s->value.ptr);
+        s->value.free = NULL;
+        s->value.ptr = NULL;
+    }
+    Widget w = s->obj;
+    XmString s1 = XmStringCreateLocalized(str ? (char*)str : "");
+    XtVaSetValues(w, XmNlabelString, s1, NULL);
+    XmStringFree(s1);
+}
 
 /* -------------------------- progressbar/spiner -------------------------- */
 
--- a/ui/motif/label.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/label.h	Mon Nov 10 21:52:51 2025 +0100
@@ -46,6 +46,8 @@
     Pixel color;
 } UiProgressBar;
 
+char* ui_label_get(UiString *s);
+void ui_label_set(UiString *s, const char *str);
 
 double ui_progressbar_get(UiDouble *d);
 void ui_progressbar_set(UiDouble *d, double value);
--- a/ui/motif/toolkit.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/toolkit.c	Mon Nov 10 21:52:51 2025 +0100
@@ -120,6 +120,10 @@
     return application_name;
 }
 
+XtAppContext ui_motif_get_app(void) {
+    return app;
+}
+
 Display* ui_motif_get_display() {
     return display;
 }
--- a/ui/motif/toolkit.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/motif/toolkit.h	Mon Nov 10 21:52:51 2025 +0100
@@ -84,6 +84,7 @@
 
 void ui_exit_mainloop();
 
+XtAppContext ui_motif_get_app(void);
 Display* ui_motif_get_display(void);
 
 void ui_set_active_window(Widget w);
--- a/ui/ui/toolbar.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/ui/toolbar.h	Mon Nov 10 21:52:51 2025 +0100
@@ -45,6 +45,7 @@
     void* onclickdata;
 
     const int *groups;
+    const int *visibility_states;
 } UiToolbarItemArgs;
 
 typedef struct UiToolbarToggleItemArgs {
@@ -57,12 +58,14 @@
     void *onchangedata;
 
     const int *groups;
+    const int *visibility_states;
 } UiToolbarToggleItemArgs;
 
 typedef struct UiToolbarMenuArgs {
     const char *label;
     const char *icon;
     const char *tooltip;
+    const int  *visibility_states;
 } UiToolbarMenuArgs;
 
 enum UiToolbarPos {
--- a/ui/ui/toolkit.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/ui/toolkit.h	Mon Nov 10 21:52:51 2025 +0100
@@ -256,6 +256,9 @@
 
 typedef void(*ui_enablefunc)(void*, int);
 
+typedef void (*ui_destructor_func)(void *memory);
+
+
 struct UiObject {
     /*
      * native widget
@@ -405,6 +408,7 @@
     void* (*get)(UiGeneric*);
     const char* (*get_type)(UiGeneric*);
     int (*set)(UiGeneric*, void *, const char *type);
+    void (*destroy)(UiGeneric*);
     void *obj;
     
     void *value;
@@ -557,8 +561,8 @@
 UIEXPORT void ui_detach_document(UiContext *ctx, void *document);
 
 UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...);
-UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups);
-UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, int *states, int nstates);
+UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups);
+UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, const int *states, int nstates);
 
 UIEXPORT void ui_set_group(UiContext *ctx, int group);
 UIEXPORT void ui_unset_group(UiContext *ctx, int group);
@@ -572,6 +576,8 @@
 UIEXPORT void  ui_free(UiContext *ctx, void *ptr);
 UIEXPORT void* ui_realloc(UiContext *ctx, void *ptr, size_t size);
 UIEXPORT char* ui_strdup(UiContext *ctx, const char *str);
+UIEXPORT void  ui_reg_destructor(UiContext *ctx, void *data, ui_destructor_func destr);
+UIEXPORT void  ui_set_destructor(void *mem, ui_destructor_func destr);
 
 // types
 
--- a/ui/ui/webview.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/ui/webview.h	Mon Nov 10 21:52:51 2025 +0100
@@ -36,8 +36,16 @@
 extern "C" {
 #endif
     
+/*
+ * WebView type string used by UiGeneric
+ */
 #define UI_WEBVIEW_OBJECT_TYPE "webview"
-    
+
+/*
+ * UiWebViewData* is returned by a webviews UiGeneric->get function
+ */
+typedef struct UiWebViewData UiWebViewData;
+
 typedef struct UiWebviewArgs {
     UiBool fill;
     UiBool hexpand;
@@ -63,11 +71,11 @@
 
 #define ui_webview(obj, ...) ui_webview_create(obj, &(UiWebviewArgs){ __VA_ARGS__ } )
 
-UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args);
+UIEXPORT UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args);
 
-void ui_webview_load_url(UiGeneric *g, const char *url);
+UIEXPORT void ui_webview_load_url(UiGeneric *g, const char *url);
 
-void ui_webview_load_content(
+UIEXPORT void ui_webview_load_content(
         UiGeneric *g,
         const char *uri,
         const char *content,
@@ -75,16 +83,20 @@
         const char *mimetype,
         const char *encoding);
 
+/*
+ * Frees a UiWebViewData object returned by a webviews UiGeneric->get function
+ */
+UIEXPORT void ui_webview_data_free(UiWebViewData *data);
 
-void ui_webview_reload(UiGeneric *g);
-UiBool ui_webview_can_go_back(UiGeneric *g);
-UiBool ui_webview_can_go_forward(UiGeneric *g);
-void ui_webview_go_back(UiGeneric *g);
-void ui_webview_go_forward(UiGeneric *g);
-const char * ui_webview_get_uri(UiGeneric *g);
-void ui_webview_enable_javascript(UiGeneric *g, UiBool enable);
-void ui_webview_set_zoom(UiGeneric *g, double zoom);
-double ui_webview_get_zoom(UiGeneric *g);
+UIEXPORT void ui_webview_reload(UiGeneric *g);
+UIEXPORT UiBool ui_webview_can_go_back(UiGeneric *g);
+UIEXPORT UiBool ui_webview_can_go_forward(UiGeneric *g);
+UIEXPORT void ui_webview_go_back(UiGeneric *g);
+UIEXPORT void ui_webview_go_forward(UiGeneric *g);
+UIEXPORT const char * ui_webview_get_uri(UiGeneric *g);
+UIEXPORT void ui_webview_enable_javascript(UiGeneric *g, UiBool enable);
+UIEXPORT void ui_webview_set_zoom(UiGeneric *g, double zoom);
+UIEXPORT double ui_webview_get_zoom(UiGeneric *g);
 
 
 #ifdef __cplusplus
--- a/ui/win32/button.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/win32/button.c	Mon Nov 10 21:52:51 2025 +0100
@@ -51,10 +51,10 @@
     HWND hwnd = CreateWindow(
             "BUTTON",
             args->label,
-            WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
+            WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
             0, 0, 100, 30,
             parent,
-            (HMENU)0,
+            (HMENU)1,
             hInstance,
             NULL);
     ui_win32_set_ui_font(hwnd);
@@ -85,6 +85,9 @@
 void ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
     UiWidget *w = (UiWidget*)widget;
 
+    printf("button eventproc\n");
+    fflush(stdout);
+
     UiEvent e;
     e.obj = w->obj;
     e.document = e.obj->ctx->document;
--- a/ui/win32/container.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/win32/container.c	Mon Nov 10 21:52:51 2025 +0100
@@ -139,6 +139,7 @@
             NULL,
             hInstance,
             NULL);
+    SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)ui_default_eventproc);
 
     W32Widget *widget = w32_widget_new(&grid_layout_widget_class, hwnd);
     ui_container_add(container, widget, &layout);
--- a/ui/win32/objs.mk	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/win32/objs.mk	Mon Nov 10 21:52:51 2025 +0100
@@ -37,6 +37,7 @@
 WIN32OBJ += container.obj
 WIN32OBJ += button.obj
 WIN32OBJ += grid.obj
+WIN32OBJ += text.obj
 
 TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%)
 TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/text.c	Mon Nov 10 21:52:51 2025 +0100
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "text.h"
+
+static W32WidgetClass textfield_widget_class = {
+    .eventproc = ui_textfield_eventproc,
+    .enable = w32_widget_default_enable,
+    .show = w32_widget_default_show,
+    .get_preferred_size = ui_textfield_get_preferred_size,
+    .destroy  = w32_widget_default_destroy
+};
+
+UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs *args) {
+    HINSTANCE hInstance = GetModuleHandle(NULL);
+    UiContainerPrivate *container = ui_obj_container(obj);
+    HWND parent = ui_container_get_parent(container);
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+
+    int width = args->width >= 0 ? args->width : 100;
+
+    HWND hwnd = CreateWindowEx(
+            WS_EX_CLIENTEDGE,
+            "EDIT",
+            "",
+            WS_VISIBLE | WS_CHILD | ES_LEFT | ES_AUTOHSCROLL,
+            0, 0, width, 25,
+            parent,
+            (HMENU)0,
+            hInstance,
+            NULL);
+    ui_win32_set_ui_font(hwnd);
+
+    W32Widget *widget = w32_widget_create(&textfield_widget_class, hwnd, sizeof(UiTextField));
+    ui_container_add(container, widget, &layout);
+
+    UiTextField *textfield = (UiTextField*)widget;
+    textfield->width = width;
+    textfield->widget.var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING);
+    textfield->widget.callback = args->onchange;
+    textfield->widget.callbackdata = args->onchangedata;
+
+    if (textfield->widget.var) {
+        UiString *s = textfield->widget.var->value;
+
+        if (s->value.ptr) {
+            // TODO: set textfield string
+        }
+        s->obj = widget;
+        s->get = ui_textfield_get;
+        s->set = ui_textfield_set;
+    }
+
+    return widget;
+}
+
+W32Size ui_textfield_get_preferred_size(W32Widget *widget) {
+    UiTextField *textfield = (UiTextField *)widget;
+    return (W32Size){ .width = textfield->width, .height = 32};
+}
+
+void ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+
+}
+
+char* ui_textfield_get(UiString *s) {
+    UiTextField *textfield = s->obj;
+
+    if (s->value.free) {
+        s->value.free(s->value.ptr);
+    }
+
+    int len = GetWindowTextLength(textfield->widget.widget.hwnd);
+    s->value.ptr = calloc(len+1, 1);
+    GetWindowText(textfield->widget.widget.hwnd, s->value.ptr, len+1);
+
+    return s->value.ptr;
+}
+
+void ui_textfield_set(UiString *s, const char *value) {
+    UiTextField *textfield = s->obj;
+    if (s->value.free) {
+        s->value.free(s->value.ptr);
+    }
+    s->value.ptr = NULL;
+    SetWindowText(textfield->widget.widget.hwnd, value);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/text.h	Mon Nov 10 21:52:51 2025 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include "../ui/text.h"
+#include "container.h"
+#include "toolkit.h"
+
+typedef struct UiTextField {
+    UiWidget widget;
+    int width;
+} UiTextField;
+
+W32Size ui_textfield_get_preferred_size(W32Widget *widget);
+void ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+char* ui_textfield_get(UiString *s);
+void ui_textfield_set(UiString *s, const char *value);
+
+#endif /* TEXT_H */
\ No newline at end of file
--- a/ui/win32/toolkit.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/win32/toolkit.c	Mon Nov 10 21:52:51 2025 +0100
@@ -123,4 +123,34 @@
 
 void ui_show(UiObject *obj) {
     ui_set_visible(obj->widget, TRUE);
+}
+
+LRESULT CALLBACK ui_default_eventproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+    W32Widget *widget = (W32Widget*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+    if (widget && widget->wclass->eventproc) {
+        widget->wclass->eventproc(widget, hwnd, uMsg, wParam, lParam);
+    }
+    switch(uMsg) {
+        case WM_DESTROY: {
+            PostQuitMessage(0);
+            break;
+        }
+        case WM_COMMAND: {
+            HWND hwndCtrl = (HWND)lParam;
+            W32Widget *cmdWidget = (W32Widget*)GetWindowLongPtr(hwndCtrl, GWLP_USERDATA);
+            if (cmdWidget && cmdWidget->wclass->eventproc) {
+                cmdWidget->wclass->eventproc(cmdWidget, hwnd, uMsg, wParam, lParam);
+            }
+        }
+        case WM_SIZE: {
+            int width  = LOWORD(lParam);
+            int height = HIWORD(lParam);
+            if (widget->layout) {
+                widget->layout(widget->layoutmanager, width, height);
+            }
+            break;
+        }
+        default: return DefWindowProc(hwnd, uMsg, wParam, lParam);
+    }
+    return 0;
 }
\ No newline at end of file
--- a/ui/win32/toolkit.h	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/win32/toolkit.h	Mon Nov 10 21:52:51 2025 +0100
@@ -53,6 +53,8 @@
     int64_t intvalue;
 } UiWidget;
 
+LRESULT CALLBACK ui_default_eventproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
 HFONT ui_win32_get_font(void);
 void ui_win32_set_ui_font(HWND control);
 
--- a/ui/win32/window.c	Sun Oct 19 21:20:08 2025 +0200
+++ b/ui/win32/window.c	Mon Nov 10 21:52:51 2025 +0100
@@ -52,41 +52,12 @@
 
 static const char *mainWindowClass = "UiMainWindow";
 
-LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
-	W32Widget *widget = (W32Widget*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
-	if (widget && widget->wclass->eventproc) {
-		widget->wclass->eventproc(widget, hwnd, uMsg, wParam, lParam);
-	}
-    switch(uMsg) {
-        case WM_DESTROY: {
-	        PostQuitMessage(0);
-        	break;
-        }
-		case WM_COMMAND: {
-        	HWND hwndCtrl = (HWND)lParam;
-        	W32Widget *cmdWidget = (W32Widget*)GetWindowLongPtr(hwndCtrl, GWLP_USERDATA);
-        	if (cmdWidget && cmdWidget->wclass->eventproc) {
-        		cmdWidget->wclass->eventproc(cmdWidget, hwnd, uMsg, wParam, lParam);
-        	}
-        }
-		case WM_SIZE: {
-        	int width  = LOWORD(lParam);
-        	int height = HIWORD(lParam);
-        	if (widget->layout) {
-        		widget->layout(widget->layoutmanager, width, height);
-        	}
-        	break;
-		}
-        default: return DefWindowProc(hwnd, uMsg, wParam, lParam);
-    }
-    return 0;
-}
 
 void ui_window_init(void) {
 	hInstance = GetModuleHandle(NULL);
 	
 	WNDCLASSEX wc = { sizeof(WNDCLASSEX) };
-    wc.lpfnWndProc = WindowProc;
+    wc.lpfnWndProc = ui_default_eventproc;
     wc.hInstance = hInstance;
     wc.lpszClassName = mainWindowClass;
     wc.hCursor = LoadCursor(NULL, IDC_ARROW);

mercurial