Calling C from a common extension

U

Uno

I'm a little bit bored with clc lately and thought that I would amuse
myself by starting a new thread.

Let's say that instead of main being in C, main is in fortran, and would
therefore be the caller:

use iso_c_binding

call uname

end program
! gfortran -Wall -Wextra uname.o caller3.f90 -o out

The C part would look something like this, except that it doesn't have main:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
FILE *stream;
char *sys;
sys = malloc(100);
stream = popen("/bin/uname -snm", "r");
fscanf( stream, "%31[^\n]", sys );
printf("sys is %s\n", sys);
pclose(stream);
return 0;
}

My question here is what would I do if characters written to sys
exceeded a certain value. Can I write this as a VLA and pass the length
back to fortran?

Das regnet aber ganz doll, und gruss,
 
B

Ben Bacarisse

Uno said:
Let's say that instead of main being in C, main is in fortran, and
would therefore be the caller:

use iso_c_binding

call uname

end program
! gfortran -Wall -Wextra uname.o caller3.f90 -o out

The C part would look something like this, except that it doesn't have main:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
FILE *stream;
char *sys;
sys = malloc(100);
stream = popen("/bin/uname -snm", "r");
fscanf( stream, "%31[^\n]", sys );

31? I'd use fgets here, BTW.
printf("sys is %s\n", sys);
pclose(stream);
return 0;
}

My question here is what would I do if characters written to sys
exceeded a certain value.

That's not a very clear question. What is the objective? The simplest
thing to do is to ignore any characters that don't fit, if that is what
you are worried about. If that won't do, there are other options such a
using realloc to grow the buffer.
Can I write this as a VLA and pass the
length back to fortran?

I don't see any way that a VLA will help here.

The real questions are all Fortran ones about the best strategy for
"returning" a string to a Fortran program. I put that in quotes because
it may not involve a function's return value. Many implementations need
special care when passing allocated storage from C to Fortran. You need
to ask in a Fortran group.

<snip>
 
U

Uno

Ben said:
sys = malloc(100);
stream = popen("/bin/uname -snm", "r");
fscanf( stream, "%31[^\n]", sys );

31? I'd use fgets here, BTW.

If it were 22,000 instead which would be better?
That's not a very clear question. What is the objective? The simplest
thing to do is to ignore any characters that don't fit, if that is what
you are worried about. If that won't do, there are other options such a
using realloc to grow the buffer.

Then how does that work? If we start the buffer at 4 chars, it is
overridden. How does one detect this and create a buffer of size 2**n,
s.t. there is no overwrite?
The real questions are all Fortran ones about the best strategy for
"returning" a string to a Fortran program. I put that in quotes because
it may not involve a function's return value. Many implementations need
special care when passing allocated storage from C to Fortran. You need
to ask in a Fortran group.

Well, now I've asked in that fortran group. I'm sure we can work out
that side of it.
 
B

Ben Bacarisse

Uno said:
Ben said:
sys = malloc(100);
stream = popen("/bin/uname -snm", "r");
fscanf( stream, "%31[^\n]", sys );

31? I'd use fgets here, BTW.

If it were 22,000 instead which would be better?

16 is safer still! I am not sure I really get your point. One
advantage of fgets is that it makes it simple to use the same size in
both the malloc and the input:

(sys = malloc(size)) != NULL && fgets(sys, size, stream) != NULL

Doing so with a scanf format involves some trickery.
Then how does that work? If we start the buffer at 4 chars, it is
overridden. How does one detect this and create a buffer of size
2**n, s.t. there is no overwrite?

You need to count the characters as they come in. When you are about to
insert a character that does not fit, realloc the buffer. It is
possible to read in larger chunks, but it is more fiddly to get right.
In this example (reading the output of uname) it seems like overkill.
Well, now I've asked in that fortran group. I'm sure we can work out
that side of it.

It may turn out that the C end will be reasonably simple. You might
well be advised to let Fortran allocate the space and have the C
function simply fill it in.
 
U

Uno

Ben said:
Uno said:
Ben said:
sys = malloc(100);
stream = popen("/bin/uname -snm", "r");
fscanf( stream, "%31[^\n]", sys );
31? I'd use fgets here, BTW.
If it were 22,000 instead which would be better?

16 is safer still! I am not sure I really get your point. One
advantage of fgets is that it makes it simple to use the same size in
both the malloc and the input:

