Using a C string like a FILE*
Asked Answered
L

6

25

I have a C function that reads a stream of characters from a FILE*.

How might I create a FILE* from a string in this situation?

Edit:

I think my original post may have been misleading. I want to create a FILE* from a literal string value, so that the resulting FILE* would behave as though there really was a file somewhere that contains the string without actually creating a file.

The following is what I would like to do:

void parse(FILE* f, Element* result);

int main(int argc, char** argv){
    FILE* f = mysteryFunc("hello world!");
    Element result;
    parse(f,&result);
}
Lightfingered answered 8/7, 2012 at 8:17 Comment(2)
You mean like calling "FILE* file = fopen("somefilename.txt", "r");"Erroneous
C's standard library doesn't provide that capability.Bilabial
S
29

Standard C provides no such facility, but POSIX defines the fmemopen() function that does exactly what you want.

Sisneros answered 8/7, 2012 at 8:29 Comment(1)
libcurl needs a FILE* and so I used this trick to create a FILE* from a std::string and then called successfully curl_easy_setopt(curl_handle, CURLOPT_READDATA, file_pointer);.Moose
A
10

Unfortunately, C's standard library doesn't provide this functionality; but there are a few ways to get around it:

  • Create a temporary file, write your string to it, then open it for reading. If you've got POSIX, gettempnam will choose a unique name for you

  • The other option (again for POSIX only) is to fork a new process, whose job will be to write the string to a pipe, while you fdopen the other end to obtain a FILE* for your function.

  • As @KeithThompson pointed out, fmemopen does exactily what you want, so if you have POSIX, use that. On any other platform, (unless you can find the platform-equivalent), you'll need a temporary file.

Auxin answered 8/7, 2012 at 8:27 Comment(0)
P
3

Last time I had this kind of problem I actually created a pipe, launched a thread, and used the thread to write the data into the pipe... you would have to look into operating system calls, though.

There are probably other ways, like creating a memory mapped file, but I was looking for something that just worked without a lot of work and research.

EDIT: you can, of course, change the problem to "how do I find a nice temporary filename". Then you could write the data to a file, and read it back in :-)

Possibility answered 8/7, 2012 at 8:24 Comment(0)
T
2
pid_t pid;
int pipeIDs[2];

if (pipe (pipeIDs)) {
   fprintf (stderr, "ERROR, cannot create pipe.\n");
   return EXIT_FAILURE;
} 

pid = fork ();
if (pid == (pid_t) 0) {
   /* Write to PIPE in this THREAD  */

   FILE * file = fdopen( pipe[1], 'w');

   fprintf( file, "Hello world");
   return EXIT_SUCCESS;

} else if (pid < (pid_t) 0) {
  fprintf (stderr, "ERROR, cannot create thread.\n");
  return EXIT_FAILURE;
}


FILE* myFile = fdopen(pipe[0], 'r');

// DONE! You can read the string from myFile

....  .....
Taber answered 8/7, 2012 at 8:44 Comment(0)
M
2

Maybe you can change the code a little bit to receive a custom handle.

void parse(my_handle *h, Element *result)
{
  // read from handle and process
  // call h->read instead of fread
}

and defines the handle like this:

struct my_handle
{
    // wrapper for fread or something
    int (*read)(struct my_handle *h, char *buffer, int readsize);
    // maybe some more methods you need
};

implement your FILE* wrapper

struct my_file_handle
{
    struct my_handle base;
    FILE *fp;
};

int read_from_file(struct my_handle *h, char *buffer, int readsize)
{
    return fread(buffer, 1, readsize, ((my_file_handle*)h)->fp);
}

// function to init the FILE* wrapper
void init_my_file_handle(struct my_file_handle *h, FILE *fp)
{
    h->base.read = read_from_file;
    h->fp = fp;
}

Now, implement your string reader

struct my_string_handle
{
    struct my_handle base;
    // string buffer, size, and current position
    const char *buffer;
    int size;
    int position;
};

