如何在 C 中为静态库设计接口
Posted
技术标签:
【中文标题】如何在 C 中为静态库设计接口【英文标题】:How to design the interface for a static library in C 【发布时间】:2012-10-26 08:16:53 【问题描述】:虽然我不是一个经验丰富的 C 程序员,但我必须在工作中使用这种语言来构建一个在 SunOS 和 Win32 上都可以编译的静态库。这个库只有几个源文件及其对应的头文件(比如:a.c
、b.c
、a.h
和 b.h
)。项目在两个平台都编译,生成库mylib.a
。
我的问题是如何将mylib.a
的实现功能暴露给其他项目,因为当我同时添加a.h
和b.h
时,我得到了一些宏重新定义错误。
编辑:我发现宏重新定义错误是因为编译命令中的宏定义(与我的头文件无关)并且它已解决:) 但我仍然想就组织我的源文件和头文件提出建议。
我的疑问是我是否修改a.h
和b.h
以供外部使用,或者我是否应该创建一个专门用于声明该库接口的头文件(比如说mylib.h
)。我不想使原始头文件过于复杂,但我不想让不同的头文件保持同步......我想听听更有经验的 C 程序员关于他们的模式、每种方法的优点以及是否有其他方法选项。
提前谢谢你。
编辑:似乎我没有提供足够的信息;下面的架构试图展示我的文件是如何组织的:每个代码文件只包含它的头文件,每个头文件都有一个保护条件,每个头文件都包含一个通用头文件,这个通用头文件包含所有头文件。
// a.c ----
#include "a.h"
// b.c ----
#include "b.h"
// a.h ----
#ifndef GUARD_A_H
#define GUARD_A_H
# include "liball.h"
void function_prototypes_implemented_in_a();
#endif // GUARD_A_H
// b.h ----
#ifndef GUARD_B_H
#define GUARD_B_H
# include "liball.h"
void function_prototypes_implemented_in_b();
#endif // GUARD_B_H
// liball.h ----
#ifndef GUARD_LIBALL_H
#define GUARD_LIBALL_H
# include <time.h> // standard headers
# include <stdioa.h>
# include "a.h" // all headers in this project
# include "b.h"
#endif // GUARD_LIBALL_H
【问题讨论】:
你能告诉我们错误和相关代码吗?你也使用标题卫士吗? “当我同时添加 a.h 和 b.h 时出现一些宏重新定义错误”?绝对只保留 2 个单独的头文件,然后修复它们。 每个头文件都应该可以单独使用或与任何其他头文件一起使用,因此您需要为您的宏提供更好的名称,例如通过在前面添加一些指示“命名空间”的字母(C 宏和 C 标识符通常都没有命名空间,所以这是你能做的最好的)。 通常会有一个或多个公共头文件供其他人使用您的库。有时它们单独使用,有时它们包含私人文件。 是的,创建一个简单的 #include 其他两个标题的第三个标题是一个好主意,以简化库用户的使用。 这种模式很好 - 它整齐地组织文件,并最大限度地减少您必须拥有的#include
指令的数量 - 但它确实意味着每个 .c
文件都依赖于每个 .h
文件这意味着增量重建的成本更高。不过,对于大多数项目来说,这并不是一个大问题,并且对从干净构建构建的影响为零,因此,如果您愿意,请随意这样做。
【参考方案1】:
你的问题太模糊了,所以我的回答是一个通用的建议,你应该避免双重包含并在标题中使用include guards,如下所示:
a.h
#ifndef __HEADER_A_H__
#define __HEADER_A_H__
...
#endif
b.h
#ifndef __HEADER_B_H__
#define __HEADER_B_H__
...
#endif
编辑:
就像我说的,避免双重(或循环)包含 liball.h
也不需要包含 a.h
和 b.h
,请注意 liball.h
标头保护与 b.h
相同,因此 b.h
不会完全定义!
【讨论】:
我已经使用了警卫,@mux。他们在构建库本身的项目中工作得很好,但显然他们并没有防止这些错误。 我添加了一些关于我的源是如何组织的更多信息,也许它可以帮助找到问题的原因。 我的项目中不存在 liball 保护的问题(这是一个快速编辑拼写错误,现在已更正)。我将a.h
和b.h
包含在liball.h
中的原因是因为a
的某些功能在b
中使用,同样——实际上,有5 个源(C)文件。【参考方案2】:
您应该有一个公共标头mylib.h
,它只包含库用户需要知道的定义。
然后,您就有了一个私有标头 mylib-internal.h
,它会在您的项目中使用。你不应该试图让这两个“同步”,而是让mylib-internal.h
包含#include "mylib.h"
。
您还应该将所有接口函数命名为mylib_initialize()
等,并确保所有私有的内部函数要么声明为static
,要么命名为_mylib_internal_whatever()
,这样用户就不会遇到命名空间冲突其他库。
【讨论】:
很好,@ams,但是在a.c
中实现的函数不应该将它们的原型声明到a.h
中吗?这是一种不好的做法吗?
@GerardoLima 是的,这是在项目中的最佳实践:它最大限度地减少了每次修改头文件时必须重新构建的.c
文件的数量。但是mylib.h
是给用户的,和libmylib.a
是一一对应的,所以这一点更重要。头文件之间的重复是一个维护问题,所以如果你这样做,你可能做错了。
对不起,@ams,但我仍然不清楚。我喜欢你创建一个导入external.h
的internal.h
的建议,但是我应该在这些文件中声明所有原型还是应该将它们保存到a.h
和b.h
中并将它们导入到external.h
中?这可能很棘手,我有超过 10 年的编程经验,但 C 的这些微妙之处有时让我看起来像个傻瓜。
每个函数、类型和宏都应该有一个声明。重复会导致错误,所以不要这样做。您应该隐藏库用户不需要知道的任何内容的定义,因此不要将它们放在外部mylib.h
中,但您可以将它们放在您选择的任何位置:源是您的。 (如果你暴露了内部细节,那么一些白痴用户会依赖它们,然后你将永远无法在不破坏他的应用程序的情况下更改它们。)以上是关于如何在 C 中为静态库设计接口的主要内容,如果未能解决你的问题,请参考以下文章
如何在 binding.gyp node-gyp 中为 node.js 扩展添加对静态库的依赖