如何设计版本化的C API

时间:2015-09-08 06:07:37

标签: c api versioning

我想设计一个C API,它在一个库中提供自己的几个版本。

我遇到了Foundation DB C API的描述,但由于FoundationDB源代码不再可用,我无法弄清楚他们是如何做到的。我知道的所有其他库在给定版本的库中提供一个API,并且必须链接到特定版本以获得所需的API。

我完全清楚支持旧的API版本是一个主要的麻烦,我会尝试第一次正确的API,但由于这个API分布在几乎没有维护可能性的地理分布式系统上,我仍然希望能够在不破坏其他软件的情况下仅更新我的库。

使用面向对象的语言,任务更容易/更简单(取决于语言)但是对于C?

3 个答案:

答案 0 :(得分:5)

我不知道Foundation DB C API是如何工作或设计的,但一种方法是使用结构和函数指针在C中模拟继承。

您从基础结构开始,类似于

struct base_api
{
    int version;
};

然后你继续"继承" (或扩展)这个基础结构:

struct version_1_api
{
    struct base_api base;
    // Function pointers for version 1 of the API
};

struct version_2_api
{
    struct base_api base;
    // Function pointers for version 1 of the API
    // Function pointers for version 2 of the API
};

然后有一个带有版本号的导出函数,并返回指针struct base_api,然后应用程序可以转换为指向相应结构的指针:

struct base_api *api = library_get_api();
if (api->version >= 2)
{
    // We have at least version 2 of the API available
    struct version_2_api *api2 = (struct version_2_api *) api;
    // Use version 2 of the API
}
else if (api->version >= 1)
{
    // We have version 1 of the API available
    struct version_1_api *api1 = (struct version_1_api *) api;
    // Use version 1 of the API
}
else
{
    // Unsupported version
}

上例中的library_get_api函数只返回指向静态结构的指针。像是这样的东西。

struct base_api *library_get_api()
{
    static version_2_api api = {
        { 2 } // Version
        // Function pointers for version 1
        // Function pointers for version 2
    };

    return (struct base_api *) &api;
}

答案 1 :(得分:2)

在C中,您可以使所有函数都是可变参数,第一个参数表示版本号,例如

int foo( int version, char *buffer, int length, ... )
{
}

这允许您在必要时添加更多参数,但不允许您更改bufferlength的类型。你当然可以这样做

int foo( int version, ... )

但是,即使是第一个版本的函数也不是自我记录的。

另一种选择是将指针传递给结构,例如

struct FooParams
{
    int version;
    char *buffer;
    int length;
};

int foo( struct FooParams *params )
{
}

结构定义应包括size和/或version,以便您知道调用者正在使用哪种结构。

答案 2 :(得分:2)

函数指针。

对于库中的每个函数,都声明一个函数指针变量:

return_type ( function_name_impl* )(parameters);

您可以多次实现该功能,与您需要的版本一样多。所以你有function_name_VERSION_1function_name_VERSION_2

版本选择功能可以为每个函数指针变量指定正确的指针。

最后,使用宏function_name,这样您的代码就可以调用所需的函数,而无需每次都选择API版本而无需使用sintax作为函数指针。

这种策略有一个重要的优势。如果您已经实现了API的第一个版本并且已经在源代码表单中使用,那么您可以使用此策略将其转换为多版本API,除了调用{之外,您将无需使用API​​在源代码中进行任何更改。 {1}};

library.h:

setVersion

library.c:

#ifndef LIBRARY_H
#define LIBRARY_H

#include <errno.h>

#define VERSION_1 1
#define VERSION_2 2

/**
 * Example of library function
 */
#define compute(a,b) ((*compute_impl)((a),(b)))

extern int (*compute_impl)( int a, int b);


/**
 * Set version of library to be used.
 * Sets errno to 0 on success. To non-zero if requested version
 *is not available
 */

extern void setVersion( int version );

#endif // LIBRARY_H

main.c:

#include "library.h"

int (*compute_impl)( int a, int b);

int compute_VERSION_1( int a, int b)
{
  return a+b;
}

int compute_VERSION_2( int a, int b)
{
  return a+b+1;
}

/**
 * Set version of library to be used.
 * Sets errno to 0 on success. To non-zero if requested version
 *is not available
 */
void setVersion( int version )
{
  switch( version )
  {
    case VERSION_1 :
      compute_impl = &compute_VERSION_1;
      break;
    case VERSION_2 :
      compute_impl = &compute_VERSION_2;
      break;
    default :
      errno = 1;
      return;
   }
   errno = 0;
   return;
}