本文介绍了我如何在 C 中进行模块化设计?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想让我的项目更加模块化,以便在删除一个模块时没有模块间的依赖关系.

I want to make my project more modular so that there are no inter-modular dependencies if one of the module is removed.

例如如果我将进程中的代码分成多个目录,例如 X、Y 和 Z,以便 X 中的数据结构不应该被 Y 和 Z 中的数据结构直接访问,反之亦然,那么我需要一些 X 之间的内部通信机制,Y 和 Z.

For e.g. If I divide the code in my process into multiple directories, say, X, Y and Z so that data structures in X should not be accessed directly by data structures in Y and Z and vice versa then I need some internal communication mechanism between X, Y and Z.

因为我是用 C 语言编码的,谁能建议一个示例项目或相同的设计注意事项?

Since I am coding in C, can anyone suggest a sample project or design considerations for the same?

推荐答案

这通常归结为 API 设计.我认为有帮助的几件事:

This often boils down to API design. A couple things that I find helpful:

  • 请记住,您的 API 位于头文件中.实现位于 C 文件中.
  • 避免使用全局变量 - 必要时使用访问器方法
  • 尽可能避免结构共享
  • 使用回调函数减少耦合

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
", info->count++, id, name);
    return 1;   /* keep iterating */
}

void test(void)
{
    struct cbinfo info = { 0 };
    printf("All foobars in the system:
");

    libfoo_iterate_foobars(test_callback, &info);

    printf("Total: %d
", info.count);
}

这里我展示了一个跟踪一些 foobar 的 libfoo.我们有一个消费者,在这个例子中,他只想显示所有 foobar 的列表.这种设计的好处:

Here I show a libfoo who tracks some foobars. And we have a consumer who in this example, simply wants to show a list of all foobars. Benefits to this design:

  • 没有全局可见的变量:除了 libfoo 之外没有任何人可以直接修改 foobars 列表.他们只能以公共 API 允许的方式使用 libfoo.

  • No globally-visible variables: No one other than libfoo can directly modify the list of foobars. They can only use libfoo in the manner allowed by the public API.

通过使用回调迭代器方法,我让消费者​​不必知道 foobar 是如何被跟踪的.今天它是一个 struct foobar 列表,也许明天它是一个 SQLite 数据库.通过隐藏结构定义,消费者只需要知道一个 foobar 有一个 name 和一个 id.

By using a callback-iterator approach, I've kept the consumer from having to know anything about how a foobar is even tracked. Today it's a list of struct foobar, maybe tomorrow it's an SQLite database. By hiding the structure definition, the consumer only needs to know that a foobar has a name and an id.

真正模块化,您将需要两件大事:

To be truly modular, you're going to need two big things:

  1. 一组定义模块如何产生和使用数据的 API
  2. 一种在运行时实际加载模块的方法

具体情况将因您的目标平台、模块化需求、预算等而异.

The specifics of this will greatly vary depending on your target platform, modular needs, budget, etc.

对于#1,您通常会有一个模块注册系统,其中一些组件会跟踪已加载模块的列表,以及有关生产和消费内容的元信息.

For #1 you would generally have a module registration system, where some component tracks a list of loaded modules, as well as meta information about what the produce and consume.

如果模块可以调用其他模块提供的代码,您将需要一种方法使其可见.这也将影响 2 的实现.以 Linux 内核为例 - 它支持 可加载的内核模块 用于将新功能、驱动程序等添加到内核中,而无需将其全部编译为一个大型二进制文件.模块可以使用 EXPORT_SYMBOL 来指示特定符号(即函数)可供其他模块调用.内核跟踪加载了哪些模块、它们导出了哪些函数以及在哪些地址处.

If modules can call into code provided by other modules, you'll need a way to make this visible. This will also play into the implementation of 2. Take the Linux kernel for example - it supports loadable kernel modules for the purpose of adding new features, drivers etc. into the kernel, without having to compile it all into one large binary. Modules can use EXPORT_SYMBOL to indicate that particular symbol (i.e. function) is available for other modules to call. The kernel tracks which modules are loaded, what functions they export, and at which addresses.

对于#2,您可以利用操作系统的共享库支持.在 Linux 和其他 Unices 上,这些动态库是 ELF(.so 文件),它们由动态加载器加载到进程的地址空间中.在 Windows 上,这些是 DLL.通常,当您的流程开始时,会自动为您处理此加载.但是,应用程序可以利用动态加载器来显式加载它选择的其他模块.在 POSIX 上,您将调用 dlopen(),在 Windows 上你会使用 LoadLibrary().任何一个函数都会向您返回某种句柄,这将允许您对模块进行进一步的查询或请求.

For #2 you might leverage your OS's shared library support. On Linux and other Unices, these dynamic libraries are ELF (.so files), which are loaded by the dynamic loader into a process's address space. On Windows, these are DLLs. Normally, this loading is handled automatically for you when your process starts. However, an application can leverage the dynamic loader to explicitly load additional modules of its choosing. On POSIX you would call dlopen(), and on Windows you would use LoadLibrary(). Either function will return some sort of handle to you, which will allow you to make further inquiries or requests about the module.

然后,您的模块可能需要(根据您的设计)导出 codingfreak_init 函数,该函数在首次加载模块时由您的应用程序调用.然后,此函数将对您的框架进行额外调用,或返回数据以指示它需要和提供哪些功能.

Your module then might be required (by your design) to export a codingfreak_init function, which is called by your application when a module is first loaded. This function would then make additional calls into your framework, or return data to indicate what facilities it requires and provides.

这些都是非常一般的信息,应该能让你的车轮转动.

This is all very general information which should get your wheels turning.

这篇关于我如何在 C 中进行模块化设计?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-24 05:50