2

I've project where I need to distinguish files belongs to linux daemon (witten in C) and simple linux program (written in C++). Those two projects used 2 shared files (helpers_functions). Daemon and program has different logging system. Daemon write to file, program to stdout.

Problem occurs when I want to log something in common functions for both programs (inside helper_functions file). I don't want to pass via parameter, that this is program A, or program B.

I've compile files belongs to separate programs with g++ flag -D, but what can I do, when I want to log from common files? I cannot define there anything, because I don't know when I use it for program A, or when for program B.

fuz
  • 88,405
  • 25
  • 200
  • 352
maslokarol
  • 43
  • 7
  • you might want to look into using a logging library. All code would log using this library, and each of your applications would have their own specific configuration for the logging library (one could write to standard out, another could write to a file, etc.). – Sander De Dycker Dec 03 '15 at 10:33
  • If I understand you correctly, only the linker really knows whether `helper_functions.o` goes into program A or program B? That means you can't use portable C++ methods, you need linker tricks. E.g. redefine the logger function of A as a _weak symbol_ in the helper_functions file. – MSalters Dec 03 '15 at 10:38

3 Answers3

1

You could add a global variable

const int iamprogram = ...;

which is defined to be PROGRAM_A in program A and PROGRAM_B in program B to solve the immediate problem. You could also make this variable directly contain the file you want to log to:

const char *program_logfile = "/path/to/logfileA";

In the long run, I suggest you to refactor your code such that the common code doesn't depend on which program it is part of. That's much more maintainable and expandable for the case where you want to use the code for a third program as well.

fuz
  • 88,405
  • 25
  • 200
  • 352
1

You could implement a callback for getting the program specific output. There's two benefits: no dependency from common part to application (common part defines the interface) and you can make the distinction at run time vs compile time, which gives more legroom for future development, such as changing the output via command line parameters or user interaction.

In the following example, let's refer to the common code part as "library".

library.h

typedef void (*logFunc_t)( logBuffer_t );
void setLogOutput( logFunc_t applicationLog );

library.c

logFunc_t logger; // might be good idea to initialize to an empty function, but omitted here

void setLogOutput( logFunc_t applicationLog )
{
  logger = applicationLog;
}

void log( logBuffer_t data )
{
  logger( data );
}

application.cpp / application.c

// here you should have the extern "C" in C++ application to ensure linkage compatibility
// I am assuming your shared code is C
extern "C" void myLogger( logBuffer_t data );

int main( int argc, char* agv[] )
{
  setLogOutput( &myLogger );
  // ...do your thing
  return 0;
}

void myLogger( logBuffer_t data )
{
  // ...log wherever
}
sendaran
  • 566
  • 3
  • 9
  • This isn't a callback, it's just dispatching to a logging system through a function pointer. It does look helpful, but IDK if you need the `set` wrapper, rather than putting that line in `main` or somewhere else that initializes logging. – Peter Cordes Dec 03 '15 at 13:15
  • @PeterCordes The way I understood the question is that OP wants to use the logging system of the running application for whatever is done in the common part without introducing dependency from common part to the specific application. So the common part calls `log(...)` and application calls it's own logger directly. Application shouldn't have direct control of the common part, and in hindsight `logger` should be `static`. So, provide means for common part to use the existing logging system but don't enforce it. Hence the wrapper to respect encapsulation. Common part can have default safe init. – sendaran Dec 03 '15 at 13:41
  • Wait a minute, if the function pointer is `static`, then how do the library functions use it? If `log()` is to be inlined at each call site, the function-pointer `logger` has to be global, not static. It could be a private member of a class, which has a global instance. But that won't work because one is written in C. – Peter Cordes Dec 03 '15 at 13:53
  • I was just in the middle of writing an answer about having the library functions directly call a logging function they *can't* see the definition for, so the symbol is unresolved in the library. It would be resolved at (dynamic-)link time. (If this works). If your method depends on that, you should just provide a logging API that libraries can use, with different implementations in the different programs. No need for a static function pointer. – Peter Cordes Dec 03 '15 at 13:55
  • @PeterCordes Inlining `log()` is a whole different thing, if such is required. But if `logger` is a static function pointer in the common module that implements `log()`, there should be no issues. Also, if the common part wants to do some output formatting inside the `log()` function instead of before calling it, inlining might not be desirable. Anyway, I was trying to give an example more on the generic and nicely encapsulated side rather than trying to optimize everything. – sendaran Dec 03 '15 at 14:03
  • Oh, you're suggesting the `static` function pointer is owned by the library? So the `setLogger()` function is defined by the library? Ah yes, that is a callback. Since you don't have any library-only section of your code block, just a "common" one, I was thinking you were going to have the `log()` function definition in the main program, like I'm suggesting in my answer. (Probably because that's what I had already thought of, and didn't realize you were saying something different until I read more carefully.) Anyway, see my answer for what I was talking about. – Peter Cordes Dec 03 '15 at 14:39
  • 1
    @PeterCordes Yes, that is what I was after. Seems we both had a similar direction of thought but slightly different approaches. I guess the main difference between your solution and mine is that your solution requires the application to implement the API, mine allows you to leave it out, but then requires that the library has a safe initialization for the case where the logging facility is not provided. I guess I'll divide my code blocks for clarity. – sendaran Dec 03 '15 at 14:53
  • yup, looks great now. Before, when it just said "common", I was thinking "shared by the library and each program", rather than "used by the library code, which is in-turn common to the two programs". But again, that's mostly because I already had a different idea in mind. Having a default log-to-stdout or stderr implementation built in to the library sounds reasonable. Then you could have the static function pointer initialized to point at that. The simple C++ program might then just use that, instead of providing its own function. – Peter Cordes Dec 03 '15 at 15:11
1

I'm not 100% sure if runtime dynamic linking can handle this. It would definitely work if you statically link the helper functions into each executable.

Provide a logging function with the same API in both programs. Have the library functions that want to log something call this function. They get the implementation provided by the program that's using the library.

Header file included by each program, and by the library

// common_log.h
#ifdef __cplusplus
extern "C"  // for the following definition only, no opening {
#endif

// used by code that can be part of either program
void common_log(char *msg, int log_prio);

Implementation in the tty C++ program (simple logging):

#include "common_log.h"
#include <iostream>

// used by the rest of the C++ program
void simple_logger(char *msg) {
    cerr << msg;
}

extern "C" void common_log(char *msg, int log_prio) {
    simple_logger(msg);
}

Implementation in the daemon C program:

#include "common_log.h"
#include <stdio.h>
#include <errno.h>

static FILE *logfp;
static int log_level;

// used by daemon code
void fancy_logger(char *msg, int log_prio) {
    if (log_prio < log_level)
        return;
    if (EOF == fputs(logfp, msg)) {
        perror("failed to write log message to log file: ");
    }
}

// or use linker tricks to make common_log an alias for fancy_log,
// if they both have the same signature and you don't need to do anything in the wrapper.

//extern "C"   // this is already C
void common_log(char *msg, int log_prio) {
    fancy_logger(msg, log_prio);
}

This requires the linker to be able to resolve undefined symbols in the library using symbols from the program that's linked against it. I think that works, similar to a library providing a weak definition of a global variable, so the main program's definition takes precedence.


If it was ok for simple_logger to also be extern "C" and have the same signature, you could just name them the same and avoid the bounce function. Or if the common function could be an alias for the program's own logging function in either of the programs, I think there are linker tricks to actually do that, instead of compiling to a single jmp instruction (tail-call optimization).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847