5

I have to use a third-party C++ library (that I can not change) and call its API from C code.

For most of the library APIs I use a wrapper as explained in this post: How to call C++ function from C?

But there is one API that takes a variable number of arguments.
Here is its definition (from the header file provided with the library):

void consoleDebug(char* format, ...);

I do not see how I can write the wrapper function for this API.
I tried this but it does not work:

extern "C" { 
void wrapper_consoleDebug(char * format, ...)
{
    va_list argptr;
    va_start(argptr,format);
    consoleDebug(format, argptr);
    va_end(argptr);
}
}

Any idea is welcome! Thanks!

rici
  • 234,347
  • 28
  • 237
  • 341

2 Answers2

1

problem of call c++ functions from c that it used different decoration.

all next for cl.exe (msvc) + link.exe toolset, but think other compiler/linkers have analog features

for example when you compile in c++ function

void consoleDebug(char* format, ...)

in obj (or static lib) file will be ?consoleDebug@@YAXPEADZZ symbol. but when you use the same function from c unit - in object file will _consoleDebug (for x86) or consoleDebug (other platforms)

if we declare in c file

void consoleDebug(char* format, ...)

and do call - in obj will be stored that external symbol consoleDebug (or _consoleDebug) used. when linker will be build code - it will search - where is [_]consoleDebug actually defined (in all obj and lib passed to him) and founf nothing - no such symbol. as result we got error unresolved external symbol [_]consoleDebug

solution here in undocumented linker option /alternatename :

/alternatename:sym1=sym2

with this we say to linker (link.exe) if he need sym1 symbol and can not found it - try use sym2 instead. with this we can create next solution:

1 - we need know exactly symbol name in c++ - we can get it with __FUNCDNAME__ macro:

for example:

#define _GET_NAMES_

#ifdef _GET_NAMES_

void consoleDebug(char* format, ...)
{   
#pragma message(__FUNCSIG__ ";\r\n")
#pragma message("__pragma(comment(linker, \"/alternatename:" __FUNCTION__ "=" __FUNCDNAME__ "\"))")
}

#endif // _GET_NAMES_

this is temporary, fake code, need only for print __FUNCDNAME__

then in c file we declare

void __cdecl consoleDebug(char *,...);

#ifdef _X86_
__pragma(comment(linker, "/alternatename:_consoleDebug=?consoleDebug@@YAXPADZZ"))
#else
__pragma(comment(linker, "/alternatename:consoleDebug=?consoleDebug@@YAXPEADZZ"))
#endif

and can free use consoleDebug

in case we have multiple functions in c++ with same short name, say

void consoleDebug(char* format, ...);
void consoleDebug(wchar_t* format, ...);

this is also easy work, need only bit different name this 2 api in c code:

void __cdecl consoleDebugA(char *,...);

#ifdef _X86_
__pragma(comment(linker, "/alternatename:_consoleDebugA=?consoleDebug@@YAXPADZZ"))
#else
__pragma(comment(linker, "/alternatename:consoleDebugA=?consoleDebug@@YAXPEADZZ"))
#endif


void __cdecl consoleDebugW(wchar_t *,...);

#ifdef _X86_
__pragma(comment(linker, "/alternatename:_consoleDebugW=?consoleDebug@@YAXPA_WZZ"))
#else
__pragma(comment(linker, "/alternatename:consoleDebugW=?consoleDebug@@YAXPEA_WZZ"))
#endif

after this we can simply call like

consoleDebugA("str %u\n", 1);
consoleDebugW(L"str %u\n", 2);

from c code.

no any shim/wrapper code need with this. in case you use not cl/link but other tool-chain and can not found analog of /alternatename name option - possible use asm file for create single jmp shim. say for x64

extern ?consoleDebug@@YAXPEADZZ:proc
extern ?consoleDebug@@YAXPEA_WZZ:proc

_TEXT segment 'CODE'

consoleDebugA proc 
    jmp ?consoleDebug@@YAXPEADZZ
consoleDebugA endp

consoleDebugW proc
    jmp ?consoleDebug@@YAXPEA_WZZ
consoleDebugW endp
_TEXT ENDS
END
RbMm
  • 31,280
  • 3
  • 35
  • 56
1

Thanks for your help!

I tried the suggestion from Sam Varshavchik and it works (at least in my case)! More precisely, here is what I did:

// wrapper.cpp
extern "C" { void (*wrapper_consoleDebug)(char * format, ...) = consoleDebug;}

// wrapper.h
extern void (*wrapper_consoleDebug)(char * format, ...);

// C file
#include "wrapper.h"

// in my code
wrapper_consoleDebug("my logger is %s","great");

I did not try the other suggestions yet, but I guess they would work too.

Thanks again!

  • This fails to account for C++ exceptions. The behavior is undefined in case a C++ exception crosses the language boundary between C++ and C. You'd have to write a C++ wrapper dealing with C++ exceptions. Doing that would make it impossible to pass the variable argument list on to the callee. – IInspectable Sep 20 '18 at 06:51
  • @IInspectable, agreed. In this case I got confirmation from the library developpers team that this function does not raise any exception, so I will keep this solution. Otherwise I guess I would use the suggestion from user4581301, ie "Do the formatting yourself and then call consoleDebug("%s", myFormattedBuffer);" – Julien Brongniart Sep 20 '18 at 10:39
  • I wouldn't take the developers' word for this. It may be true today, but what about tomorrow? And it may not even be true today. If the function isn't allowed to have C++ exceptions escape, make them write a formal contract. In C++ you do this by using the `noexcept` specifier. As written currently, no one is checking the contract communicated to you. – IInspectable Sep 20 '18 at 10:58