I have no experience with C (only C++ and higher level languages). Right now I have tried and failed to find general guidelines on how to write good C code in a way that allows to separate the programm logic clearly into replacable parts that can be combined with other parts or reused in other projects.

To give some context:

I will have two parties communicating over some channel (e.g. TCP or over a serial port, etc.) that both have to perform computations on the received messages and send their results back and forth many times. Both the exact nature of the computations and the type of communication may need to change. For this reason, I want to separate the communication and peripherals part from the computation-logic part into "modules" (whatever that might mean exactly in terms of the implementation), which I will call "peripheral module" and "protocol module". The communication is assymetric; I will call the parties "Master" and "Slave", which (I think) should both be implemented as the same single compiled programm, selecting Master/Slave behaviour as specified by user input.

The kind of thing I'm imagining is as follows: The "Master" process starts in its "peripheral module", which establishes a connection with the "Slave" process and initializes/fetches some data. It passes the data to the "protocol module", along with (as general as possible) instructions on how it can send messages to (and receive from) the "Slave Process". The protocol module takes over and sends messages to the "Slave Processes" where they are received by its "protocol module" and communication continues. During execution the "protocol module" should be able to send debug/status information back to the "peripheral module", which may or may not run as a separate thread (I currently don't think that is absolutely necessary). Both number, length and structure of messages sent depends on the "protocol module", as does the exact nature of the status/debug information it can provide. There may be several different versions of the "peripheral module", designed to run on different OS and possibly also integrated devices, that should all be able to use any "protocol module" implementation. There may also be several different implementations of the "protocol module".

My current implementation ideas:

Make a header file "interface.h" that acts as an interface between "protocol module" and "peripheral module" that all implementations of each module should conform to. This header file declares functions (all with external linkage) that look something like this (returning error codes with meanings also defined in "interface.h"):

  • int recieve_data(char* data, int num_bytes)
  • int send_data(char* buffer, int num_bytes)
  • int status_info_output(char* buffer, int num_bytes)
  • int begin_protocol(char* initial_data, int num_bytes, SOME_STRUCT config)

The methods recieve_data, send_data and status_info_output are defined by "peripherals.c" and called from "protocol.c", while begin_protocol is defined by "protocol.c" and called from "peripherals.c". The module "protocol.c" will have global variables defining configuration parameters. (With static linkage and perhaps visible access methods, declared in a "protocolXY.h" hearder file that is unique to each "protocol module" implementation).

The issue is where and how to defined SOME_STRUCT, since its exact content will be different (with large overlaps) for different implementations of "protocol.c". Also the "protocol module" will define message headers containing information that needs to be monitored during programm execution. I'm not sure if the status_info_output is versatile enough to provide such functionality. I'm also not sure how to inlude status messages and commands such as "the user interrupting communication during execution" in a way that is cleanly communicated to the other party and can be implemented independently of the "protocol module" (i.e. will most likely not differ between different implementations of it).

What bothers me further is that, from a design perspective, the "protocol module" could be viewed as some sort of static library (it doesn't have to be linked as such). I have never seen library headers demanding from the user (the "peripherals module") to define certain functions. I considered a scheme where begin_protocol is passed function pointers to tell it how to communicate, but this seems more convoluted and perhaps bad for performance optimization (which may be important for the integrated system).

Can anyone provide some advice on how to structure such a project in C and what common practices are in writing modular programms (and perhaps libraries)?

Thanks in advance!

  • 1
    If/when you support both TCP and Serial communications, do you want to support that from a single executable via configuration, or do you want that as separate executables? How comfortable would you be designing this in a language that supports OO like C++? – Bart van Ingen Schenau May 15 '20 at 12:05
  • 1
    Not related, but "recieve_data" should be spelt "receive_data" – Peter M May 15 '20 at 12:24
  • 1
    You might want to look at C plugin systems - what you're doing seems very similar or could be implemented this way. The Linux VFS layer is another example, with the filesystems being "plugged in" to the system with a "simple" interface. (I'm not saying filesystem code is simple. The registration interface is straightforward though.) – Mat May 15 '20 at 12:56
  • 1
    Function pointers are awesome. I don't think you should worry about performance; a function pointer is going to have essentially the same overhead as calling a function directly. – Robert Harvey May 15 '20 at 15:06
  • 1
    But to answer your question directly, if you're looking for runtime polymorphism (i.e. the processing of a message changes depending on its type), function pointers seem like a good way to go. No elaborate plugin system required. You could even write a factory method that accepts a "message type" and returns a function pointer to the correct processor function. – Robert Harvey May 15 '20 at 15:12

If you are fine with having to create a new executable when you want to use a different peripheral implementation, then your design is mostly good. In C it is not uncommon to have one header file specify an interface and then have multiple source files implementing it in different ways, where the build system decides which source file gets used.

What I would recommend to do differently is also the area where you have the most doubts. In my opinion, the peripheral layer/module should be only responsible for getting bytes across the communication link to the other side and to receive them there.

If you want the Master node to be responsible for choosing the protocol that will be used over the communication link, that is not something that belongs in the peripheral module. It would be a better design to have both modules start up with a "protocol selection" protocol. Then the Master communicates to the Slave using the "protocol selection" protocol what protocol to use for the rest of the communications and both devices activate or switch over to that protocol. This way, a management layer that knows about all available/supported protocols can handle the initialization.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.