
typedef void *(*gs_fetch_addr_fn)(void);

typedef struct {
    PyObject_HEAD

    PyObject         *gs_name;
    CTypeDescrObject *gs_type;
    char             *gs_data;
    gs_fetch_addr_fn  gs_fetch_addr;

} GlobSupportObject;

static void glob_support_dealloc(GlobSupportObject *gs)
{
    Py_DECREF(gs->gs_name);
    Py_DECREF(gs->gs_type);
    PyObject_Del(gs);
}

static PyTypeObject GlobSupport_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "FFIGlobSupport",
    sizeof(GlobSupportObject),
    0,
    (destructor)glob_support_dealloc,           /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
};

#define GlobSupport_Check(ob)  (Py_TYPE(ob) == &GlobSupport_Type)

static PyObject *make_global_var(PyObject *name, CTypeDescrObject *type,
                                 char *addr, gs_fetch_addr_fn fetch_addr)
{
    GlobSupportObject *gs = PyObject_New(GlobSupportObject, &GlobSupport_Type);
    if (gs == NULL)
        return NULL;

    Py_INCREF(name);
    Py_INCREF(type);
    gs->gs_name = name;
    gs->gs_type = type;
    gs->gs_data = addr;
    gs->gs_fetch_addr = fetch_addr;
    return (PyObject *)gs;
}

static void *fetch_global_var_addr(GlobSupportObject *gs)
{
    void *data;
    if (gs->gs_data != NULL) {
        data = gs->gs_data;
    }
    else {
        Py_BEGIN_ALLOW_THREADS
        restore_errno();
        data = gs->gs_fetch_addr();
        save_errno();
        Py_END_ALLOW_THREADS
    }
    if (data == NULL) {
        PyErr_Format(FFIError, "global variable '%s' is at address NULL",
                     PyText_AS_UTF8(gs->gs_name));
        return NULL;
    }
    return data;
}

static PyObject *read_global_var(GlobSupportObject *gs)
{
    void *data = fetch_global_var_addr(gs);
    if (data == NULL)
        return NULL;
    return convert_to_object(data, gs->gs_type);
}

static int write_global_var(GlobSupportObject *gs, PyObject *obj)
{
    void *data = fetch_global_var_addr(gs);
    if (data == NULL)
        return -1;
    return convert_from_object(data, gs->gs_type, obj);
}

static PyObject *cg_addressof_global_var(GlobSupportObject *gs)
{
    void *data;
    PyObject *x, *ptrtype = new_pointer_type(gs->gs_type);
    if (ptrtype == NULL)
        return NULL;

    data = fetch_global_var_addr(gs);
    if (data != NULL)
        x = new_simple_cdata(data, (CTypeDescrObject *)ptrtype);
    else
        x = NULL;
    Py_DECREF(ptrtype);
    return x;
}
