Best practices for mod interoperability - DavidPH/GDCC GitHub Wiki

The problem

GDCC-CC implements the global address space (where all objects reside in and pointers point to by default) as a global array in ACS. This allows for many features (like dynamic memory allocation, passing around pointers across libraries). This can cause issues for multiple GDCC mods loaded at the same time. While this may be perfectly acceptable for major "base" projects and their add-ons, it may be more troublesome for smaller mods or libraries aiming to be compatible with other projects.

Solutions

To solve this, an approach that does not sacrifice many features can be used. By compiling libc and libGDCC into a separate module, and including it in the project under the name libc.o, only the latest libc.o will take precedence and all modules linking to it dynamically will use that instead. The minimum static allocation address should also be increased to leave room for mods needing it. The recommended setup is:

gdcc-makelib --alloc-minimum Sta "" 100000 --target-engine Zandronum -o libc.o libGDCC libc

The lower 100k words are available on the global address space, so there is more than enough space for most mods that would require static storage. The resulting libc.o can be directly included in the acs/ folder of the project and linked to with -llibc

This does not take care of everything, though. All statically allocated objects in the global address space will still potentially allocate over other mod's statically allocated objects. The easiest fix if the objects do not need to be pointed to is simply making a module-specific address space and defining them there. For example:

// Define a module address space called "mymod_sta"
__addrdef __mod_arr mymod_sta;

// Define a couple objects in that address space.
mymod_sta int test[16];
mymod_sta __str string;

Otherwise, you can place a pointer to a struct containing all your global objects in the global address space with a specific address, and dynamically allocate the struct with a no_init_delay OPEN script:

struct
{
    int test[16];
    __str string;
} mymod_globals_t;

[address(10), no_init](/DavidPH/GDCC/wiki/address(10),-no_init) mymod_globals_t *mymod_globals;

[call("ScriptS"), script("open"), no_init_delay](/DavidPH/GDCC/wiki/call("ScriptS"),-script("open"),-no_init_delay)
void mymod_globals_init ()
{
    if(mymod_globals)
        return;
    
    mymod_globals = malloc(sizeof(*mymod_globals));
    
    for(int i = 0; i < 16; i++)
        mymod_globals->test[i] = 0;
    
    mymod_globals->string = s"this is a string";
}

The specific address allocation is required to allow other modules to link to the struct properly (this doesn't automatically happen with GDCC-CC, it's not reasonably possible with the default address space implementation).

Other things to be aware of

C strings and ACS strings

C string literals need to statically allocate space on the global address space. The starting address can't be modified (outside of changing the allocation base for the whole module), so they may easily clash with other mods using static storage. The easiest fix is to simply use #pragma GDCC STRENT_LITERAL ON and use ACS strings where possible (and the _str variants of the C print functions). This comes with the caveat of not being able to directly use C string functions.

If using C string functions is required, there's other possible workarounds.

char string[] = "test C string";

won't allocate any space for the string literal, only the array. So the array can have an address attribute specifying a custom start address, or alternatively be declared inside a function which will dynamically allocate it (but will of course only last as long as the function itself does).

An ACS string could also be converted to C, using __sprintf_str (or doing the conversion manually), for example this:

__str myAcsString = s"this is a test string";

char *myCString = malloc(ACS_StrLen(myAcsString) + 1); /* add one to account for the null terminator */
__sprintf_str(myCString, s"%S", myAcsString);