1

Moinsen,

I am somehow stucked in a design problem. Language is ANSI-C.

Lets assume we have a tinkerbox of software-modules:

  • one module for the logic Logic
  • (at least) one module doing some logging Logger
  • two modules, both giving a "frame" to let the program run, lets say
    • one with a GUI
    • one for commandline
  • ...

Therefore, the same logic could used in a comandline- and a graphic-version of the software. The Logic has to log some errors but should not know anything about the specific logger as it could be dependent on the "frame". It is obvious to give Logic a function pointer that has to be filled by the frame to bind the used Logger to the Logic.

At Logging-Module (all code Pseudo-ANSI-C):

    void Logger_Log(char *sLogText)
    {
      //do some stuff
    }

At Logic-Module:

    void Logic_PseudoLog(char *sLogText)
    {
      printf(sLogText);
    }

    void(* Logic_Log)(char *sLogText) = &Logic_PseudoLog;

    void Logic_SetLogger(void(* LogFct)(char *sLogText))
    {
      Logic_Log = LogFct;
    }

At GUI/Cmd-Line:

    #include "Logger.h"
    #include "Logic.h"
    Logic_SetLogger(&Logger_Log);

Now I want to introduce different severity levels for logging and implement them as an enum in Logger:

    //Logger.h:
    typedef enum {
      DEBUG,
      INFO,
      ERROR
    } teLogLevel;

    void Logger-Log(char *sLogText, teLogLevel eLevel);

And here the problem rises: The function-pointer at Logic needs to have the correct signature. To do so, it has to know about teLogLevel. Therefore Logic has to know about the Logger, exactly the case I wanted to avoid with the indirection in the first place.

    #include "Logger.h"
    void(* Logic_Log)(char *sLogText, teLogLevel eLevel);

The situation as layed out is just an example. Please don't solve it by saying something like "use int instead of enum" or "build three functions for the levels". The bottemline question is:

How to handle enums in an indirection with functionpointers at ANSI-C?

How to "inject" enums into a module, that should not now about the origin of the enums?

2 Answers 2

1

In your current design, the Logic module already has a dependency on the Logger module, but that dependency is hidden. The dependency lies in the fact that both Logic and Logger must agree on the set of functions used for logging (one function) and their signatures (void (char*)).

The way out of here is to clearly separate the interface from the implementation. In particular, the Logger module only provides the logging interface. The implementations are either provided by the GUI/Cmd-Line modules, or by separate Logger-Implementation modules that can depend on the GUI/Cmd-Line modules.

The Logger module would only consist of a single header file, like this

/* File: Logger.h */
typedef enum {
  DEBUG,
  INFO,
  ERROR
} teLogLevel;

typedef void (*LoggerFuncT)(char *sLogText, teLogLevel eLevel);

/* Logger function that discards all logging. Can be used as a safe default. */
static void NullLogFunc(char*, teLogLevel) {}

All other modules that either use or implement logging functionality need to refer to this header to know the correct function signatures and other types (including enums).
For example, the Logic module would use it like this:

#include "Logger.h"
LoggerFuncT Logic_Log = NullPseudoLog;

void Logic_SetLogger(LoggerFuncT LogFct)
{
  Logic_Log = LogFct;
}

void Logic_DoSomething()
{
  Logic_Log("Entering Logic_DoSomething()", DEBUG);
  //...
  Logic_Log("Leaving Logic_DoSomething()", DEBUG);
}

In the GUI module, you could have an implementation like this:

void GUI_LogToDebugWindow(char* sText, teLogLevel eLevel)
{
    //...
}

/* Let the Logic module use this function as logger.
 * The compiler will complain here if the logger signatures don't match. */
Logic_SetLogger(GUI_LogToDebugWindow); 
3
  • the typedef puzzles me. Could you give an example how to implement the function using the new type?
    – Oliver
    Commented Jul 2, 2014 at 10:50
  • @Oliver: See my update. Unfortunately, the typedef can't be used effectively when defining a function. Commented Jul 2, 2014 at 11:05
  • I did it just the way you showed. I hoped, there is a way to use the typedef at the definition of the function I don't see. Something like LoggerFunct GUI_LogToDebugWindow().. But thanks for clarification and the edit. It will hopefully help the next reader!
    – Oliver
    Commented Jul 2, 2014 at 11:19
0

Logic.h declares a function to set the logger function: Logic_SetLogger(), in order to use it.

Therefore, Logic.h must know the prototype of the logger function, whatever Logger.h is, i.e., in the most generic way possible.

This is already the case with the first argument: a pointer to a char.

For the second argument, I see two alternatives:

  1. The usual way to handle it is to use an int, which is the generic form of an enum. Representing an error level with an int is very common among C developers.

  2. In your case, not wanting an int, you need to use an enum type in the prototype definition of the logger function (which is in Logic.h as the argument of Logic_SetLogger()). Therefore, Logic.h needs to define the enum type as well.

In my opinion, defining error levels in Logic.h is not a good design choice.

3
  • Agreed on both points, but 1) is a bit to specific, the logger was more an example, I also have enmus that are "not ordered". 2) I totaly agree at that this is not a good choice. Therefore I asked for a better one..
    – Oliver
    Commented Jul 2, 2014 at 7:50
  • In C, enums get ordered as soon as they are defined, even enum Color or enum Fruit. So using an int as a generic form of enum still makes sense.
    – mouviciel
    Commented Jul 2, 2014 at 9:03
  • I have a different view. Technically you are right, but the strength in enum is, from my point of view, to have speaking names instead of numbers. To stay in the example, I am not only not able to say Log("Text", DEBUG) but I also have to know, which int corespondent to DEBUG.
    – Oliver
    Commented Jul 2, 2014 at 11:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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