Search code examples
cforward-declarationopaque-pointers

Am I correct to assume one cannot forward-declare a library's opaque pointer type?


There are a lot of questions out there about forward declarations and opaque types, but most seem to be from the perspective of the library author, or people trying to use incomplete types without pointers or some such.

I'm using a library whose interface accepts/returns FOO * pointers. I'd like to confirm that I cannot (or should not) somehow forward-declare FOO or FOO * in my header file (which defines a struct with a FOO * member).

I do know that I could just #include <library.h> in both my header and my .c file, but since this is really just a learning project, I wanted to get clarification. (On the one hand, it seems like a forward-declaration might be possible, since my struct member is only a pointer and thus its size is known without knowing what FOO is—but on the other hand, I don't know if it's valid/smart to typedef something to FOO when the library is already doing that.)

Thanks in advance!


Solution

  • Assuming you never need to dereference the pointer, then you can use an opaque type pointer if you know the struct tag name for it:

    typedef struct FOO FOO;
    

    You can now create FOO * variables, and use them. And you probably could find the structure tag from the header file, but you should be aware that the library owners could change it at any time.

    It is still usually best to include the official header, but if most of your code is not accessing the actual library, just passing around a handle to something returned by the library, you can avoid the 'cost' of including the actual header. You should measure what that cost is before deciding on what is probably premature optimization. It might be argued that if you have to ask the question, you don't know enough to be sure of doing it right, and you're in danger of getting burnt.

    Note that you cannot create actual variables of the type; for that to work, the compiler needs to know how big the structure actually is, which means you need the details from the header.

    Strictly speaking, if you don't know the tag name, that won't work. And likewise, if the structure doesn't have a tag, you can't do it either. And if it isn't a structure type, you can't do it.

    Note that if you know the structure tag, you can also write:

    struct FOO *fp;
    

    If you have to get inventive, everything works for passing around pointers until you reach the point where you need to access the actual library functions. Then you need the actual library header (to make sure the information is right), and if your structure tag is wrong, all hell breaks loose. So, if you're going to play this game, make sure you get the structure tag correct.

    Note that C11 allows a typedef to be repeated as long as it is the same each time, whereas earlier versions of C did not allow that. This can be a considerable help.

    Working example

    This is close to a minimal example that shows how it might be done. It assumes C11 where the repeated typedef is legitimate. It won't work for C99 or C89/C90 because the typedef for FOO is repeated when projfunc.c is compiled. (There are various ways you can adapt it so that it will work in C99 or earlier, but they're messier, using #ifdef or similar around the project.h typedef — since the presumption is that you can't alter library.h; if you can, it is part of your project after all.)

    The project.h header is used primarily by the general code that belongs to the project that uses the library defining FOO — which is represented by projmain.c in this example. It can be used on its own, or with library.h, which is illustrated by projfunc.c which is the project code that actually interfaces to the library and makes calls to the library. The file library.c only uses library.h.

    You can play with alternative declarations of FOO in project.h to see what goes wrong where. For example, typedef struct BAR FOO; will fail; so will typedef struct FOO *FOO;.

    project.h

    #ifndef PROJECT_H_INCLUDED
    #define PROJECT_H_INCLUDED
    
    typedef struct FOO FOO;
    
    typedef struct Project
    {
        FOO    *foop;
        char   *name;
        int     max;
        double  ratio;
    } Project;
    
    extern int proj_function(Project *pj);
    
    #endif /* PROJECT_H_INCLUDED */
    

    library.h

    #ifndef LIBRARY_H_INCLUDED
    #define LIBRARY_H_INCLUDED
    
    typedef struct FOO
    {
        int x;
        int y;
    } FOO;
    
    extern FOO *foo_open(const char *file);
    extern int foo_close(FOO *foop);
    extern int foo_read(FOO *foop, int *x, int *y);
    extern int foo_write(FOO *foop, int x, int y);
    
    #endif /* LIBRARY_H_INCLUDED */
    

    projmain.c

    #include "project.h"
    
    int main(void)
    {
        Project pj = { 0, 0, 0, 0.0 };
    
        if (proj_function(&pj) != 0)
            return 1;
        return 0;
    }
    

    projfunc.c

    #include "project.h"
    #include "library.h"
    #include <stdio.h>
    
    int proj_function(Project *pj)
    {
        int x, y;
    
        pj->foop = foo_open("classic-mode");
        if (foo_write(pj->foop, 1, 2) < 0)
        {
            foo_close(pj->foop);
            return -1;
        }
        if (foo_read(pj->foop, &x, &y) < 0)
        {
            foo_close(pj->foop);
            return -1;
        }
        printf("x = %d, y = %d\n", x, y);
        return 0;
    }
    

    library.c

    #include "library.h"
    #include <assert.h>
    
    static FOO foo = { 0, 0 };
    
    FOO *foo_open(const char *file)
    {
        assert(file != 0);
        return &foo;
    }
    
    int foo_close(FOO *foop)
    {
        assert(foop == &foo);
        foo.x = foo.y = 0;
        return 0;
    }
    
    int foo_read(FOO *foop, int *x, int *y)
    {
        assert(foop == &foo);
        *x = foop->x + 1;
        *y = foo.y + 1;
        return 0;
    }
    
    int foo_write(FOO *foop, int x, int y)
    {
        assert(foop == &foo);
        foo.x = x + 1;
        foop->y = y + 2;
        return 0;
    }