(sys = malloc(size)) != NULL && fgets(sys, size, stream) != NULL

Doing so with a scanf format involves some trickery.
Then how does that work? If we start the buffer at 4 chars, it is
overridden. How does one detect this and create a buffer of size
2**n, s.t. there is no overwrite?

You need to count the characters as they come in. When you are about to
insert a character that does not fit, realloc the buffer. It is
possible to read in larger chunks, but it is more fiddly to get right.
In this example (reading the output of uname) it seems like overkill.
Well, now I've asked in that fortran group. I'm sure we can work out
that side of it.

It may turn out that the C end will be reasonably simple. You might
well be advised to let Fortran allocate the space and have the C
function simply fill it in.


I was thinking that a reasonable step in the realloc() would be
multiples of 16.


stream = popen("/bin/uname -snm", "r");
fgets(sys,len,stream); /* len is the allocated length */
n=strlen(sys)-1;
if(n>=0 && sys[n]=='\n') sys[n]=0;
else (do something here)

printf("n is %d, and sys is %s\n", n, sys);
pclose(stream);


I don't attribute with this source, as I don't see how it would catch
the over-run character by character.

Do you agree?
 
B

Ben Bacarisse

I was thinking that a reasonable step in the realloc() would be
multiples of 16.


stream = popen("/bin/uname -snm", "r");
fgets(sys,len,stream); /* len is the allocated length */
n=strlen(sys)-1;
if(n>=0 && sys[n]=='\n') sys[n]=0;
else (do something here)

printf("n is %d, and sys is %s\n", n, sys);
pclose(stream);


I don't attribute with this source, as I don't see how it would catch
the over-run character by character.

Do you agree?

I'm sorry, but I don't know what you mean.

The main problem when using fgets to read long lines is determining when
a partial line has been read. This can be done marking the last space
in the buffer with something other than a null byte and observing if it
is overwritten. If it is, and the previous character is not \n, we know
that a partial line has been seen.

The normal strategy for realloc is to double the buffer size every time
(though lots of other patterns are quite acceptable). Putting these two
together gives something like this loop:

char *rest_of_line, *line = 0;
size_t space_left, buffer_size = 0;
do {
size_t new_size = buffer_size ? buffer_size * 2 : 16;
char *new_line = realloc(line, new_size);
if (new_line == 0)
break;
line = new_line;
rest_of_line = line + (buffer_size ? buffer_size - 1 : 0);
space_left = new_size - (rest_of_line - line);
buffer_size = new_size;
rest_of_line[space_left - 1] = !0;
} while (fgets(rest_of_line, space_left, fp) &&
rest_of_line[space_left - 1] == 0 &&
rest_of_line[space_left - 2] != '\n');

This is complex enough that I'm sure I've got something wrong though I
won't say "not tested" because I always try to test posted code. I hope
this helps.
 
U

Uno

Ben said:
I was thinking that a reasonable step in the realloc() would be
multiples of 16.


stream = popen("/bin/uname -snm", "r");
fgets(sys,len,stream); /* len is the allocated length */
n=strlen(sys)-1;
if(n>=0 && sys[n]=='\n') sys[n]=0;
else (do something here)

printf("n is %d, and sys is %s\n", n, sys);
pclose(stream);


I don't attribute with this source, as I don't see how it would catch
the over-run character by character.

Do you agree?

I'm sorry, but I don't know what you mean.

The main problem when using fgets to read long lines is determining when
a partial line has been read. This can be done marking the last space
in the buffer with something other than a null byte and observing if it
is overwritten. If it is, and the previous character is not \n, we know
that a partial line has been seen.

The normal strategy for realloc is to double the buffer size every time
(though lots of other patterns are quite acceptable). Putting these two
together gives something like this loop:

char *rest_of_line, *line = 0;
size_t space_left, buffer_size = 0;
do {
size_t new_size = buffer_size ? buffer_size * 2 : 16;
char *new_line = realloc(line, new_size);
if (new_line == 0)
break;
line = new_line;
rest_of_line = line + (buffer_size ? buffer_size - 1 : 0);
space_left = new_size - (rest_of_line - line);
buffer_size = new_size;
rest_of_line[space_left - 1] = !0;
} while (fgets(rest_of_line, space_left, fp) &&
rest_of_line[space_left - 1] == 0 &&
rest_of_line[space_left - 2] != '\n');

