【问题标题】:How to call C++ functions with variable number of arguments from C code如何从 C 代码中调用具有可变数量参数的 C++ 函数
【发布时间】:2018-09-18 21:48:43
【问题描述】:

我必须使用第三方 C++ 库(我无法更改)并从 C 代码调用它的 API。

对于大多数库 API,我使用包装器,如本文所述: How to call C++ function from C?

但是有一个 API 可以接受可变数量的参数。
这是它的定义(来自库提供的头文件):

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

我不知道如何为此 API 编写包装函数。
我试过了,但它不起作用:

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

欢迎任何想法!谢谢!

【问题讨论】:

    标签: c++ c variadic


    【解决方案1】:

    c 调用 c++ 函数的问题是它使用了不同的装饰。

    cl.exe (msvc) + link.exe 工具集的所有下一个,但认为其他编译器/链接器具有模拟功能

    例如当你在c++函数中编译时

    void consoleDebug(char* format, ...)

    obj(或静态 lib)文件中将是 ?consoleDebug@@YAXPEADZZ 符号。 但是当您使用 c 单元中的相同功能时 - 在目标文件中将 _consoleDebug(对于 x86)或 consoleDebug(其他平台)

    如果我们在 c 文件中声明

    void consoleDebug(char* format, ...)

    并调用 - obj 将存储使用的外部符号 consoleDebug(或 _consoleDebug)。当链接器将是构建代码时 - 它会搜索 - [_]consoleDebug 实际定义在哪里(在所有传递给他的 objlib 中)并且什么都没有 - 没有这样的符号。结果我们得到了错误unresolved external symbol [_]consoleDebug

    在未记录的链接器选项中的解决方案 /alternatename

    /alternatename:sym1=sym2
    

    如果他需要 sym1 符号但找不到它,我们会告诉链接器 (link.exe) - 尝试改用 sym2。有了这个,我们可以创建下一个解决方案:

    1 - 我们需要准确地知道 c++ 中的符号名称 - 我们可以通过 __FUNCDNAME__ 宏获得它:

    例如:

    #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_
    

    这是临时的假代码,只需要打印__FUNCDNAME__

    然后在 c 文件中我们声明

    void __cdecl consoleDebug(char *,...);
    
    #ifdef _X86_
    __pragma(comment(linker, "/alternatename:_consoleDebug=?consoleDebug@@YAXPADZZ"))
    #else
    __pragma(comment(linker, "/alternatename:consoleDebug=?consoleDebug@@YAXPEADZZ"))
    #endif
    

    并且可以免费使用consoleDebug

    如果我们在 c++ 中有多个具有相同短名称的函数,比如说

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

    这也很简单,只需要在c代码中为这2个api命名一点不同:

    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
    

    在此之后我们可以简单地调用类似

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

    来自 c 代码。

    这个不需要任何垫片/包装器代码。如果您使用的不是 cl/link 而是其他工具链并且找不到 /alternatename 名称选项的模拟 - 可能使用 asm 文件来创建单个 jmp shim .说 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
    

    【讨论】:

      【解决方案2】:

      感谢您的帮助!

      我尝试了 Sam Varshavchik 的建议并且它有效(至少在我的情况下)!更准确地说,这就是我所做的:

      // 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");
      

      我还没有尝试其他建议,但我想它们也可以。

      再次感谢!

      【讨论】:

      • 这无法解释 C++ 异常。如果 C++ 异常跨越 C++ 和 C 之间的语言边界,则行为未定义。您必须编写处理 C++ 异常的 C++ 包装器。这样做会导致无法将变量参数列表传递给被调用者。
      • @IInspectable,同意。在这种情况下,我得到了库开发团队的确认,这个函数不会引发任何异常,所以我会保留这个解决方案。否则我想我会使用 user4581301 的建议,即“自己进行格式化,然后调用 consoleDebug("%s", myFormattedBuffer);"
      • 我不会相信开发人员的话。今天可能是真的,但明天呢?今天甚至可能不是真的。如果该函数不允许 C++ 异常转义,请让它们编写正式合同。在 C++ 中,您可以使用 noexcept 说明符来执行此操作。正如目前所写,没有人检查与您沟通的合同。
      猜你喜欢
      • 2011-03-20
      • 2011-12-24
      • 1970-01-01
      • 2011-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-10
      相关资源
      最近更新 更多