c++caws-sdk-cpp

Correct use of variable argument functions C API with C++ implementation


I want to use a C++ logging library in a mixed C/C++ application. The legacy application is full of printf style logging. The new library also supports that.

The C files are compiled with C compiler, so I am not able to include C++ logging headers into them.

I am struggling to create a C++ library with C-style headers and C++ source file:

//logger.h
extern "C" {
    extern void RS_LOG_INFO_PRIVATE(const char* fmt, ...);
    #define LOG_INFO(...)  RS_LOG_INFO_PRIVATE(__VA_ARGS__)
}

//logger.cpp
#include "logger.h"
#include <aws/core/utils/logging/LogMacros.h>
#include <utility>
template<class... Args>
void RS_LOG_INFO_PRIVATE(const char* fmt, Args&&... args) {    
    AWS_LOG_DEBUG(fmt, std::forward<Args>(args)...);
}

//main file
#include "logger.h"

int main() {
    LOG_INFO("test %s", "me");
}

I get undefined reference to 'RS_LOG_INFO_PRIVATE'.

nm -C also verifies this:

U RS_LOG_INFO_PRIVATE(char const*, ...) 

Is there a solution to solve this, or I should change my approach?


Solution

  • If you are recompiling your application, you can seriously consider changing from a C compiler to a C++ compiler. Doing so will cause various compilation issues, but resolving them is usually no more difficult than some major modification to how C compilation was done (e.g., new compiler vendor, or increasing compiler warning levels). In this way, you may be able to avoid using C style variable arguments, and replace them with variadic template calls instead.

    If switching to a C++ compiler is not viable, then you have to make your header files act as a C header file for the C compiler, and act as a C++ header file for the C++ compiler (or maintain two different header files for the different compilers).

    Assuming your C and C++ compiler have compatible C ABIs, then the usual mechanism for detecting which compiler is being used is the __cplusplus macro.

    #pragma once
    
    #ifdef __cplusplus
    // ... C++ stuff
    #endif
    

    One common trick is to make a C header file useable to a C++ header file by wrapping its contents with extern "C". It seems you attempted to do this, but without the __cplusplus check.

    #pragma once
    
    /* A C header file made to work for C++ */
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    /* rest C stuff ... */
    
    #ifdef __cplusplus
    }
    #endif
    

    A C compiler will not understand what extern "C" is, hence it is hidden by the __cplusplus check, which would be defined for a C++ compiler.

    The library implementation of the variable argument functions should not be different when implemented in C++ (#includeing the header file). The implementation would then call whatever C++ implementation code is required to actually complete what the call is supposed to do.