我想设计一个C API,它在一个库中提供自己的几个版本。
我遇到了Foundation DB C API的描述,但由于FoundationDB源代码不再可用,我无法弄清楚他们是如何做到的。我知道的所有其他库在给定版本的库中提供一个API,并且必须链接到特定版本以获得所需的API。
我完全清楚支持旧的API版本是一个主要的麻烦,我会尝试第一次正确的API,但由于这个API分布在几乎没有维护可能性的地理分布式系统上,我仍然希望能够在不破坏其他软件的情况下仅更新我的库。
使用面向对象的语言,任务更容易/更简单(取决于语言)但是对于C?
答案 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, ... )
{
}
这允许您在必要时添加更多参数,但不允许您更改buffer
或length
的类型。你当然可以这样做
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_1
,function_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;
}