Skip to content

C ABI missing some functions that are available in gdxcc.h #6

Description

@kdheepak

I noticed that some functions are not available in the dynamic library (inspected with nm on MacOS) but are available as part of the gdxcc.h header file.

I'm not sure if this is intended or if this should be a bug. Can you clarify is the intended API is

  1. only the c__gdx... handle-oriented API, OR
  2. the full set of gdxcc.h declarations.

Notes:

  • All of the c__gdx... symbols seem to be present in the dynamic library.
  • gdxcreate, gdxcreated, gdxfree, xcreate, and xfree are present. These
  • The following gdxcc.h declarations do not appear to be exported from the dylib:
gdxGetReady
gdxGetReadyD
gdxGetReadyL
gdxCreateDD
gdxCreateL
gdxLibraryLoaded
gdxLibraryUnload
gdxCorrectLibraryVersion
gdxGetScreenIndicator
gdxSetScreenIndicator
gdxGetExceptionIndicator
gdxSetExceptionIndicator
gdxGetExitIndicator
gdxSetExitIndicator
gdxGetErrorCallback
gdxSetErrorCallback
gdxGetAPIErrorCount
gdxSetAPIErrorCount
gdxErrorHandling
gdxInitMutexes
gdxFiniMutexes

Is this intentional? If yes, should API interfaces not expose functions like gdxLibraryLoaded or gdxLibraryUnload?

Activity

  1. self-assigned this
    on Apr 1, 2026
  2. 0x17 commented on Apr 1, 2026

    @0x17
    Member

    Good question, @kdheepak. The GDX dynamic library is built with the GDX sources and gdxcclib.cpp and this does not include some gdxcc.{h,c}-exclusive functions like gdxGetExceptionIndicator. Both are generated from a YAML description of the API by our custom apigenerator-tool.

    The purpose of the gdxcc is having a convenient and portable interface to load and use the GDX DLL/.so/.dylib from a C project. As such, it offers some extra functionality related to library loading on top of exposing the actual API surface of the GDX library. A core feature is robust loading of API entrypoints (and error handling) that tries different casing which I believe has also something to do with the Delphi origins of GAMS.

    So I would say it is intentional the dynamic library does not contain these functions in its ABI. The intended API is only the gdx* functions available as C__gdx* in the dynamic library. And the "single source of truth" reference of what is part of the API (and what is not) is what is in the src/gdxapi.yaml which should be consistent with the gdx*-methods in the TGXFileObj which can be used in C++ either directly or through the library via gdxcppwrap.hpp.

    Does this help? I see this is somewhat confusing and am considering, if this should be also explicitly mentioned in the docs somewhere.

  3. kdheepak commented on Apr 1, 2026

    @kdheepak
    Author

    (I'm assuming you meant to link to gdxapi.yaml and apigenerator)

    Thanks that helps! And thanks for the pointers and for the fast responses!

    The reason I was asking was that Pyomo appears to be assuming this function will always exist, e.g. see this line in the GAMS.py solver in Pyomo. I'm assuming this code will fail if the user has the bundled Python GAMS API in their path, because pyomo will import that first, and then will call the unload function (which would work on the gdxcc package but not on gams.core.gdx package).

    Specifically, this kind of issue arises when a programming language can generate bindings from the C code or header files directly. For example, when using SWIG in Python can generate a new DLL that exposes python friendly functions by generating C code. So if the SWIG interface binds with gdxLibraryUnload, that will be exposed in python as well, like how it is done here (generated code here). I imagine this will also happen in Rust's bindgen, Zig, Java, Nim etc when not using dynamic loading.

    I'll report this particular issue to Pyomo (and link to this issue), I'm not sure if others are using the same pattern so maybe the issue is not widespread, just something to be aware of.

    I think documenting this would be nice. Looking through the source code more, all these functions that are missing in the dynamic library seem to be associated with just loading the library (there's functions for loading, checking whether it is loaded, setting error messages during loading, mutexes for locking the variable holding a reference etc). I think even if I wanted to make a Rust interface for example, I'd be able to use dlopen and not bother with any of these functions. Let me know if I'm mistaken about any of this.

  4. 0x17 commented on Apr 1, 2026

    @0x17
    Member

    (I'm assuming you meant to link to gdxapi.yaml and apigenerator)

    Oooops. Yes, I mixed up our internal copies and the mirrored GitHub and put in the wrong links. Thank you for pointing that out!

    Specifically, this kind of issue arises when a programming language can generate bindings from the C code or header files directly.

    Good point. Maybe the generated/gdxcwrap.h could be a more suitable source for generating bindings as its interface isn't including the "noise" from the dynamic library loading/unloading related code and logic (afaics). But I am now curious, how the extra function endpoints in gdxcc.h will mess with binding generators of popular languages like Rust and if we should adapt our docs to point people interested in generating bindings for their language of choice to a suitable header file or if we can modify the template for gdxcc.h, so automatic binding generators ignore the superfluous functions.

    I think even if I wanted to make a Rust interface for example, I'd be able to use dlopen and not bother with any of these functions. Let me know if I'm mistaken about any of this.

    Yes, that's my understanding. I haven't tried, though.

    I will leave the issue open to remind myself to look into ways to make GDX more approachable for other wrapper/binding generators. If we don't maintain bindings ourselves, we at least should remove unnecessary hurdles. 👍

  5. kdheepak commented on Apr 1, 2026

    @kdheepak
    Author

    Maybe the generated/gdxcwrap.h could be a more suitable source

    That seems like a good idea to me, with the caveat that I have a limited understanding of the goals of the generated code.

    But I am now curious, how the extra function endpoints in gdxcc.h will mess with binding generators of popular languages

    My memory of this is a little hazy at the moment but I remember it being dependent on the generator. For example, SWIG parsers a header file using their own built in parser, and you can provide it rules for what the header should should contain. You can be explicit in this "interface" file, or use SWIG specific rules. Julia on the other hand uses Clang.jl to generate bindings, so it can handle more nuances of a header file correctly. I think the "export" was important to get right, for it to show up in the generated code and in the shared library (it has to be an exported function to show up in the shared library, but ignored or a noop in binding generators).

    In past work I've been involved in we generated just one header file from the cmake build process, and that header file has:

    1. A ..._EXPORT macro
    2. An extern C block for the entire file
    3. opaque pointers that are typedef'd
    4. Every C function namespaced
    5. Every C function documented using doxygen conventions

    That makes it somewhere between trivial to relatively easy to port to Python / Julia / Rust etc.

    This header file can also easily be consumed by libraries like cffi for Python. cffi for example can read the headers directly as a string:

    from cffi import FFI
    ffibuilder = FFI()
    
    # cdef() expects a single string declaring the C types, functions and
    # globals needed to use the shared object. It must be in valid C syntax.
    ffibuilder.cdef("""
        float pi_approx(int n);
    """)

    In Julia we used Clang.jl to generate the Julia code from the header files to build idiomatic interfaces on top of that.

    And we only need to ship the single header file and the shared library for this to work. We also shipped static libraries but that was in case someone wanted to build a static version.

    My past experience was focused primarily on dynamic linking using dlopen. I'm sure there are many ways to do this and may depend on the initialization of the library + the goals of the bindings. Just sharing all this for your reference, and happy to talk about it more too :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions