Search code examples
c++cstatic-librariescalling-convention

Why is it I can use C++ code in a library and call from a C program. How does that work?


I created a static library on Windows using Visual Studio 2008 with these two files:

header file was a simple C API:

#ifdef __cplusplus
extern "C" {
#endif

   void init();
   void stop();

#ifdef __cplusplus
}
#endif

Implementation however was using C++ features:

#include "my_lib_api.h"

#include <iostream>

void init() {
   std::cout << "Initializing my_lib\n";
}
void stop() {
   std::cout << "stopping my_lib\n";
}

I then built with these commands:

mkdir Release
cl.exe /O2 /Oi /GL /D "WIN32" /D "NDEBUG" /D "_LIB" /FD /EHsc /MT /Gy /Fo"Release\\" /W3 /c /Zi /TP my_lib_api.cpp /nologo /errorReport:prompt
lib.exe /OUT:my_lib_api.lib /LTCG .\Release\my_lib_api.obj /NOLOGO

I then copied my_lib_api.lib and my_lib_api.h to a new folder where I created a new Visual Studio console application with a main.c source file:

#include "my_lib_api.h"

int main(int argc, char* argv[]) {
  init();

  stop();
  return 0;
}

I built using same /MT multi-threaded C runtime library to be consistent with library. It then built like this:

mkdir Release
cl.exe /O2 /Oi /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /FD /EHsc /MT /Gy /Fo"Release\\" /W3 /c /Zi /TC .\main.c /nologo /errorReport:prompt

successfully created Release/main.obj

link.exe /OUT:c_api_so.exe /INCREMENTAL:NO /MANIFEST:NO /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /LTCG /DYNAMICBASE /NXCOMPAT /MACHINE:X86 my_lib_api.lib kernel32.lib user32.lib shell32.lib .\Release\main.obj /NOLOGO /ERRORREPORT:PROMPT

Successfully created c_api_so.exe which runs just fine.

This means I can write a library in C++ and as long as I provide a C API then both C and C++ calling programs can use my library? Is this universally true? Should this always work? Why does it work? I assume that for each platform you would say only support one compiler/linker and provide eg on Windows a Visual Studio based library and on linux a GNU C based library? Are there any issues to be aware of?


Solution

  • Broadly speaking, a function is an interface. All the caller cares about is that it meets that interface. The implementation is totally irrelevant. In this case, by using extern "C" you told the C++ compiler to make a binary interface that the C compiler can use, which it did. The interface is a match (guaranteed by Standard), so you can quite safely call it from C. Just to re-state, the body is totally immaterial here and may be implemented in any language at all, ever. Many languages offer the power to define functions that can be called from C.

    To be slightly more specific, the C ABI (ABI is the name for the binary interface here) is not specified by Standard, but virtually all operating systems have a defined C ABI that everybody on that platform uses. This is the major reason that C is still the "lingua franca" of programming- virtually everybody can speak C on a binary level.