Hello again!
In this video, we are going to look at interfacing to C. There are many interfaces which are written in C,
which we may want to use in our programs.
For example, if we want the operating system to perform some service for us, we have to call a function
that is written in C. And database APIs are, similarly, usually written in C.
There are lots of useful third-party libraries and frameworks which are written in C. And also, many interpreted
languages have so-called "foreign function interfaces", which means that they can call functions which
are written in C. For example, if you are working in Python, and you have something which is very time
critical, and Python cannot do it fast enough, then you could replace that bit of the Python script with
a call to a foreign function.
So how do we do this?
C is almost a complete subset of C++.
So this means that C programs are usually legal C++ programs.
There are some things which get in the way.
For example, you could have C code which uses words which are reserved in C++ but not in C.
So you could have a variable called class, for example, because C does not have classes.
In C99, there were some incompatible features introduced. Although one of them, which is designated
initializers, is now in C++. Either 20 or 23, I cannot remember which.
So, usually, the simplest way to do this is to take the entire source code for the program, whether it is
written in C or C++, and compile the whole lot with a C++ compiler.
Unfortunately, that is not always possible.
There may be some incompatibilities in the C code, that you cannot easily remove.
The process of building the C deo may be complex, and it is not really feasible to try and insert a
C++ compiler into that process.
And, of course, you may not have access to the source code in the first place.
So, quite often you have to interface with C at the binary level.
So you would get a header which will declare all the functions in the C interface. And then you will
have some compiled binary code for the functions.
And normally that would be some kind of library. And the library could be a static library or a dynamic library.
Unfortunately, there is no standard on binary interfaces in C or C++.
So this means that the compilers you use have to agree on various things.
So they have to use the same object file format.
They have to agree on the conventions for calling functions, and they have to agree on the word size.
If you have one compiler using 32 bits, and one compiler using 64 bits, then when you combine them
together, that's not going to work.
Usually you would use the same compiler on the same computer, because all the main compilers can be used
as either C or C++ compilers. And they are compatible with themselves.
If you are using any features from the C++ library, which includes iostream, then the final step which builds
the program executable needs to be done with the C++ compiler, and that will incorporate the C++
binary code into the program.
We need to be aware of something called "name mangling". In
C, if you have a function in the source code, then it will have the same name in the binary object file.
In C++,
that does not happen.
The compiler will do something called "mangling" of the name. Or "decoration", as Microsoft prefer to call
it.
The reason for that is that C++ has function overloading, which C does not.
If you look at the actual symbols in an object file.Aand you can do that with the "nm" command on Linux
or other Unixes. Windows used to provide something called "objectdump.exe".
I do not know if they still do.
If you do that, you will see that the function which takes int will have this name.
So, "double underscore Z three func one i". That is with gcc.
It could be different with different compilers.
If we have another function with the same name but different arguments that it will have a different
symbol name in the binary file.
And that is how you can have two functions which have the same name, without any conflicts.
So, name mangling does not occur in C. If we want to have binary objects in C++, which are compatible with C objects,
we use something called "extern C". So we put extern double quote C, close quote before the function signature.
And this is a compiler directive.
It tells the compiler, not to mangle the function name.
So in this case, we do not get any underscore things.
We just get the name "func" in the object file.
And if we have multiple symbols, we can put them inside braces.
And we can also say that everything in a header file needs to be "extern
C". So this says that the compiler should not mangle anything in "cstuff"
"dot h".
If we are writing a C++ function that is going to be called from C, we can only use things which C understands.
So we can only use built-in types.
We can also use arrays of built-in types, and pointers to built-in types.
We can use structs which have members of built-in types only.
The function needs to go in the global namespace, because C does not understand namespaces.
We have a header which represents the interface.
This is going to be seen by both the C compiler and the C++ compiler.
It may contain some things which the C compiler will not understand. Such as the "extern C" directive.
So there is a trick for hiding things from the C compiler. When we are compiling with the C++ compiler.
This symbol will be defined in the pre-processor. Double underscore C++ and then we can do a conditional
compilation to exclude this from the C compiler.
So if this symbol is defined, so this is a branch which occurs during the pre-processing, before the
compiler starts actually compiling the code.
So if this symbol is defined, then this branch will be included and compiled in the code.
Otherwise, this branch will be compiled and then we have an endif to finish it off.
So this is all carried out before any actual compilation takes place.
Resource management, which C does not do at all. For memory management,
C uses malloc() and free().
It does not have new and delete. Resource
management is done by using raw pointers.
So the programmer is responsible for everything.
There are no destructors, no RAII, no smart pointers.
Typically, if you have a pointer to a resource, there will be some function which you pass this to, when
you no longer need it. And that will release the resource and perform any tidying up that is required.
Alternatively, if you have allocated memory, there may be a function, or you may be expected to release
it yourself, when it is no longer needed.
So it is important to be clear about what you need to do to release resources.
And if you are lucky, there will be documentation, and it will be up to date, and accurate.
Or, more realistically, you have to read the source code to find out what's going on.
So let's look at an example.
So here is a C main function.
This is going to call some C++ code.
There is the C++ code it is going to call.
Nothing very C++-specific, but iy is a nice, simple example.
Then we have the header which represents this interface.
So we have our preprocessor trick.
If we are compiling this as C++, then we will have the "extern C" in front of it.
And the C++ compiler will not mangle the name. So the symbol will be "add" in the binary.
If this is not defined, then we are compiling in C, and the symbol will be called "add" anyway.
So I am going to compile my C++ code to an object file. "minus c" means just produce the object file,
and not the full program binary.
So we now have add.o, which is a C++ binary.
And there I compiled my C++ object file with a C source file.
So we now have a program.
Let's see if it works.
And yes, it does!
Something that is useful to know about is converting C++ library containers into arrays. So you can pass
them to C functions. With string and vector
have a data() member function, which we have mentioned before, and this will return the container's
internal memory buffer as a pointer. And then you can use that as an array.
So this works for strings and vectors.
So here is an example with vectors.
This time we have a C++ program which is going to create a vector. And then it is going to use a C function
for printing out the elements of this vector.
There is our C function.
It will take the array as a pointer, but it also needs the number of elements. Because C arrays are the same
as built-in arrays in C++.
They do not know how many elements they have.
So we have to tell them.
And then in our array header. This time we are not using "extern C".
So this is something that was provided by a C programmer.
They do not know anything about "extern C".
So in the main function, we have to do this for our selves.
We have to say that this include file is going to be "extern C".
So that will tell the C++ compiler not to mangle the function names.
So I do the same thing again.
I compile my c code to an object file.
And then I compile it with the C++ source code.
And there we are. For associative containers,
the trick is to convert them to a vector, or vectors. And then you can use the same trick again,
of calling the data() member in. With a set, for example, we can call the copy() algorithm and use
that to populate a vector. And then we get the array from the vector. With a map,
we need two separate vectors, one for the keys and one for the values. And this C interface function
should have two separate arrays, one for keys and one for values.
And I am going to leave you that as an assignment.
Okay.
So that is it for this video.
I will see you next time.
But until then, keep coding!