Redirect stderr and stdout

Hello,
I am trying to implement a C# interface for mmgtools.
My current goal is to be able to use it in Grasshopper

I’m having a problem capturing output messages to stderr.

Due to the Grasshopper implementation, I cannot run mmgs as a child process and get error messages like BakeMyScan does in python.

At the moment I am targeting Windows and am forced to use Windows specific functions to redirect the output and error file descriptor.

Maybe it would be good to have a function to define stdout and stderr?
like:

FILE* MMGERR;
void MMGS_Set_stderr (FILE* f)
{
    if (err_stream) {
        MMGERR= f;
    } else {
        MMGERR= stderr;
    }
}
...
    fprinf(MMGERR, ...);

Another more complete option would be to change all messages on stderr to error codes.
This would allow any interface implementation to handle errors properly bypassing regular expressions on stderr.

Merci et bravo pour votre travail sur mmgtools! :blush:
jmv.

Hello,
Thanks for your suggetions!
We will take into consideration setting the file streams, it should not be too invasive and it can have many applications in fact.
Error codes surely would need a more careful design, since only MMG5_SUCCESS, MMG5_LOWFAILURE and MMG5_STRONFAILURE codes are returned from the API for the moment, but we will think about it.

Thanks!
Luca

Bonjour, désolé de parler en français, mon niveau d’anglais me limite dans mes échanges.

J’ai édité les sources pour pouvoir faire cette gestion des erreurs dans mon code C #.

Je ne prétends pas savoir si ma solution est correcte ou non, mon niveau en C est comme mon anglais. Mais cela fonctionne pour moi. Voici une approche possible et expérimentale :

J’ai regroupé les fonctions d’écriture dans une structure:

struct MMG5_Console {
    FILE* outf;
    void (*setlevel) (int level);
    void (*printf) (const char* const, ...);
    void (*flush) ();
} ;

extern struct MMG5_Console* MMG5_stdconsole_ptr;

int MMG5_Init_console(struct MMG5_Console** console);

static inline void MMG5_console_setlevel(int level) {
    if (level == 0) MMG5_stdconsole_ptr->outf = stdout;
    else MMG5_stdconsole_ptr->outf = stderr;
}
static inline void MMG5_console_printf(const char* const msg, ...) {
    va_list arglist;
    va_start(arglist, msg);
    vfprintf(MMG5_stdconsole_ptr->outf, msg, arglist);
    va_end(arglist);
}
static inline void MMG5_console_flush() {
    fflush(MMG5_stdconsole_ptr->outf);
}

Avec quelques macros pour aider

#define INFO      (*MMG5_stdconsole_ptr->setlevel) (0); \
                  (*MMG5_stdconsole_ptr->printf)("\n")

#define WARNING   (*MMG5_stdconsole_ptr->setlevel) (1); \
                  (*MMG5_stdconsole_ptr->printf)("\n  ## Warning: %s: ", __func__)
                  
 // ERROR define by wingdi.h
#define UERROR    (*MMG5_stdconsole_ptr->setlevel) (2); \
                  (*MMG5_stdconsole_ptr->printf)("\n  ## Error: %s: ", __func__)

#define MSG(...) (*MMG5_stdconsole_ptr->printf)(##__VA_ARGS__)

#define FLUSH    (*MMG5_stdconsole_ptr->flush)()

La fonction MMG5_Init_console et la structure MMG5_Console me permettent de modifier stderr et stdout à la volée. Ceci est utile lorsque je souhaite que les logs de la première passe soient séparés de ceux de la seconde passe. L’implémentation est vraiment simple

struct MMG5_Console* MMG5_stdconsole_ptr;
// MMG5_Init_console est avant tout une fonction utilitaire
// placer au début des points d'entrée tels que "int main (...)"
int MMG5_Init_console(struct MMG5_Console** console) {
    if (*console == NULL) {
        *console = (struct MMG5_Console*)calloc(1, sizeof(struct MMG5_Console));
    }
    if (*console == NULL) {
        fprintf(stderr, "\n  ## Error: Cannot initialize stdout/stderr ##  \n");
        return 0;
    }
    MMG5_stdconsole_ptr = *console;
    (*console)->outf = stdout;
    (*console)->setlevel = &MMG5_console_setlevel;
    (*console)->printf = &MMG5_console_printf;
    (*console)->flush = &MMG5_console_flush;
    return 1;
}

Pour le reste en effet ce n’est pas trop invasif mais surtout long et répétitif. il s’agit de modifier tous les “printf”

void MMG5_mmgUsage(char *prog) {
  INFO;
  MSG("\nUsage: %s [-v [n]] [opts..] filein [fileout]\n",prog);
...

ou encore

/** Safe allocation with calloc */
#define MMG5_SAFE_CALLOC(ptr,size,type,law) do   \
  {                                              \
    ptr = (type*)mycalloc(size,sizeof(type));    \
    if ( !ptr ) {                                \
      UERROR;                                    \
      MSG("Memory problem: calloc");             \
      law;                                       \
    }                                            \
  }while(0)

Il aurait été possible d’écrire directement WARNING (message), mais je les ai décomposés pour pouvoir redéfinir complètement les macros et avoir une logique basée sur un état. INFO, WARNING et UERROR servent donc de commutateur entre les différents flux de sortie.

Sur ma copie locale, je n’ai modifié que les sources de “common” et “mmgs”, dont j’avais besoin.
Si cette implémentation peut vous être utile dites-moi et je vous enverrai un lien vers les sources.
Pour le moment je n’ai pas fini, je me retrouve coincé à un autre niveau. J’essaye d’automatiser la création des liens entre votre code source C et mes sources C#
Passer de

int MMGS_mmgslib(MMG5_pMesh mesh,MMG5_pSol met);

à

[DllImport ("mmg-api.dll")]
internal static extern int MMGS_mmgslib(IntPtr mesh,IntPtr met);

jmv

Bonjour jmv,
Merci pour avoir partagé ta solution!

Je vois la logique de séparer le niveau et le message et ça me semble pratique; cela combiné avec des fonctions du type MMG5_Set_stdout et MMG5_Set_stderr comme proposé dans le premier message peut s’avérer très versatile. Je pense notamment soit à la possibilité de séparer les logs pour des exécutions successives, soit à la possibilité de séparer les logs de chaque procès pour le debug parallèle de ParMmg (seulement 3D pour l’instant).

J’espère que ça a avancé sur les liens, malheureusement je n’ai pas de compétences en C#…

Luca