// string reader
int read_from_string(struct my_handle *h, char *buffer, int readsize)
{
    // implement it yourself. It's easy.
}

// create string reader handle
void init_my_string_handle(struct my_string_handle *h, const char *str, int strsize)
{
    // i think you know how to init it now.
}

//////////////////////////////////////////////////

And now, you can simply send a handle to your parse function. The function doesn't care where the data comes from, it can even read data from network!

Monostome answered 8/7, 2012 at 13:2 Comment(0)
P
0

This is an old question, but deserves a better answer.

C has always had the ability to read and write strings using the formatted I/O functions. You just need to keep track of where you are in the string!

Reading a string

To read a string you need the %n format string specifier, which returns the number of bytes read each time we use sscanf(). Here is a simple example with a loop:

#include <stdio.h>

int main(void)
{
  const char * s = "2 3 5 7";
  int n = 0;
  int value;
  while (sscanf( s+=n, "%d%n", &value, &n ) == 1)
  {
    printf( "value = %d\n", value );
  }
}

Another way to have written that loop would be:

for (int value, n;  sscanf( s, "%d%n", &value, &n ) == 1;  s += n)

Whichever floats your boat best. The loop is not important.

What is important is that we increment the value of s after every read.

  • Notice how we don’t bother to remember original value of s in this example? If it matters, use a temporary, as we do in our next example.

  • It is also important that we stop reading when sscanf fails. This is the normal usage for the scanf family of functions.

Writing a string

In this case sprintf() helps us by directly returning the number of bytes written. Here’s a simple example of building a string using several formatted outputs:

#include <stdio.h>

int main(void)
{
  char s[100] = {0};

  char * p = s;
  p += sprintf( p, "%d %s", 3, "three" );
  p += sprintf( p, "; " );
  p += sprintf( p, "%.2f %s", 3.141592, "pi" );
  *p = '\0'; // don’t forget it!
  
  printf( "s = \"%s\"\n", s );
  printf( "number of bytes written = %zu = %zu\n", p-s, strlen(s) );
}

The important points:

  • This time we do not want to clobber s (and in this particular example couldn’t even if we wanted to), so we use a helper p.
  • We cannot forget to manually add that null-terminator. (Which should make sense, since we are manually building the string.)
  • BUFFER OVERFLOW IS POSSIBLE

That last point is significant, and a usual concern when building strings in C. As always, whether using strcat() or sprintf(), always make sure you have enough room to append everything you intend to write to your string!

Don’t use %n when writing

We could have used the %n specifier as well, but then we hit a cross-platform issue with MSVC: Microsoft targets %n and the printf() family of functions as a security issue. Whether or not you accept Microsoft’s reasoning you must live with the way things are.

If you are undeterred, you can add a little platform-specific code and use it anyway:

#ifdef _WIN32
_set_printf_count_output( 1 );
#endif

int n;
printf( "Hello%n world!", &n );

True FILE * I/O

Notice that we aren’t touching actual FILE * I/O functions, like fgetc()? If you need that, then you need an actual file.

As mentioned above, use tmpfile() to open a temporary read/write file and use the usual FILE * I/O functions on it. Our read-a-string example could be re-written as:

#include <stdio.h>

int main(void)
{
  FILE * f = tmpfile();
  if (!f) return 1;
  
  fprintf( f, "2 3 5 7" );
  rewind( f );
  
  int value;
  while (fscanf( f, "%d", &value ) == 1)
  {
    printf( "value = %d\n", value );
  }
  
  fclose( f );
}

This works just fine. Remember that tmpfile() might not give you an actual file on disk with a filename. You don’t need that anyway. In other words, it may very well be an in-memory buffer provided by the OS... which is kind of what this thread is about anyway, right?

Hopefully these options will give a deeper insight into the C standard I/O functions and their use. Next time you need to read or build a formatted string in parts, you will have a better grasp of the tools already provided for you.

Ponytail answered 19/9, 2022 at 6:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.