如何在 C 中为静态库设计接口

Posted

技术标签:

【中文标题】如何在 C 中为静态库设计接口【英文标题】:How to design the interface for a static library in C 【发布时间】:2012-10-26 08:16:53 【问题描述】:

虽然我不是一个经验丰富的 C 程序员,但我必须在工作中使用这种语言来构建一个在 SunOS 和 Win32 上都可以编译的静态库。这个库只有几个源文件及其对应的头文件(比如:a.cb.ca.hb.h)。项目在两个平台都编译,生成库mylib.a

我的问题是如何将mylib.a 的实现功能暴露给其他项目,因为当我同时添加a.hb.h 时,我得到了一些宏重新定义错误。

编辑:我发现宏重新定义错误是因为编译命令中的宏定义(与我的头文件无关)并且它已解决:) 但我仍然想就组织我的源文件和头文件提出建议。

我的疑问是我是否修改a.hb.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.hb.h,请注意 liball.h 标头保护与 b.h 相同,因此 b.h 不会完全定义!

【讨论】:

我已经使用了警卫,@mux。他们在构建库本身的项目中工作得很好,但显然他们并没有防止这些错误。 我添加了一些关于我的源是如何组织的更多信息,也许它可以帮助找到问题的原因。 我的项目中不存在 liball 保护的问题(这是一个快速编辑拼写错误,现在已更正)。我将a.hb.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.hinternal.h 的建议,但是我应该在这些文件中声明所有原型还是应该将它们保存到a.hb.h 中并将它们导入到external.h 中?这可能很棘手,我有超过 10 年的编程经验,但 C 的这些微妙之处有时让我看起来像个傻瓜。 每个函数、类型和宏都应该有一个声明。重复会导致错误,所以不要这样做。您应该隐藏库用户不需要知道的任何内容的定义,因此不要将它们放在外部mylib.h 中,但您可以将它们放在您选择的任何位置:源是您的。 (如果你暴露了内部细节,那么一些白痴用户会依赖它们,然后你将永远无法在不破坏他的应用程序的情况下更改它们。)

以上是关于如何在 C 中为静态库设计接口的主要内容,如果未能解决你的问题,请参考以下文章

如何在 binding.gyp node-gyp 中为 node.js 扩展添加对静态库的依赖

在 iOS 中为静态库添加前缀时过滤不需要的符号

在 Xamarin.iOS 中为 2 个依赖静态库创建绑定

windows系统中的c/c++设计——cl与link的进阶(生成静态库并调用)

linux c: 静态库和动态库的生成和使用

在linux内核中使用静态库