【问题标题】:Challenges in writing wrappers for C++ functions so that they can be used from C code为 C++ 函数编写包装器以使它们可以从 C 代码中使用的挑战
【发布时间】:2010-10-26 18:42:03
【问题描述】:

我现在正在为 C++ 函数编写包装器,以便可以在 C 代码中使用它们。

我们的想法是使用 g++ 编译 cpp 文件,使用 gcc 编译 c 文件,然后将它们链接在一起(!),但仅将那些需要的函数公开给 C 程序,通过使它们在头文件中可用'test.h'(或者可能是 test.hpp?),像这样:

(注意我如何不公开函数'vector Tokenize(const string& str,const string& delimiters)')

test.h:

/* Header can be read by both C+ and C compilers, just the way we want! */
#ifndef TEST_H
#define TEST_H

#ifdef __cplusplus
extern "C" {
#endif

#if defined(__STDC__) || defined(__cplusplus)
 extern int TokenizeC(const char* text, const char* delim, char ***output);   /* ANSI C prototypes */
 extern void reclaim2D(char ***store, unsigned int itemCount);
#endif

#ifdef __cplusplus
}
#endif

#endif /* TEST_H */

test.cpp:

#include <string>
#include <iostream>
#include <vector>

#include <assert.h>

#include "test.h"

using namespace std;

vector<string> Tokenize(const string& str,const string& delimiters)
{
 vector<string> tokens;

 string::size_type delimPos = 0, tokenPos = 0, pos = 0;

 if(str.length() < 1)  return tokens;

 while(1)
 {
   delimPos = str.find_first_of(delimiters, pos);
   tokenPos = str.find_first_not_of(delimiters, pos);

   if(string::npos != delimPos)
   {
     if(string::npos != tokenPos)
     {
       if(tokenPos < delimPos) tokens.push_back(str.substr(pos,delimPos-pos));
       else tokens.push_back("");
     }
     else tokens.push_back("");

     pos = delimPos + 1;
   }
   else
   {
     if(string::npos != tokenPos) tokens.push_back(str.substr(pos));
     else tokens.push_back("");
     break;
   }
 }

 return tokens;
}

int TokenizeC(const char* text, const char* delim, char ***output)
{
    if((*output) != NULL) return -1; /* I will allocate my own storage, and no one tells me how much. Free using reclaim2D */

    vector<string> s = Tokenize(text, delim);

    // There will always be a trailing element, that will be blank as we keep a trailing delimiter (correcting this issue would not be worth the time, so this is a quick workaround)
    assert(s.back().length() == 0); // This will be nop'ed in release build
    s.pop_back();

    (*output) = (char **)malloc(s.size() * sizeof(char *));

    for(vector <string>::size_type x = 0; x < s.size(); x++)
    {
        (*output)[x] = strdup(s[x].c_str());

        if(NULL == (*output)[x])
        {
            // Woops! Undo all
            // TODO : HOW to test this scenario?

            for(--x; x >= 0; --x)
            {
                free((*output)[x]);
                (*output)[x] = NULL;
            }

            return -2; 
        }
    }

    return x; /* Return the number of tokens if sucessful */
}

void reclaim2D(char ***store, unsigned int itemCount)
{
    for (int x = 0; itemCount < itemCount; ++x)
    {
        free((*store)[x]);
        (*store)[x] = NULL;
    }

    free((*store));
    (*store) = NULL;
}

poc.c:

#include <stdio.h>
#include "test.h"

int main()
{
    const char *text = "-2--4--6-7-8-9-10-11-", *delim = "-";

    char **output = NULL;

    int c = TokenizeC(text, delim, &output);

    printf("[*]%d\n", c);

    for (int x = 0; x < c; ++x)
    {
        printf("[*]%s\n", output[x]);
    }

    reclaim2D(&output, c);

    return 0;
}

你发现有什么不对吗?

对于初学者,当我运行这个程序时,我得到“不满意的代码符号'__gxx_personality_v0'”

谢天谢地,这里有一些东西:What is __gxx_personality_v0 for?

一旦我使用选项“-fno-exceptions -fno-rtti”运行 g++,现在输出将失败,并显示“不满意的数据符号 '_ZNSs4_Rep20_S_empty_rep_storageE'”

