【问题标题】:C++ library API. Using converter classes instead of plain C apiC++ 库 API。使用转换器类而不是普通的 C api
【发布时间】:2012-08-02 08:29:56
【问题描述】:

这个问题是关于 C++ C++ 互操作性的。

众所周知,标准库类/函数的实现可能因供应商而异。此外,即使在同一个库供应商中,使用不同的编译器键、配置(调试/发布)等时,实现也可能不同。

由于这个原因,许多库开发人员转而使用旧的纯 C 样式 API。 这会导致界面丑陋且容易出错。

例如,为了从某个函数中获取字符串,使用了诸如 Win GetCurrentDirectory 函数之类的接口:

DWORD WINAPI GetCurrentDirectory(
  __in   DWORD nBufferLength,
  __out  LPTSTR lpBuffer
);

三个参数+两边的一些样板代码(检查缓冲区大小是否足够等)只是为了得到简单的字符串。

我正在考虑使用一些辅助适配器/代理类,它会自动完成所有转换,并且可以简单地重复使用。

类似:

#include <string>
#include <algorithm>
#include <iostream>
#include <ostream>

class StringConverter
{
    char *str; // TODO: use smart pointer with right deleter
public:
    StringConverter(const std::string &user_string) // Will be defined only at user side
    {
        str=new char[user_string.length()+1];
        (*(std::copy(user_string.begin(),user_string.end(),str)))=0;
    }
    operator std::string() // Will be defined only at library side
    {
        return std::string(str);
    }
    ~StringConverter()
    {
        delete [] str;
    }
};

StringConverter foo()
{
    return std::string("asd");
}


int main(int argc,char *argv[])
{
    std::cout << std::string(foo()) << std::endl;
    return 0;
}

http://ideone.com/EfcKv

注意,我打算只在用户端定义从用户字符串到 StringConverter 的转换,并且只在库内部定义从 StringConverter 到库字符串的转换。 此外,应该使用右删除器(从右堆)。

您如何看待这种方法?

是否存在一些重大缺陷?

有没有更好的选择?

【问题讨论】:

  • 你的代码违反了三法则,没有明显的目的。请举个例子。
  • “违反三原则”——是的,我知道,这只是为了演示。
  • “没有明显的目的”,重读我的问题,看看在哪里定义了转换。提示:不同的 std::string 实现
  • “违反三法则”顺便说一句,一些智能指针(我已经说过它是必需的) - 将解决这个问题。
  • 所以,我将您的计划总结如下:您主要担心标准库的兼容性以及编译器之间语言的非基本功能的兼容性。您不必担心以互操作方式表示 StringConverter 所需的语言的简单部分。因此,您将库的转换部分编译到其中,并且用户代码的部分通过导出的标头提供,并且用户的编译器将其编译到他们的应用程序中。交换接口是函数/方法调用方案和StringConverter的表示

标签: c++ api stl interop portability


【解决方案1】:

这种技术适用于标准数据类型不兼容的某些情况,但在其他情况下也不会更好:想到名称修饰差异和类内存布局差异(vptrs 和标签)。

这就是首选 C API 的原因。

但是您可以通过将 C API 隐藏在库调用者永远不需要看到它的地方来提高可用性。然后添加一个薄的、惯用的 C++ 覆盖层,提供可见的库接口。在某些情况下,可以在调用方和库端使用薄覆盖代码,每个都在自己的环境中编译:标志、链接约定等。仅交换 C 数据类型。这些数据类型越简单,您获得的兼容性就越强。

该层还负责根据每个对象在 API 的同一侧进行内存分配和释放,就像您的代码一样。但它可以更灵活。例如,可以安排在调用者中分配的对象在库中释放,反之亦然。

// library.h for both caller and library

// "shadow" C API should not be used by caller
extern "C" {
void *make_message(char *text);
char *get_text_of_message(void* msg);
void send_message(void *msg); // destroys after send.
}

// Thin C++ overlay
class Message {
  void *msg;
public:

  Message(const std::string &text) {
    msg = make_message(text.c_str());
  }

  void send() {
    if (msg) send_message(msg);
    else error("already sent");
    msg = 0;
  }

  std:string getTextString() {
    return std:string(get_text_of_message(void* msg));
  }
}

【讨论】:

  • “我想到了名称修改差异和类内存布局差异(vptrs 和标签)。” - 我使用非常小的 C++ 子集进行转换类。可以更进一步,将转换类的所有内部数据放入聚合的 POD 结构成员中。如果需要传递删除器,可以将其作为函数指针存储在 POD 结构中。不需要 vtable。因此,ABI 要求非常低。我可以承受一些 ABI 要求,以牺牲一些兼容性。无论如何,这比进行数十种不同的构建要好。
  • “但是你可以通过将 C API 隐藏在库调用者永远不需要看到它的地方来提高可用性。” - 好吧,我想消除在双方都做样板代码的需要。手动将每个函数手动包装三次是乏味且容易出错的:第一次在中间 - 普通 C,第二次在用户端,第三次在库端。相反,我正在考虑仅将某些类型包装到 *Converter 类中 - 因此您可以使用许多不同的此类类型作为函数参数,而无需将每个此类函数包装三次。
  • 如果它是一些自动工具来生成每个函数的包装器 - 这将改变情况,但目前我不知道这样的工具。我想到的最接近的东西是 SWIG,但它不做 C++ C++ 的东西。
  • @John 很明显,您使用了一个简单的 C++ 类。我有一段时间没有使用 C++,但是当我这样做时,您甚至无法使用 Borland C++、g++ 和 MSC 编译您的简单类,并且可以链接其中的任何两个。名字被不同地打乱了。我的方法会起作用,因为 C 调用约定是相同的。注意:将 C++ 添加到 C++ 到 SWIG 不会是一件大事。
  • 即使我需要为主要编译器进行构建,而无需在一个编译器系列中进行多次构建——这将是一个巨大的胜利。正如我所说,我可以承受这种权衡。 “我的方法会起作用,因为 C 调用约定是相同的” 什么是“你的”方法?普通的 C-api,手动包装每个函数?我知道这件事——这是不可接受的。 “NB:将 C++ 添加到 C++ 到 SWIG 不会是一件大事” - 好吧,我现在需要它,你知道一些现成的工具吗?
猜你喜欢
  • 2017-02-18
  • 1970-01-01
  • 2022-10-01
  • 2019-11-26
  • 2021-06-10
  • 1970-01-01
  • 2013-10-24
相关资源
最近更新 更多