How to modular design in C?

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

For example, if I divide the code in my process into several directories, say, X, Y and Z, so that data structures in X should not directly access data structures in Y and Z and vice versa, then I need an internal communication mechanism between X, Y and Z.

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

+1
source share
2 answers

This often boils down to API design. A few things that I find useful:

  • Remember that your API is in your header files. The implementation is in the C files.
  • Avoid global variables - use access methods if necessary.
  • Avoid sharing structure
  • Use callback functions to reduce communication

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); } 

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

  • There are no variables visible around the world: no one but libfoo can directly modify the foobars list. They can use libfoo only according to the public API.

  • Using the iterator callback approach, I did not let the consumer know anything about how to track the fobar. Today is a list of struct foobar , maybe tomorrow is a SQLite database. Anyone hiding the definition of a structure, the consumer should only know that foobar has a name and id .


To be truly modular, you will need two big things:

  • A set of APIs that defines how modules produce and consume data.
  • Method for loading modules at runtime

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

For # 1, you usually have a module registration system where some component keeps track of the list of loaded modules, as well as meta-information about what it produces and consumes.

If modules can invoke code provided by other modules, you will need a way to make this visible. This will also play in implementation 2. Take the Linux kernel, for example, it supports loadable kernel modules in order to add new features, drivers, etc. to the kernel, without having to compile it into one large binary file. Modules can use EXPORT_SYMBOL to indicate that a particular character (i.e., Function) is available to other called modules. The kernel keeps track of which modules are loaded, which functions they export, and to which addresses.

For # 2, you can use shared library support for the OS. On Linux and other Unix, these dynamic libraries are ELF files ( .so ) that are loaded by the dynamic loader into the process address space. On Windows, this is a DLL. Usually this download is automatically processed for you when you start your process. However, the application can use the dynamic loader to explicitly load additional modules of its choice. On POSIX you would call dlopen() , and on Windows you would use LoadLibrary() . Any of these functions will return some kind of handle that will allow you to make further requests or requests for the module.

Then your module may be required (according to your design) to export the codingfreak_init function, which is called by your application the first time the module is loaded. This function will then make additional calls to your infrastructure or return data to indicate what tools it requires and provides.

This is all very general information, which should lead to the rotation of your wheels.

+3
source

I would install the β€œpublic” API before you start coding. Then the code uses only this API from outside each module. Do not lie; use only the public API (although the API can be developed as needed). It can help handle data structures, such as objects in an object-oriented language, and a public API, as well as object methods, as far as possible. If possible, avoid using the fields of the internal data structure directly outside the module; although the return of clearly defined data structures is normal if they are part of the API. Just do not modify them directly outside the module from which they come. If you spend a lot of time developing interfaces in front, you can make a very convenient project.

0
source

All Articles