我想让我的项目更加模块化,以便在删除其中一个模块时没有模块间依赖关系。
例如如果我将我的进程中的代码分成多个目录,比如X,Y和Z,那么X中的数据结构不应该由Y和Z中的数据结构直接访问,反之亦然,那么我需要X之间的一些内部通信机制, Y和Z.
由于我使用C编码,是否有人可以建议样本项目或设计注意事项?
答案 0 :(得分:4)
这通常归结为API设计。我发现有帮助的几件事:
<强> libfoo.h 强>
int (*libfoo_callback)(void *arg, const char *name, int id);
/**
* Iterate over all known foobars in the system.
*/
int libfoo_iterate_foobars(libfoo_callback cb, void *arg);
<强> libfoo.c 强>
#include "libfoo.h"
/* Private to libfoo.c */
struct foobar {
struct foobar *next;
const char *name;
int id;
};
/* Don't make this globally visible */
static struct foobar *m_foobars;
int libfoo_iterate_foobars(libfoo_callback cb, void *arg)
{
struct foobar *f;
for (f = m_foobars; f != NULL; f = f->next) {
int rc = cb(f->name, f->id);
if (rc <= 0)
return rc; /* Stop iterating */
}
return 0;
}
<强> some_consumer.c 强>
#include <stdio.h>
#include "libfoo.h"
struct cbinfo {
int count;
};
static int test_callback(void *arg, const char* name, int id)
{
struct cbinfo *info = arg;
printf(" foobar %d: id=%d name=%s\n", info->count++, id, name);
return 1; /* keep iterating */
}
void test(void)
{
struct cbinfo info = { 0 };
printf("All foobars in the system:\n");
libfoo_iterate_foobars(test_callback, &info);
printf("Total: %d\n", info.count);
}
在这里,我展示了跟踪一些foobars的libfoo
。我们有一个消费者,在这个例子中,只是想显示所有foobars的列表。这种设计的好处:
没有全局可见的变量:libfoo
以外的任何人都无法直接修改foobar列表。它们只能以公共API允许的方式使用libfoo。
通过使用回调迭代器方法,我让消费者不必了解有关如何跟踪foobar的任何信息。今天它是struct foobar
的列表,也许明天它是一个SQLite数据库。通过隐藏结构定义,消费者只需要知道foobar有name
和id
。
要成为真正的模块化,你需要两件大事:
具体情况因您的目标平台,模块化需求,预算等而异。
对于#1,你通常会有一个模块注册系统,其中一些组件跟踪已加载模块的列表,以及有关产生和消费的元信息。
如果模块可以调用其他模块提供的代码,则需要一种方法使其可见。这也将用于实现2.以Linux内核为例 - 它支持loadable kernel modules,目的是将新功能,驱动程序等添加到内核中,而无需将其全部编译成一个大型二进制文件。模块可以使用EXPORT_SYMBOL
来指示特定符号(即函数)可供其他模块调用。内核跟踪加载哪些模块,导出哪些函数以及哪些地址。
对于#2,您可以利用操作系统的共享库支持。在Linux和其他Unices上,这些dynamic libraries是ELF(.so
文件),它们由动态加载程序加载到进程的地址空间中。在Windows上,这些是DLL。通常,当您的流程开始时,会自动处理此加载。但是,应用程序可以利用动态加载程序显式加载其选择的其他模块。在POSIX上,您可以拨打dlopen()
,在Windows上,您可以使用LoadLibrary()
。这两个函数都会向您返回某种句柄,这样您就可以对模块进行进一步的查询或请求。
然后可能需要您的模块(按照您的设计)导出codingfreak_init
函数,该函数在首次加载模块时由您的应用程序调用。然后,此函数将对您的框架进行额外调用,或返回数据以指示它需要和提供的工具。
这是非常一般的信息,应该让你的车轮转动。
答案 1 :(得分:0)
在您开始编码之前,我会设置一个“公共”API。然后,代码仅使用每个模块外部的API。不要作弊;仅使用公共API(尽管API可以根据需要发展)。它可以帮助尽可能多地处理面向对象语言中的对象之类的数据结构,以及像对象方法那样的公共API。尽可能避免在模块外直接使用内部数据结构字段;虽然如果它们是API的一部分,返回定义良好的数据结构是可以的。只是不要直接在它们来自的模块之外修改它们。如果您花费大量时间预先设计接口,则可以创建一个非常易于维护的项目。