Please advise with libcsv. I need to read only first row - c

I had successfully used libcsv to parse CSV files. Fast and simple to use, I tell you. But now comes a situation where I am a little stuck. Being able to parse file as whole, now I need to parse only first row.
My basic usage(not too far from the official documentation) is following:
typedef struct data_t {
<...>
} data_t;
...
void cb_f(void *s, size_t len, void *data) {
...
}
void cb_r(int c, void *data) {
((counts*)data)->rows++;
}
...
struct csv_parser p;
data_t data;
...
if(csv_parse(&p, buf, fsize, cb_f, cb_r, &data) != fsize) {
fprintf(stderr, " * while parsing file: %s\n", csv_strerror(csv_error(&p)) );
return 1;
}
...
csv_fini(&p, NULL, NULL, NULL);
Now I need to somehow stop the parser after it had processed the first row of a CSV file.
I did try to call csv_fini(&p, NULL, NULL, NULL); in the row callback, cb_r, early. No luck, the parser stops only after parsing whole buffer.
Any suggestions of how to change the libcsv parser operation mode ?

Related

Decoding data with libFLAC using C

My purpose is to decode flac data for testing purposes. I'm going to create a small stub that has a function that takes data and size as input arguments. There is no need to make anykind of output file because I'm only going to make test for decoding. I have read some examples and api documentation from libflac page (https://xiph.org/flac/api/).
Now this is giving me :ERROR: initializing decoder: (null) because FLAC__stream_decoder_init_stream is commented. Reason it is commented is that I don't know how to properly use it and get decoding work. Any advice and comments that could help me to get decoding work?
#include <stdio.h>
#include <stdlib.h>
#include "share/compat.h"
#include "FLAC/stream_decoder.h"
static void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *data);
static FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *data);
static FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *data);
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FLAC__bool ok = true;
FLAC__StreamDecoder *decoder = 0;
FLAC__StreamDecoderInitStatus init_status;
// init decoder
if((decoder = FLAC__stream_decoder_new()) == NULL) {
fprintf(stderr, "ERROR: allocating decoder\n");
return 1;
}
(void)FLAC__stream_decoder_set_md5_checking(decoder, true);
init_status = FLAC__stream_decoder_init_stream ( decoder, read_callback, /*seek_callback*/ NULL, /*tell_callback*/ NULL, /*length_callback*/ NULL, /*eof_callback*/ NULL, write_callback, /*metadata_callback*/ NULL, error_callback, data);
if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
fprintf(stderr, "ERROR: initializing decoder: %s\n", FLAC__StreamDecoderInitStatusString[init_status]);
ok = false;
}
if(ok) {
ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
fprintf(stderr, "decoding: %s\n", ok? "succeeded" : "FAILED");
fprintf(stderr, " state: %s\n", FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)]);
}
FLAC__stream_decoder_delete(decoder);
return 0;
}
void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *data)
{
(void)decoder, (void)data;
fprintf(stderr, "Got error callback: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
}
Update:
After adding callback functions I get this error:
flac_fuzzer.c:23:16: error: no matching function for call to 'FLAC__stream_decoder_init_stream'
init_status = FLAC__stream_decoder_init_stream ( decoder,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/FLAC/stream_decoder.h:1073:40: note: candidate function not viable: no known conversion from 'const uint8_t *'
(aka 'const unsigned char *') to 'void *' for 10th argument; take the address of the argument with &
FLAC_API FLAC__StreamDecoderInitStatus FLAC__stream_decoder_init_stream(
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You typically need to implement a event processing framework, similar to what is done with ffmpeg where the flac library hooks into the event processing using the callbacks. What you have implemented so far looks like the (minimal) initialization part of the flow graph, but you are missing all the processing elements.
Shamefully just looking at what somebody else have done here and here you will need to implement the callback functions with signatures like
write callback example:
static FLAC__StreamDecoderWriteStatus flac_write_music_cb(
const FLAC__StreamDecoder *decoder,
const FLAC__Frame *frame,
const FLAC__int32 *const buffer[],
void *client_data)
error callback example:
static void flac_error_music_cb(
const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status,
void *client_data)
You should also look at how the flac data is read, and implement the supporting function as as well.
Since you used extern "C", then you must be actually compiling it as C++ code. Now, in C++ you need to use const_cast<> in order to pass const uint8_t* argument where a non-constant one (i.e. void*) is expected.

Loading from file to linked list

I am trying to load an unknown amount of data from a file in to a linked list
Load Function
void load(FILE *file, Node **head) // while feof somewhere
{
char tempArtist[30]={'\0'}, tempAlbum[30]={'\0'}, tempTitle[30]={'\0'}, tempGenre[30]={'\0'},tempSpace='\0';
char tempPlay[100]={'\0'}, tempRating[6]={'\0'}, tempMins[8]={'\0'}, tempSecs[8]={'\0'};
Data *temp;
temp=(Data*)malloc(sizeof(Data));
while(!feof(file))
{
fscanf(file,"%s",tempArtist);
fscanf(file,"%s",tempAlbum);
fscanf(file,"%s",tempTitle);
fscanf(file,"%s",tempGenre);
fscanf(file,"%s",tempMins);
fscanf(file,"%s",tempSecs);
fscanf(file,"%s",tempPlay);
fscanf(file,"%s",tempRating);
temp->mins=strdup(tempMins);
temp->secs=strdup(tempSecs);
temp->album=strdup(tempAlbum);
temp->artist=strdup(tempArtist);
temp->genre=strdup(tempGenre);
temp->song=strdup(tempTitle);
temp->played=strdup(tempPlay);
temp->rating=strdup(tempRating);
insertFront(head,temp);
}
}
The problem I am having is that when I go to print out the list all the entries are the same as the last one read from the file. This is something to do with the strdup() but I can't get it to copy to the data type without getting access violations.
What is another method(proper method) that I could use to copy the strings read from the file and pass them to the insert?
InsertFront
void insertFront(Node **head, Data *data)
{
Node *temp=makeNode(data);
if ((*head) == NULL)
{
*head=temp;
}
else
{
temp->pNext=*head;
*head=temp;
}
}
Data struct
typedef struct data
{
char *artist;
char *album;
char *song;
char *genre;
char *played;
char *rating;
char *mins;
char *secs;
}Data;
testing file
snoop
heartbeat
swiggity
rap
03
10
25
4
hoodie
cakeboy
birthday
hiphop
02
53
12
5
You use the same instance of temp for all your lines. The allocation should go inside the loop, ideally after you have established that the whole entry was read in successfully.
By the way, feof is not a good way to control input loops. You should check the return value of fscanf instead.
while (1) {
if (fscanf(file,"%s",tempAlbum) < 1 ) break;
if (fscanf(file,"%s",tempArtist) < 1) break;
// ...
temp = (Data *) malloc(sizeof(Data));
temp->album=strdup(tempAlbum);
temp->artist=strdup(tempArtist);
// ...
insertFront(head, temp);
}
Note how the allocation of a new node happens only after a whole record was read.
There are other ways to improve the code. For example you have very short buffers for the strings that contain numerical data. What if there is a line slip-up and you read a longer line into into such a short buffer? Also, your input seems to be line-wise, so it might be good to use fgets instead of fscan(f, "%s"), which will only read "words" and will have problems with lines that have spaces.
while(!eof) is wrong. You can do like.
for (;;) {
ret = fscanf()
if(ret == EOF) break;
}

bilingual program in console application in C

I have been trying to implement a way to make my program bilingual : the user could chose if the program should display French or English (in my case).
I have made lots of researches and googling but I still cannot find a good example on how to do that :/
I read about gettext, but since this is for a school's project we are not allowed to use external libraries (and I must admit I have nooo idea how to make it work even though I tried !)
Someone also suggested to me the use of arrays one for each language, I could definitely make this work but I find the solution super ugly.
Another way I thought of is to have to different files, with sentences on each line and I would be able to retrieve the right line for the right language when I need to. I think I could make this work but it also doesn't seem like the most elegant solution.
At last, a friend said I could use DLL for that. I have looked up into that and it indeed seems to be one of the best ways I could find... the problem is that most resources I could find on that matter were coded for C# and C++ and I still have no idea how I would do to implement in C :/
I can grasp the idea behind it, but have no idea how to handle it in C (at all ! I do not know how to create the DLL, call it, retrieve the right stuff from it or anything >_<)
Could someone point me to some useful resources that I could use, or write a piece of code to explain the way things work or should be done ?
It would be seriously awesome !
Thanks a lot in advance !
(Btw, I use visual studio 2012 and code in C) ^^
If you can't use a third party lib then write your own one! No need for a dll.
The basic idea is the have a file for each locale witch contains a mapping (key=value) for text resources.
The name of the file could be something like
resources_<locale>.txt
where <locale> could be something like en, fr, de etc.
When your program stars it reads first the resource file for specified locale.
Preferably you will have to store each key/value pair in a simple struct.
Your read function reads all key/value pair into a hash table witch offers a very good access speed. An alternative would be to sort the array containing the key/value pairs by key and then use binary search on lookup (not the best option, but far better than iterating over all entries each time).
Then you'll have to write a function get_text witch takes as argument the key of the text resource to be looked up an return the corresponding text in as read for the specified locale. You have to handle keys witch have no mapping, the simplest way would be to return key back.
Here is some sample code (using qsort and bsearch):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define DEFAULT_LOCALE "en"
#define NULL_ARG "[NULL]"
typedef struct localized_text {
char* key;
char* value;
} localized_text_t;
localized_text_t* localized_text_resources = NULL;
int counter = 0;
char* get_text(char*);
void read_localized_text_resources(char*);
char* read_line(FILE*);
void free_localized_text_resources();
int compare_keys(const void*, const void*);
void print_localized_text_resources();
int main(int argc, char** argv)
{
argv++;
argc--;
char* locale = DEFAULT_LOCALE;
if(! *argv) {
printf("No locale provided, default to %s\n", locale);
} else {
locale = *argv;
printf("Locale provided is %s\n", locale);
}
read_localized_text_resources(locale);
printf("\n%s, %s!\n", get_text("HELLO"), get_text("WORLD"));
printf("\n%s\n", get_text("foo"));
free_localized_text_resources();
return 0;
}
char* get_text(char* key)
{
char* text = NULL_ARG;
if(key) {
text = key;
localized_text_t tmp;
tmp.key = key;
localized_text_t* result = bsearch(&tmp, localized_text_resources, counter, sizeof(localized_text_t), compare_keys);
if(result) {
text = result->value;
}
}
return text;
}
void read_localized_text_resources(char* locale)
{
if(locale) {
char localized_text_resources_file_name[64];
sprintf(localized_text_resources_file_name, "resources_%s.txt", locale);
printf("Read localized text resources from file %s\n", localized_text_resources_file_name);
FILE* localized_text_resources_file = fopen(localized_text_resources_file_name, "r");
if(! localized_text_resources_file) {
perror(localized_text_resources_file_name);
exit(1);
}
int size = 10;
localized_text_resources = malloc(size * sizeof(localized_text_t));
if(! localized_text_resources) {
perror("Unable to allocate memory for text resources");
}
char* line;
while((line = read_line(localized_text_resources_file))) {
if(strlen(line) > 0) {
if(counter == size) {
size += 10;
localized_text_resources = realloc(localized_text_resources, size * sizeof(localized_text_t));
}
localized_text_resources[counter].key = line;
while(*line != '=') {
line++;
}
*line = '\0';
line++;
localized_text_resources[counter].value = line;
counter++;
}
}
qsort(localized_text_resources, counter, sizeof(localized_text_t), compare_keys);
// print_localized_text_resources();
printf("%d text resource(s) found in file %s\n", counter, localized_text_resources_file_name);
}
}
char* read_line(FILE* p_file)
{
int len = 10, i = 0, c = 0;
char* line = NULL;
if(p_file) {
line = malloc(len * sizeof(char));
c = fgetc(p_file);
while(c != EOF) {
if(i == len) {
len += 10;
line = realloc(line, len * sizeof(char));
}
line[i++] = c;
c = fgetc(p_file);
if(c == '\n' || c == '\r') {
break;
}
}
line[i] = '\0';
while(c == '\n' || c == '\r') {
c = fgetc(p_file);
}
if(c != EOF) {
ungetc(c, p_file);
}
if(strlen(line) == 0 && c == EOF) {
free(line);
line = NULL;
}
}
return line;
}
void free_localized_text_resources()
{
if(localized_text_resources) {
while(counter--) {
free(localized_text_resources[counter].key);
}
free(localized_text_resources);
}
}
int compare_keys(const void* e1, const void* e2)
{
return strcmp(((localized_text_t*) e1)->key, ((localized_text_t*) e2)->key);
}
void print_localized_text_resources()
{
int i = 0;
for(; i < counter; i++) {
printf("Key=%s value=%s\n", localized_text_resources[i].key, localized_text_resources[i].value);
}
}
Used with the following resource files
resources_en.txt
WORLD=World
HELLO=Hello
resources_de.txt
HELLO=Hallo
WORLD=Welt
resources_fr.txt
HELLO=Hello
WORLD=Monde
run
(1) out.exe /* default */
(2) out.exe en
(3) out.exe de
(4) out.exe fr
output
(1) Hello, World!
(2) Hello, World!
(3) Hallo, Welt!
(4) Hello, Monde!
gettext is the obvious answer but it seems it's not possible in your case. Hmmm. If you really, really need a custom solution... throwing out a wild idea here...
1: Create a custom multilingual string type. The upside is that you can easily add new languages afterwards, if you want. The downside you'll see in #4.
//Terrible name, change it
typedef struct
{
char *french;
char *english;
} MyString;
2: Define your strings as needed.
MyString s;
s.french = "Bonjour!";
s.english = "Hello!";
3: Utility enum and function
enum
{
ENGLISH,
FRENCH
};
char* getLanguageString(MyString *myStr, int language)
{
switch(language)
{
case ENGLISH:
return myStr->english;
break;
case FRENCH:
return myStr->french;
break;
default:
//How you handle other values is up to you. You could decide on a default, for instance
//TODO
}
}
4: Create wrapper functions instead of using plain old C standard functions. For instance, instead of printf :
//Function should use the variable arguments and allow a custom format, too
int myPrintf(const char *format, MyString *myStr, int language, ...)
{
return printf(format, getLanguageString(myStr, language));
}
That part is the painful one : you'll need to override every function you use strings with to handle custom strings. You could also specify a global, default language variable to use when one isn't specified.
Again : gettext is much, much better. Implement this only if you really need to.
the main idea of making programs translatable is using in all places you use texts any kind of id. Then before displaying the test you get the text using the id form the appropriate language-table.
Example:
instead of writing
printf("%s","Hello world");
You write
printf("%s",myGetText(HELLO_WORLD));
Often instead of id the native-language string itself is used. e.g.:
printf("%s",myGetText("Hello world"));
Finally, the myGetText function is usually implemented as a Macro, e.g.:
printf("%s", tr("Hello world"));
This macro could be used by an external parser (like in gettext) for identifying texts to be translated in source code and store them as list in a file.
The myGetText could be implemented as follows:
std::map<std::string, std::map<std::string, std::string> > LangTextTab;
std::string GlobalVarLang="en"; //change to de for obtaining texts in German
void readLanguagesFromFile()
{
LangTextTab["de"]["Hello"]="Hallo";
LangTextTab["de"]["Bye"]="Auf Wiedersehen";
LangTextTab["en"]["Hello"]="Hello";
LangTextTab["en"]["Bye"]="Bye";
}
const char * myGetText( const char* origText )
{
return LangTextTab[GlobalVarLang][origText ].c_str();
}
Please consider the code as pseudo-code. I haven't compiled it. Many issues are still to mention: unicode, thread-safety, etc...
I hope however the example will give you the idea how to start.

How to copy bitmap to clipboard using the win32 API?

How do I copy a buffer that would save to a ".BMP" file to the clipboard using the win32 API? I.e., I have a raw buffer of a Windows V3 Bitmap (including the header) that I can literally write() to a file and will result in a valid .BMP file, but I want to copy it to the clipboard instead.
On OS X, in plain C, the code would look something like this (which works as intended):
#include <ApplicationServices/ApplicationServices.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
PasteboardRef clipboard;
CFDataRef data;
if (PasteboardCreate(kPasteboardClipboard, &clipboard) != noErr) {
return PASTE_OPEN_ERROR;
}
if (PasteboardClear(clipboard) != noErr) return PASTE_CLEAR_ERROR;
data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, bitmapBuffer, buflen,
kCFAllocatorNull);
if (data == NULL) {
CFRelease(clipboard);
return PASTE_DATA_ERROR;
}
if (PasteboardPutItemFlavor(clipboard, 42, kUTTypeBMP, data, 0) != noErr) {
CFRelease(data);
CFRelease(clipboard);
return PASTE_PASTE_ERROR;
}
CFRelease(data);
CFRelease(clipboard);
return PASTE_WE_DID_IT_YAY;
}
I am unsure how to accomplish this with the win32 API. This is as far as I've gotten, but it seems to silently fail (that is, the function returns with a successful error code, but when attempting to paste, the menu item is disabled).
#include <windows/windows.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
if (!OpenClipboard(NULL)) return PASTE_OPEN_ERROR;
if (!EmptyClipboard()) return PASTE_CLEAR_ERROR;
if (SetClipboardData(CF_DSPBITMAP, bitmapBuffer) == NULL) {
CloseClipboard();
return PASTE_PASTE_ERROR;
}
CloseClipboard();
return PASTE_WE_DID_IT_YAY;
}
Could anyone provide some insight as to how to fix this?
Edit
Per Aaron and martinr's suggestions, I've now modified the code to the following:
#include <windows/windows.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
HGLOBAL hResult;
if (!OpenClipboard(NULL)) return PASTE_OPEN_ERROR;
if (!EmptyClipboard()) return PASTE_CLEAR_ERROR;
hResult = GlobalAlloc(GMEM_MOVEABLE, buflen);
if (hResult == NULL) return PASTE_DATA_ERROR;
memcpy(GlobalLock(hResult), bitmapBuffer, buflen);
GlobalUnlock(hResult);
if (SetClipboardData(CF_DSPBITMAP, hResult) == NULL) {
CloseClipboard();
return PASTE_PASTE_ERROR;
}
CloseClipboard();
return PASTE_WE_DID_IT_YAY;
}
But it still has the same result. What am I doing wrong?
Final Edit
The working code:
#include <windows/windows.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
HGLOBAL hResult;
if (!OpenClipboard(NULL)) return PASTE_OPEN_ERROR;
if (!EmptyClipboard()) return PASTE_CLEAR_ERROR;
buflen -= sizeof(BITMAPFILEHEADER);
hResult = GlobalAlloc(GMEM_MOVEABLE, buflen);
if (hResult == NULL) return PASTE_DATA_ERROR;
memcpy(GlobalLock(hResult), bitmapBuffer + sizeof(BITMAPFILEHEADER), buflen);
GlobalUnlock(hResult);
if (SetClipboardData(CF_DIB, hResult) == NULL) {
CloseClipboard();
return PASTE_PASTE_ERROR;
}
CloseClipboard();
GlobalFree(hResult);
return PASTE_WE_DID_IT_YAY;
}
Thanks, martinr!
I think the hMem needs to be a return value from LocalAlloc, an HMEMORY rather than a pointer.
EDIT
Sorry yes, GlobalAlloc with GMEM_MOVEABLE is required, not LocalAlloc.
EDIT
I suggest you use CF_DIB clipboard data format type.
DIB is the same as BMP except it is without the BITMAPFILEHEADER, so copy the source bytes except for the first sizeof(BITMAPFILEHEADER) bytes.
EDIT
From OpenClipboard() documentation (http://msdn.microsoft.com/en-us/library/ms649048(VS.85).aspx):
"If an application calls OpenClipboard with hwnd set to NULL, EmptyClipboard sets the clipboard owner to NULL; this causes SetClipboardData to fail."
You need to set up a window; even if you're not doing WM_RENDERFORMAT type stuff.
I found this a lot with Windows APIs. I haven't used the Clipboard APIs per se but with other APIs I usually found that creating a hidden window and passing that handle to the relevant API was enough to keep it quiet. There's usually some notes on issues to do with this if you're creating a window from a DLL rather than an EXE; read whatever is the latest Microsoft word about DLLs, message loops and window creation.
As regardsBITMAPINFO, that's not the start of the stream the clipboard wants to see :- the buffer you give to SetClipboardData should start right after where the BITMAPFILEHEADER stops.
You need to pass a HANDLE to SetClipboard() (that is - memory allocated with GlobalAlloc()) rather than passing a straight pointer to your bitmap.
Echo Aaron and martinr. Example here, crucial section:
// Allocate a global memory object for the text.
hglbCopy = GlobalAlloc(GMEM_MOVEABLE,
(cch + 1) * sizeof(TCHAR));
if (hglbCopy == NULL)
{
CloseClipboard();
return FALSE;
}
// Lock the handle and copy the text to the buffer.
lptstrCopy = GlobalLock(hglbCopy);
memcpy(lptstrCopy, &pbox->atchLabel[ich1],
cch * sizeof(TCHAR));
lptstrCopy[cch] = (TCHAR) 0; // null character
GlobalUnlock(hglbCopy);
// Place the handle on the clipboard.
SetClipboardData(CF_TEXT, hglbCopy);

printing on screen and a text file

I need to dump the certain things into a text file and same has needs to be displayed on screen. (I'm telling about a C program utiltiy)
The menu option looks like following,
1. display AA parameters
2. display BB parameters
3. display CC parameters
4. dump all
5. Exit
Select option >
If they select 1/2/3, it just needs to displayed on screen only or if they select option #4,it need to display all the parameters one by one and same needs to dumped in a .txt file.
I know, we can use the printf and fprintf functions to display on screen and write it to text file respectively. The thing is that I've display more that 20 parameters and each have at least 20 sub-parameters.
I'm currently implemented as below,
printf ( "Starting serial number [%ld]\n",
serial_info_p->start_int_idx);
fprintf(file_p, "Starting serial number [%ld]\n",
serial_info_p->start_int_idx)
printf ( "Current Serial number [%d]\n",
serial_info_p->current_int_idx);
fprintf(file_p, "Current Serial number [%d]\n",
serial_info_p->current_int_idx);
Is there an easiest way to implement this to cut down the number of lines of code?
Edit: the C++ tag seems misleading, can someone remove it please? thanks :)
I use variadic macros to customize printf and friends.
I would write something like this:
#define tee(fp,fmt, ...) \
{ \
printf (fmt, __VA_ARGS__); \
fprintf (fp, fmt, __VA_ARGS__); \
}
(the name comes from the tee(1) utility)
Something like this allows you to add any number of output streams, and allows changing them at runtime simply by modifying the PrintTarget linked list.
/** gcc -Wall -o print_target print_target.c && ./print_target */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct PrintTarget* PrintTargetp;
void* xmalloc (size_t size);
PrintTargetp pntCreate (PrintTargetp head, FILE* target);
void pntDestroy (PrintTargetp list);
typedef struct PrintTarget
{
FILE* target;
PrintTargetp next;
} PrintTarget;
void myPrintf (PrintTargetp streams, char* format, ...)
{
va_list args;
va_start(args, format);
while (streams)
{
vfprintf(streams->target, format, args);
streams = streams->next;
}
va_end(args);
}
int main(void)
{
PrintTargetp streams = pntCreate(NULL, stdout);
streams = pntCreate(streams, fopen("somefile.txt", "a+")); //XXX IO errors?
myPrintf(streams, "blah blah blah...\n");
pntDestroy(streams);
return 0;
}
Here's a definition of auxiliary functions:
PrintTargetp pntCreate (PrintTargetp head, FILE* target)
{
PrintTargetp node = xmalloc(sizeof(PrintTarget));
node->target = target;
node->next = head;
return node;
}
void pntDestroy (PrintTargetp list)
{
while (list)
{
PrintTargetp next = list->next;
free(list);
list = next;
//XXX cycles?
//XXX close files?
}
}
void* xmalloc (size_t size)
{
void* p = malloc(size);
if (p == NULL)
{
fputs("malloc error\n", stderr);
abort();
}
return p;
}
You could also just pipe the output of your prorgam to the tee(1) command.
If you're writing a console application, you should be able to output to the screen (standard output) using something like:
fprintf(stdout, "Hello World\n");
This should enable you to move the code that prints your data to its own function, and to pass in a FILE* for it to print to. Then the function can print to the screen if you pass "stdout", or to a file if you pass in a different FILE*, e.g.:
void print_my_stuff(FILE* file) {
fprintf( file,"Starting serial number [%ld]\n", serial_info_p->start_int_idx);
fprintf(file, "Current Serial number [%d]\n", serial_info_p->current_int_idx);
.
.
.
}
Edit: I didn't notice you needed a C solution. I'll leave this answer for reference, but it obviously requires C++.
You could create a new stream class that sends the output to two streams. I found an implementation of this at http://www.cs.technion.ac.il/~imaman/programs/teestream.html. I haven't tried it, but it should work.
Here's the code from the link:
#include <iostream>
#include <fstream>
template<typename Elem, typename Traits = std::char_traits<Elem> >
struct basic_TeeStream : std::basic_ostream<Elem,Traits>
{
typedef std::basic_ostream<Elem,Traits> SuperType;
basic_TeeStream(std::ostream& o1, std::ostream& o2)
: SuperType(o1.rdbuf()), o1_(o1), o2_(o2) { }
basic_TeeStream& operator<<(SuperType& (__cdecl *manip)(SuperType& ))
{
o1_ << manip;
o2_ << manip;
return *this;
}
template<typename T>
basic_TeeStream& operator<<(const T& t)
{
o1_ << t;
o2_ << t;
return *this;
}
private:
std::ostream& o1_;
std::ostream& o2_;
};
typedef basic_TeeStream<char> TeeStream;
You would use it like this:
ofstream f("stackoverflow.txt");
TeeStream ts(std::cout, f);
ts << "Jon Skeet" << std::endl; // "Jon Skeet" is sent to TWO streams
I'd go more radical than what people have suggested so far, but maybe it is too much for you. (The 'inline' keyword is C99; you can omit it without much consequence if you code to C89.)
/*
** These could be omitted - unless you get still more radical and create
** the format strings at run-time, so you can adapt the %-24s to the
** longest tag you actually have. Plus, with the strings all here, when
** you change the length from 24 to 30, you are less likely to overlook one!
*/
static const char fmt_int[] = "%-24s [%d]\n";
static const char fmt_long[] = "%-24s [%ld]\n";
static const char fmt_str[] = "%-24s [%s]\n"; /* Plausible extra ... */
static inline void print_long(FILE *fp, const char *tag, long value)
{
fprintf(fp, fmt_long, tag, value);
}
static inline void print_int(FILE *fp, const char *tag, int value)
{
fprintf(fp, fmt_int, tag, value);
}
static inline void print_str(FILE *fp, const char *tag, const char *value)
{
fprintf(fp, fmt_str, tag, value);
}
static void dump_data(FILE *fp, const serial_info_t *info)
{
dump_long("Starting serial number", info->start_int_idx);
dump_int( "Current Serial number", info->current_int_idx);
/* ... and similar ... */
}
Then the calling code would call dump_data() once (with argument stdout) for options 1, 2, 3 and twice (once with stdout, once with file pointer for output file) for option 4.
If the number of parameters got truly huge (into the multiple hundreds), I'd even go as far as to consider a data structure which encoded type and offset information (offsetof from <stddef.h>) and pointers to functions and such like, so that there would be just a loop in dump_data() iterating over a structure which encodes all the necessary information.
You could also simplify life by using the same basic integer type (long in your example) for all the integer members of the data structure.
Fred Brooks in "Mythical Man Month" - a book well worth reading if you've not already done so, but make sure you read the Twentieth Anniversary edition - says in Chapter 9:
Show me your flowcharts [code] and conceal your tables [data structures], and I shall continue to be mystified. Show me your tables, and I won't usually need your flowcharts; they'll be obvious.
A table-driven version of this code could end up saving space, as well as frustration when having to change a hundred related functions in the same way whereas a simple change in the tabular data could have fixed the whole lot.
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
FILE *f = fopen("somefile.txt", "a+");
FILE *fp[] = { stdout, f };
int i = 0;
for (i = 0; i < ARRAY_LEN(fp); i++) {
fprintf(fp[i], "Starting serial number [%ld]\n", serial_info_p->start_int_idx);
fprintf(fp[i], "Current serial number [%ld]\n", serial_info_p->start_int_idx);
}
fclose(f);

Resources