This is complex enough that I'm sure I've got something wrong though I
won't say "not tested" because I always try to test posted code. I hope
this helps.

Well of course it helps, Ben, but the above is a little more than I'm
interesting in right now. It's heavy with C, and the do-while control
always confuses the bejesus out of me.

I'm trying to adapt Heathfield's published advice on this topic:


$ gcc -Wall -Wextra b1.c -o out
b1.c: In function ‘main’:
b1.c:19: warning: implicit declaration of function ‘strlen’
b1.c:19: warning: incompatible implicit declaration of built-in function
‘strlen’
b1.c:19: error: ‘s’ undeclared (first use in this function)
b1.c:19: error: (Each undeclared identifier is reported only once
b1.c:19: error: for each function it appears in.)
b1.c:23: error: ‘p’ undeclared (first use in this function)
$ cat b1.c

// snippet from chp 8 c unleashed
// all rights reserved, the eton company
// 731 Sedge Way
// the UK

#include <stdio.h>
#include <stdlib.h>

int main (void)

{



/* Listing 8.4 */
static char *buffer = NULL;
static size_t bufsize = 0;
size_t len = strlen(s) + 1;

while(len > bufsize)
{
p = realloc(buffer, bufsize * 2);
if(p != NULL)
{
bufsize *= 2;
buffer = p;
}
else
{
p = realloc(buffer, len);
if(p != NULL)
{
bufsize = len;
buffer = p;
}
else
{
/* What to do next depends very heavily
* on the nature of the application.
*/
printf("What we need here is a design decision!\n");
}
}
}

return 0;
}

// gcc -Wall -Wextra b1.c -o out
$

I see a lot of shortcomings with this source. As a journeyman
carpenter, I've learned to recognize the corners I can knock down with
the detail sander, and those I can't.

Ben, do you agree with me that 's' is what is getting passed to and fro?
The fortran caller will declare the pointer. Caller cannot know how
large the output is.

So instead of thinking of the passed command from a fortran string as
'uname', instead consider it to be ls /etc or ls etc. I have
post-windows-can't see-one-slash from the other syndrome. Please work
with me.

I'm asking for advise on design printf("What we need here is a design
decision!\n");decisions.
 
B

Ben Bacarisse

Uno said:
Ben Bacarisse wrote:
Well of course it helps, Ben, but the above is a little more than I'm
interesting in right now. It's heavy with C, and the do-while control
always confuses the bejesus out of me.

I'm trying to adapt Heathfield's published advice on this topic:

$ gcc -Wall -Wextra b1.c -o out
b1.c: In function ‘main’:
b1.c:19: warning: implicit declaration of function ‘strlen’
b1.c:19: warning: incompatible implicit declaration of built-in
function ‘strlen’
b1.c:19: error: ‘s’ undeclared (first use in this function)
b1.c:19: error: (Each undeclared identifier is reported only once
b1.c:19: error: for each function it appears in.)
b1.c:23: error: ‘p’ undeclared (first use in this function)

I seems unlikely that Richard Heathfield published code with these
errors. Unless you can fix them, your question below in unanswerable
(at least by me).

I see a lot of shortcomings with this source. As a journeyman
carpenter, I've learned to recognize the corners I can knock down with
the detail sander, and those I can't.

Surely the fact that is does not compile is more than a "shortcoming"?
Ben, do you agree with me that 's' is what is getting passed to and
fro? The fortran caller will declare the pointer. Caller cannot know
how large the output is.

No, but neither can I disagree. s is undeclared along with p and
nothing is getting passed to a fro so I can't tell what you intend.

Your questions in the Fortran group should have ended up with what you
need to write in Fortran to call a C function that can return an
allocated string (the fact that it is allocated may be significant --
don't forget to tell them this). They will also tell you what the C
function prototype is for this function. These are all Fortran
questions that I can't answer.

A subsidiary question is how to free the storage. Fortran might be able
to do that directly, or it may require another call to a C function.

Once you have that, people here might help with the C. You will also
have some useful constraints: the exact function type that needs to be
written.

I'm asking for advise on design printf("What we need here is a design
decision!\n");decisions.

You've not said what the overall objective is so no one can help with
the design. I have to assume that you really need an allocated string
of arbitrary length to be passed back to a Fortran program because that
is now what you are asking about even though there may be much better
ways to solve the problem or design the program.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top