Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle differently sized type in C library from C++

Tags:

c++

c

I have an external C library that I link to from C++. The library defines a struct that uses bool and includes a typedef for it for C. The problem is that this typedef uses an int, so (on my platform) instead of this bool having a size of 1 byte, it is 4 bytes big.

When I then #include the header and create the struct in C++, bool (being the C++ standard type) has a size of 1 byte and thus the struct has a completely different memory layout, which causes all kinds of stack corruptions when I pass it to the C library in order to modify it there.

Is there a way to fix this situation without modifying the external library? I guess compiling the external library with a C++ compiler instead of a C compiler would work, correct? Is this my only chance of fixing this?

lib.h:

#ifndef __cplusplus
typedef int bool;
#endif

typedef struct {
  int numValues;
  bool values[20];
} bArray;

int arraySize();

lib.c:

#include "lib.h"

int arraySize() {
    return sizeof(bArray);
}

main.cpp:

#include <iostream>

extern "C" {
#include "lib.h"
}

int main() {
    std::cout << "size in c++: " << sizeof(bArray) << std::endl
              << "size in c  : " << arraySize() << std::endl;
    return 0;
}

output:

size in c++: 24
size in c  : 84
like image 328
nioncode Avatar asked Aug 24 '18 20:08

nioncode


2 Answers

You could write your own header file and use that instead of the broken one that is provided by the library:

fixed_lib.h

typedef struct {
  int numValues;
  int values[20];
} bArray;

int arraySize();

Of course, this means that you would need to maintain it in case the library is updated. However, if the library is actively maintained, then perhaps a better approach would be to send a pull request to resolve the issue upstream.

Furthermore, if there are other headers in the C library which depend on lib.h, then lib.h should really have a header guard, that must be used in your replacement as well, and you must make take care to include the replacement first.

If there is no header guard, then you would have to duplicate all such headers that you depend on, if those headers also depend on lib.h.

I guess compiling the external library with a C++ compiler instead of a C compiler would work, correct?

It could only potentially work if the library happens to have been written in a subset of C that is also valid C++. Furthermore, this C++ compiled version would not be binary-compatible with the C version of the library, so you then cannot have other dependencies which themselves depend on this (originally) C library.


If modifying the C library is OK, another approach would be to fix the library header itself to use the actual boolean data type:

lib.h

#ifndef __cplusplus
#include <stdbool.h>
#endif

typedef struct {
  int numValues;
  bool values[20];
} bArray;

int arraySize();

Of course, this would require you to re-compile the library, and it would no longer be binary compatible with the original version that used int. Furthermore, this would make the library require C99 standard, and could no longer work in C89.

like image 95
eerorika Avatar answered Nov 01 '22 07:11

eerorika


There is no general way to pass data directly from C++ to/from C code in this instance, since the bool types in the library differ from those in C++. This means it is extremely dangerous to allow the C code direct access to data structures used by C++ code or vice versa (e.g. a change made in one language may not have the expected effect in the other).

The general solution is to introduce a header file that can be used from both C and C++ code, but doesn't allow access from C++ code to C code or vice versa.

This means that header must only contain declarations that are acceptable in both C and C++. In other words, since bool works differently in your third party library than in C++, there should be nothing referring to bool in that header.

For example, a header named interface.h

#ifndef __cplusplus
    extern "C" {
#endif

typedef struct
{
    int numValues;
    char values[20];
} interface_bArray;

int interface_arraySize();

int interface_somefunc(interface_bArray *v);

#ifndef __cplusplus
    }
#endif

In compiling as C++, the above wrap all of the declarations in an extern "C" block. If compiling as C, it doesn't. Assuming the C and C++ compilers interoperate, this will allow the types and functions to be used in both languages. Note that it is important to ensure this header does NOT #include "lib.h" so C++ code never sees that header. An include guard on this header may be appropriate (but unrelated to providing an interface between C++ and C code).

I'm using the prefix interface_ in the declarations to ensure names are distinct from those in lib.h. You can pick whatever prefix you like, as long as they don't introduce name clashes with others in your program or in lib.h.

Then a C file (say, interface.c) which will ONLY ever be compiled as C.

#include "interface.h"
#include "lib.h"

int interface_arraySize()
{
    return arraySize();
}

int interface_somefunc(interface_bArray *v)
{
     int i, retval;
     bArray temp;

     /*  copy data from a form understood in C++ code to form understood by C library */

     temp.numValues = v->numValues;
     for (i = 0; i < 20; ++i) temp.values[i] = !(!v->values[i]);  /* maps all non-zero to 1 */

     retval = some_function_from_lib(&temp);

     /*  copy data library back to a form understood in C++ code */

     v->numValues = temp.numValues;

     for (i = 0; i < 20; ++i) v->values[i] = !(!temp.values[i]);  /* maps all non-zero to 1 */

     return retval;
}

Within this C file, the only bool type seen is that understood by the library.

This means there is no data passed directly from C++ code to the library. Instead a copy is made, given to the library, and then results are copied back.

C++ code which needs (indirect) use of the library obtains that by #include "interface.h" and NEVER includes lib.h.

To build your program, compile the library as usual (presumably using a C compiler), compile interface.c using only a C compiler, and compile other code as usual (e.g. using a C++ or C compiler as appropriate). Then link as required.

An advantage of this approach is that, if the library is ever changed (e.g. an updated) version, then the only file in your program that needs to be updated is the interface.c and (only if the library introduces new functionality that you wish to exploit in C++ code) the interface.h.

There are two down sides.

Firstly, the costs (performance, memory,etc) of COPYING across the boundary between C++ and C are the necessary price you pay for an interface that works consistently between the two languages, when there is an incompatibility (in this case, different notion of a bool type between C and C++ code). If you are unwilling to accept such costs, there is no general solution - any solutions will potentially vary between C and C++ compilers, between compiler versions, etc.

Second, if the library changes, you will need to remember to check if the interface files need updating. One part of this cost is having two data structures for the same thing - one only visible in C code, and one on the C++ that can also be passed to C code and copied from there. You might get lucky, and find that the interface.c does not compile if the library changes. Or you may not. Again, I view the cost of ongoing maintenance of the interface as being a necessary cost of using the library across programming languages. This does naturally mean that functions provided by the library must be sufficiently valuable to justify that overhead, rather than rolling a C++-specific version of it.

like image 2
Peter Avatar answered Nov 01 '22 06:11

Peter