【问题标题】:Call C API with vector of strings from C++使用 C++ 中的字符串向量调用 C API
【发布时间】:2021-10-25 08:19:57
【问题描述】:

在我的 C++ 应用程序中,我使用了一个公开 C API 的外部库。一些 C 函数将字符串数组作为输入,并为此使用 char**

void c_api_function(char** symbols, int count);

(注意:我认为指向 const 的指针会更合适,但似乎 const 的正确性对库作者来说并不重要。)

字符串必须使用特定的编码。

目前,为了调用 API,我首先将字符串转换为正确的编码并将结果存储在 vector<string> 中。然后我创建一个vector<char*> 可以传递给C API:

std::string encode(std::string const& symbol);

void call_api(std::vector<std::string> const& symbols)
{
    std::vector<std::string> encoded_symbols;
    for (auto const& s : symbols)
    {
        encoded_symbols.push_back(encode(s));
    }
    std::vector<char*> encoded_symbol_ptrs;
    for (auto const& s : encoded_symbols)
    {
        encoded_symbols_ptrs.push_back(s.data());
    }

    c_api_function(encoded_symbols_ptrs.data(), (int)encoded_symbols_ptrs.size());
}

我不喜欢这种方法,因为我需要两个向量。第一个向量确保字符串保持活动状态,第二个向量可以传递给 API。有没有只使用单个容器,但仍然使用自动内存管理的方法?如有必要,我可以随意更改encode函数的签名,例如使用std::unique_ptr作为返回值。

【问题讨论】:

  • 您不能将 C++ std::vector 作为 char** 参数传递给 C API。
  • 我认为这是可以做到的。 (编码为unique_ptr 而不是string 将无济于事 - 您仍然需要一个单独的指针容器。)
  • 通常当我在 C 程序中看到 void c_api_function(char**); 时,我希望该函数通过引用返回一个 C 字符串。
  • c_api_function如何知道数组有多少元素?
  • 您正在将 'encoded_symbols' 传递给 'c_api_function' - 它不会编译错误的类型,您想改为传递 'encoded_symbol_ptrs'。还有一件事——如果你知道数组的最终大小,在循环中调用 push_back() 是一种不好的做法,因为它以不必要的重定位结束

标签: c++ c-api


【解决方案1】:
//
// This is how to do this with the smallest number of allocations as possible (best performance)
//
// I guess: if string contains only ascii encode has no job to do and then input == output,
// then allocation isn't necessary and input string can be used to pass to c_api_function()
// I would recommend to change the encode() to do not generate output string if encode has nothing to do
// in that case if encode() returns true (encoding was made: input != output) and output will be store in 'out'
// if encode() returns false then input == output and 'out' isn't used
//
bool encode(const std::string& symbol_in, std::string& out);

void call_api(const std::vector<std::string>& symbol_in) {
    const size_t c = symbol_in.size(); // size usually have cost in operation: end - begin :)
    if (c > 0x7FFFFFFF) { // good idea if you must convert from size_t to int further
      throw std::overflow_error("..we have a problem here..");
    }

    auto it_in = symbol_in.cbegin(); // const iterator for input
    auto it_end = symbol_in.cend(); // const iterator for input end
    
    std::vector<std::string> encoded_symbols(c); // allocate array of string, but some std::string items may not be used if encode will return false
    std::vector<const char*> encoded_symbols_raw(c); // array of C raw pointers

    auto it_out = encoded_symbols.begin(); // iterator for std::string objects output
    auto raw_out = encoded_symbols_raw.begin(); // iterator for raw output

    for (; it_in != it_end; ++it_in, ++it_out, ++raw_out) {
      if (encode(*it_in, *it_out)) { // if *it_out contains encoding result:
        *raw_out = it_out->c_str(); // set std::string buffer as raw pointer
      }
      else {
        *raw_out = it_in->c_str(); // no encoding needed - just pass input string buffer
      }
    }

    c_api_function((char**)encoded_symbols_raw.data(), (int)c);
}

【讨论】:

    【解决方案2】:

    由于encoded_symbols 仅存在于您的call_api() 函数中,我希望有一个包含所有编码符号的对象和一个作用于它们的成员函数。作为一个可执行的例子:

    #include <iostream>
    #include <memory>
    #include <string>
    #include <vector>
    
    void c_api_function(char** symbols, int count)
    {
        for(int x = 0; x < count; ++x)
        {
            std::cout << symbols[x] << '\n';
        }
    }
    
    std::string encode(std::string const& symbol)
    {
        return symbol;
    }
    
    class EncodedSymbols
    {
        friend void call_api(EncodedSymbols& symbols);
        friend void call_api(std::vector<std::string> const& symbols);
    
    public:
        EncodedSymbols(const EncodedSymbols& other) = delete;
        EncodedSymbols(EncodedSymbols&& other) = default;
    
        ~EncodedSymbols()
        {
            for(int x = 0; x < number_of_encoded_symbols; ++x)
            {
                delete[] encoded_symbol_array[x];
            }
        }
    
        static EncodedSymbols create_from(const std::vector<std::string>& symbols)
        {
            EncodedSymbols obj;
    
            obj.encoded_symbol_array = std::make_unique<char*[]>(symbols.size());
            obj.number_of_encoded_symbols = symbols.size();
    
            for(int x = 0; x < symbols.size(); ++x)
            {
                const std::string encoded = encode(symbols[x]);
    
                obj.encoded_symbol_array[x] = new char[encoded.length() + 1];
                std::copy(encoded.begin(), encoded.end(), obj.encoded_symbol_array[x]);
                obj.encoded_symbol_array[x][encoded.length()] = '\0';
            }
    
            return obj;
        }
    
        void call_api()
        {
            c_api_function(encoded_symbol_array.get(), number_of_encoded_symbols);
        }
    
    private:
        EncodedSymbols() = default;
    
        std::unique_ptr<char*[]> encoded_symbol_array;
        int number_of_encoded_symbols;
    };
    
    void call_api(EncodedSymbols& symbols)
    {
        c_api_function(symbols.encoded_symbol_array.get(), 
            symbols.number_of_encoded_symbols);
    }
    
    void call_api(std::vector<std::string> const& symbols)
    {
        auto encoded = EncodedSymbols::create_from(symbols);
    
        c_api_function(encoded.encoded_symbol_array.get(), 
            encoded.number_of_encoded_symbols);
    }
    
    int main()
    {
        std::vector<std::string> symbols{"one", "two"};
        auto encoded_symbols = EncodedSymbols::create_from(symbols);
    
        encoded_symbols.call_api();
        call_api(encoded_symbols);
        call_api(symbols);
    
        return 0;
    }
    

    如果您的 C 库中有其他函数作用于编码符号,那么(在我看来)将它们放在一个类中更有意义。所有手动内存管理都可以隐藏在一个漂亮的界面后面。

    如果您愿意,您还可以拥有一个作用于EncodedSymbols 实例的裸函数。我也包含了那个变体。

    作为第三种选择,您可以保留当前的函数原型并为 RAII 使用 EncodedSymbols 类型。我在我的例子中也证明了这一点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多