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!

0

2 Answers 2

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
Sign up to request clarification or add additional context in comments.

Comments

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!

3 Comments

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, 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);"
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.