C++ C-API函数的风格

C++ C-API函数的风格,c++,c,api,C++,C,Api,我正在开发一个支持多种编程环境(如VB6和FoxPro)的库。我必须坚持C约定,因为它是最低的公分母。现在我有一个关于风格的问题 假设函数处理输入并返回一个字符串。在此过程中,可能会发生错误。目前提议的样式如下: int func(input params... char* buffer, unsigned int* buffer_size); 这种风格的好处是原型中包含了所有内容,包括错误代码。并且可以避免内存分配。问题是函数非常冗长。由于缓冲区大小可以是任意的,因此需要更多的代码来实现 另

我正在开发一个支持多种编程环境(如VB6和FoxPro)的库。我必须坚持C约定,因为它是最低的公分母。现在我有一个关于风格的问题

假设函数处理输入并返回一个字符串。在此过程中,可能会发生错误。目前提议的样式如下:

int func(input params... char* buffer, unsigned int* buffer_size);
这种风格的好处是原型中包含了所有内容,包括错误代码。并且可以避免内存分配。问题是函数非常冗长。由于缓冲区大小可以是任意的,因此需要更多的代码来实现

另一个选项是返回char*,并返回NULL以指示错误:

char* func(input params...);
此样式要求调用方删除缓冲区。内存分配是必需的,因此服务器程序可能会面临内存碎片问题

第二个选项的变体是使用线程局部变量来保存返回的指针char*,这样用户就不需要删除缓冲区


你喜欢哪种款式?原因呢?

第二种变体更干净

COM是第二种方法的实现。服务器调用SetErrorInfo来设置出错的详细信息,并返回错误代码。调用方检查代码,并可以调用GetErrorInfo以获取详细信息。调用方负责释放IErrorInfo,但是在第一个变量中传递每个调用的参数也不是很好


服务器可以在启动时预先分配足够的内存,这样它肯定有足够的内存返回错误详细信息。

如果必须在显示的两种样式中进行选择,我每次都会选择第一种样式。第二种样式为库的用户提供了一些其他需要考虑的东西,即内存分配,而有人肯定会忘记释放内存。

我更喜欢第一种定义,其中传递了缓冲区及其大小。也有例外情况,但通常您不希望在调用函数后进行清理。然而,如果我分配内存并将其传递到函数中,那么我知道我必须自己清理


处理不同大小的缓冲区应该没什么大不了的。

第二种风格的另一个问题是,分配内存的上下文可能不同。例如:

// your library in C
char * foo() {
   return malloc( 100 );
}

// my client code C++
char * p =  foo();      // call your code
delete p;               // natural for me to do, but ... aaargh!

这只是问题的一小部分。可以说双方都应该使用malloc&free,但是如果他们使用不同的编译器实现呢?最好所有分配和解除分配都发生在同一个地方。这是否是库r客户端代码取决于您。

当其他程序员使用它时,第一版不太容易出错

如果程序员必须自己分配内存,他们更有可能记住释放内存。如果一个库为它们分配内存,这是另一种抽象,可能/将导致复杂性。

没有什么值得思考的

  • 分配和解除分配应该在同一范围内发生(理想情况下)。最好由调用者传入预先分配的缓冲区。呼叫方可以在稍后安全地释放此内容。这就提出了一个问题——缓冲区应该有多大?我看到在Win32中广泛使用的一种方法是将NULL作为输入缓冲区传递,而
    size
    参数将告诉您需要多少

  • 您监督了多少可能的错误情况?返回
    char*
    可能会限制错误报告的范围

  • 您希望满足哪些先决条件和后决条件?您的原型是否反映了这一点

  • 您是否在呼叫者或被呼叫者中执行错误检查

我真的不能告诉你一个比另一个好,因为我没有大局。但我相信这些东西可以让你和其他帖子一样开始思考。

说到这个话题,我有点“坏货”。我曾经为嵌入式电信设计和维护过相当大的API。一种你不能想当然的环境。甚至像全局变量或TLS这样的东西也不行。有时甚至堆缓冲区也会出现,实际上是ROM内存寻址的

因此,如果您正在寻找“最低公分母”,您可能还需要考虑目标环境中有哪些可用的语言构造(编译器可能接受标准C中的任何内容,但如果不支持某些内容,链接器将拒绝)

话虽如此,我还是会选择备选方案1。部分原因是(正如其他人所指出的),您永远不应该直接为用户分配内存(下面将进一步解释间接方法)。即使用户保证使用纯C和纯C,他们仍然可以使用自己定制的内存管理API来跟踪泄漏、诊断日志记录等。支持这样的策略通常是值得赞赏的

错误通信是处理API时最重要的事情之一。由于用户可能有不同的方法来处理代码中的错误,因此在整个API中,您应该尽可能保持这种通信的一致性。用户应该能够以一致的方式和最少的代码将错误处理包装到API中。我通常建议使用清晰的枚举代码或defines/typedef。我个人更喜欢typedef:ed枚举:

typedef enum {

  RESULT_ONE,
  RESULT_TWO

} RESULT;
…因为它提供类型/分配安全性

有一个get last error函数也很好(但是需要中央存储),我个人使用它只是为了提供关于已识别错误的额外信息

备选方案1的详细程度可以通过制作以下简单化合物来限制:

struct Buffer
{
  unsigned long size;
  char* data;
};
那么您的api可能会看起来更好:

ERROR_CODE func( params... , Buffer* outBuffer );
这一战略也为更复杂的机制打开了大门。例如,假设您必须能够为用户分配内存(例如,如果您需要调整缓冲区大小),t
struct Buffer
{
  unsigned long size;
  char* data;
  void* (*allocator_callback)( unsigned long size );
  void  (*free_callback)( void* p );
};
int func(char* buffer, size_t buffer_size, input params...);

// Style 1 functions
int fooBuff(char* buffer, unsigned int buffer_size, input params... );

// Style 2 functions
char* fooBuffAlloc(input params...);
bool fooBuffFree(char* foo);