当然,这两种环境(一种用于编译 - HP-UX B.11.23 ia64 和一种用于在其上运行二进制文件 - HP-UX B.11.31 ia64)具有不同的库版本(但架构相同),而这不应该是错误的原因。

我也想测试一下标有“// TODO : HOW to test this scenario?”的案例,不过可以等一下。

任何指针?

【问题讨论】:

    标签: c++ interface unix


    【解决方案1】:

    在链接时避免未定义符号的最简单方法是使用 g++(不是 gcc)链接。不过,您仍然可以使用 gcc 编译 .c 文件。

    也请一次使用系统。如果您在同一系统上运行所有 gcc 和 g++ 命令(无论是旧系统还是新系统),链接错误可能会消失。

    【讨论】:

    • 这就是我所做的: g++ -c -fPIC -Wall -Wuninitialized -fno-exceptions -fno-rtti -O -D__hpuxita -mlp64 -I$(INCLUDEPATH) $
    • +1,问题出在最后一行,你应该链接 g++ 而不是 gcc,默认情况下会包含所有必要的 c++ 内容。
    • Evan,是的,但是生成的二进制文件不会是名称混乱的 C++ 二进制文件等吗?我希望生成的二进制文件是 C 的。我只想在使用 STL 非常有益的特定情况下使用 C++,但生成的二进制文件需要与 C 兼容。
    • 消息'无法找到库'libstdc++.so'现在停止了所有的乐趣-也许如果我可以自己找到库,可执行文件现在终于可以运行了吗?
    • 有趣的是,即使我设置了 SHLIB_PATH、LD_LIBRARY_PATH_64、LD_LIBRARY_PATH 甚至是 PATH 指向 .so 文件,我仍然会收到错误消息!
    【解决方案2】:

    要从 C 调用 C++ 函数,您不能使用错误的名称。删除对 __cplusplus 执行 extern "C" 的条件测试。即使您的函数将由 C++ 编译器编译,使用 extern "C" 也会导致它避免名称混淆。

    这是一个例子:

    C 文件。

    /* a.c */
    #include "test.h"
    
    void call_cpp(void)
    {
      cpp_func();
    }
    
    int main(void)
    {
      call_cpp();
      return 0;
    }
    

    头文件。

    /* test.h */
    #ifndef TEST_H
    #define TEST_H
    extern "C" void cpp_func(void);
    #endif
    

    CPP 文件。

    // test.cpp
    #include <iostream>
    #include "test.h"
    
    extern "C" void cpp_func(void)
    {
      std::cout << "cpp_func" << std::endl;
    }
    

    编译器命令行。

    g++ a.c test.cpp
    

    【讨论】:

    • 我应该使用 gcc 而不是 g++ 来链接 - 不像 Evan 上面所说的?
    • 删除条件测试以获得无名称修饰似乎与头文件中的原型声明混淆,因为 gcc 对我咆哮“警告:函数 'TokenizeC' 的隐式声明”
    • 条件仅适用于 C++,它应该可以工作......我相信这不是问题。
    • 为什么我需要在两个地方都有 extern - 源和标题?标题中还不够吗?
    • 声明和定义必须匹配。这就是你得到的原因:“警告:函数'TokenizeC'的隐式声明”。
    【解决方案3】:

    你有没有考虑修改 Swig 来做到这一点的原因?我似乎记得 Swig 有一个开发分支可以做到这一点......

    【讨论】:

    • 当我问这个问题时,我认为使用 Swig 将 C++ 代码与 C 接口是一种太多的解决方法。不过,如果您有一些链接,我会对查看路径感兴趣。
    • 很难找到...我想大多数人最终只是使用 C++ 作为胶水代码,但是我设法找到了以下链接,我认为这是我记得的同一个链接过去,虽然我怀疑兴趣不大:swig.svn.sourceforge.net/viewvc/swig/branches/gsoc2008-maciekd/…
    • 是的,我在生产代码中使用 C/C++,并且我更喜欢重写代码以在这种情况下具有最少的依赖关系。为链接 +1。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-13
    • 2015-10-21
    • 1970-01-01
    • 1970-01-01
    • 2016-06-06
    • 2014-01-21
    相关资源
    最近更新 更多