Passing an array from FORTRAN to C then passing it within C andReturning it to FORTRAN

Discussion in 'C Programming' started by deadpickle, Nov 6, 2010.

  1. deadpickle

    deadpickle Guest

    Im trying to read an xml file. What happens is that I have a FORTRAN
    program that calls a C library that uses libxml2 to read an xml file.
    The passed array (from FORTRAN) needs to be passed to several
    functions within the C program before being returned to the FORTRAN
    program. My question is how do I pass the array within C and how do I
    return the array to FORTRAN.

    The code:
    #include <stdio.h>
    #include <libxml/parser.h>
    #include <libxml/tree.h>
    #include <libxml/xmlstring.h>
    #include <libxml/xpath.h>

    #ifdef LIBXML_TREE_ENABLED

    static void get_item_values(xmlNode * b_node, int *max, double
    ellipseinfo[5][*max], int mark) {

    xmlNode *sub_node = NULL;
    xmlChar *attrname;
    int cluster = 0;

    for (sub_node = b_node; sub_node; sub_node = sub_node->next) {
    if (sub_node->type == XML_ELEMENT_NODE) {
    if ( !xmlStrcmp(sub_node->name, "item") ) {
    attrname = xmlGetNoNsProp(sub_node,"value");
    double value = xmlXPathCastStringToNumber(attrname);
    ellipseinfo[mark][cluster] = value;
    cluster++;
    }
    }
    }
    }

    static void print_element_names(xmlNode * a_node, double *ellipseinfo)
    {
    xmlNode *cur_node = NULL;
    xmlChar *attrname;

    for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
    if (cur_node->type == XML_ELEMENT_NODE) {
    if ( !xmlStrcmp(cur_node->name, "datacolumn") ) {
    attrname = xmlGetNoNsProp(cur_node, (const xmlChar *) "name");
    if (xmlStrEqual(attrname, (const xmlChar *) "Latitude")) {
    int mark = 0;
    get_item_values(cur_node->children, max, ellipseinfo,mark);
    }
    else if (xmlStrEqual(attrname, (const xmlChar *) "Longitude")) {
    int mark = 1;
    get_item_values(cur_node->children, max, ellipseinfo[5]
    [*max],mark);
    }
    if (xmlStrEqual(attrname, (const xmlChar *) "LatRadius")) {
    int mark = 2;
    get_item_values(cur_node->children, max, ellipseinfo[5]
    [*max],mark);
    }
    if (xmlStrEqual(attrname, (const xmlChar *) "LonRadius")) {
    int mark = 3;
    get_item_values(cur_node->children, max, ellipseinfo[5]
    [*max],mark);
    }
    if (xmlStrEqual(attrname, (const xmlChar *) "Orientation")) {
    int mark = 4;
    get_item_values(cur_node->children, max, ellipseinfo[5]
    [*max],mark);
    }
    }
    }
    print_element_names(cur_node->children,max,ellipseinfo[5][*max]);
    }
    }

    int
    read_xml(int *max, char *filename, double ellipseinfo[5][*max])
    {
    xmlDoc *doc = NULL;
    xmlNode *root_element = NULL;

    LIBXML_TEST_VERSION

    doc = xmlReadFile(filename, NULL, 0);

    if (doc == NULL) {
    printf("error: could not parse file %s\n", filename);
    }

    root_element = xmlDocGetRootElement(doc);

    print_element_names(root_element, &ellipseinfo[5][max]);

    xmlFreeDoc(doc);

    xmlCleanupParser();

    return;
    }
    #else
    int main(void) {
    fprintf(stderr, "Tree support not compiled in\n");
    exit(1);
    }
    #endif
     
    deadpickle, Nov 6, 2010
    #1
    1. Advertising

  2. Re: Passing an array from FORTRAN to C then passing it within C and Returning it to FORTRAN

    deadpickle <> wrote:
    > Im trying to read an xml file. What happens is that I have a FORTRAN
    > program that calls a C library that uses libxml2 to read an xml file.
    > The passed array (from FORTRAN) needs to be passed to several
    > functions within the C program before being returned to the FORTRAN
    > program. My question is how do I pass the array within C and how do I
    > return the array to FORTRAN.


    First of all, in C you can't "pass" arrays, you only can pass
    pointers to arrays. And the syntax for two-dimensional arrays
    may obscur this a bit.

    Arrays (be they one- or multi-dimensional) in C are stored
    continously in memory, just as they are in FORTRAN. But the
    important difference is the layout of the elements in me-
    mory - in FORTRAN the elements of the first column come
    first, then the elements of the second column etc. while
    in C the elements of the first row are stored first, then
    the elements of the second row etc. It looks as if you
    are aware of that and thus some of the strange stuff you
    are trying to do;-)

    Lets start with the function which I guess you are trying to
    call from FORTRAN:

    > int
    > read_xml(int *max, char *filename, double ellipseinfo[5][*max])


    My guess is that under FORTRAN 'ellipseinfo' is a two-dimen-
    sional array with 'max' columns and 5 elements per row. If
    that assumption is wrong you can probably forget most of the
    rest I am going to write.

    Now this function signature may lead to trouble. At least
    with C89 if you pass around two-dimensional arrays you have
    to have a compile time constant at least for all the lower
    dimension. Only the size of the highest dimension can be left
    unspecified (but can't be a variable). Thus it would be ok to
    have e.g.

    double ellipseinfo[ ][ 5 ]

    to tell the compiler it's a two-dimensional array with 5
    elements per row and an unspecified number of columns but

    double ellipseinfo[ 5 ][ *max ]

    won't work with C89 since there were no variable length arrays.
    I will start with describing how to do it in C89 and then add a
    version that requires C99 (and which then is more similar to your
    original version).

    > {
    > xmlDoc *doc = NULL;
    > xmlNode *root_element = NULL;


    These initialization are ok but unnecessary.

    >
    > LIBXML_TEST_VERSION
    >
    > doc = xmlReadFile(filename, NULL, 0);
    >
    > if (doc == NULL) {
    > printf("error: could not parse file %s\n", filename);


    Shouldn't you return here? Also, error messages are typically
    written to stderr instead of stdout, which is what printf()
    is printing to. Thus it might be better to use

    > fprintf(stderr, "error: could not parse file %s\n", filename);


    > }
    >
    > root_element = xmlDocGetRootElement(doc);


    Shouldn't you also check if this function call succeeded?

    > print_element_names(root_element, &ellipseinfo[5][max]);


    Now, assuming that the function signature had been ok this
    would pass a pointer to the element with indices 5 and
    'max' as the third argument to the function. Several pro-
    blems: 'max' is a pointer so you would need at least '*max'
    here as the second index. And the '&' in front of the array
    is wrong. Finally, even under C89 you would have to pass
    the value of '*max' first and then the array itself (without
    any indices).

    > xmlFreeDoc(doc);
    >
    > xmlCleanupParser();
    >
    > return;
    > }


    Now here's a version that may be better suited when you don't
    have a C99 compiler. Note that the function accepts as the third
    element a pointer to the array, we're going to treat this as a
    flat array and deal with indexing later on manually.

    void
    read_xml( int * max,
    char * filename,
    double * ellipseinfo )
    {
    xmlDoc *doc;
    xmlNode *root_element;

    LIBXML_TEST_VERSION

    doc = xmlReadFile( filename, NULL, 0 );

    if ( doc == NULL )
    {
    fprintf( stderr, "error: could not parse file %s\n", filename );
    return;
    }

    root_element = xmlDocGetRootElement( doc );

    if ( root_element == NULL )
    {
    fprintf( stderr, "error: could not get root element\n" );
    return;
    }

    print_element_names( root_element, ellipseinfo, *max );

    xmlFreeDoc( doc );
    xmlCleanupParser( );
    }

    Note that the call of print_element_names() now has an additional
    argumenst, the maximum number of rows in the array. It is needed
    to make sure we get the indexing right and to avoid writing past
    the end of the array.The number of columns is well-known to be 5,
    so we don't need to pass that also.

    The print_element_names() function seemed too complicated to
    me, so I changed it and added another function:

    static void
    print_element_names( xmlNode * node,
    double * ellipseinfo,
    int max_rows )
    {
    while ( node )
    {
    if ( node->type == XML_ELEMENT_NODE )
    read_datacolumn( node, ellipseinfo, max_rows );
    else
    print_element_names( node->children, ellipseinfo, max_rows );

    node = node->next;
    }
    }

    As far as I understand it a node either has type XML_ELEMENT_NODE,
    in which case you try to read in data for one of the types of data
    columns, or it's a node that contains data columns as children, so
    you recurse.

    Let's continue with the function for reading a column;

    static void
    read_datacolumn( xmlNode * node,
    double * ellipseinfo,
    int max_rows )
    {
    xmlChar *attrname;
    int index;
    const xmlChar * keys[ ] = { ( const xmlChar * ) "Latitude",
    ( const xmlChar * ) "Longitude",
    ( const xmlChar * ) "LatRadius",
    ( const xmlChar * ) "LonRadius",
    ( const xmlChar * ) "Orientation" };

    /* This should be a 'datacolumn' node with a 'name' attribute */

    if ( ! xmlStrcmp( node->name, ( const xmlChar * ) "datacolumn" )
    || ! ( attrname = xmlGetNoNsProp( node, ( const xmlChar * ) "name" ) ) )
    return;

    /* The name must be one of those from the 'keys' array - if it is
    we read the nodes child with the data */

    for ( index = 0; index < 5; index++ )
    if ( xmlStrEqual( attrname, keys[ index ] ) )
    {
    get_item_values( node->children,
    ellipseinfo + index * max_rows, max_rows );
    return;
    }
    }

    I find iterating over an array of keywords easier to read than
    a lot if if-else constructs. And the intent in your code seemed
    to be to find a certain key and then fill the corresponding co-
    lumn in all rows with the data for this keyword. Due to the layout
    of the array in FORTRAN all data for a keyword are in continuousd
    memory locations, i.e. in memory the array looks somewhat like
    this:

    Lat0 Lat1 Lat2 Lat3 ..... LatMax
    Lon0 Lon1 Lon2 Lon3 ..... LonMax
    LatR0 LatR1 LatR2 LatR3 ..... LatRMax
    LonR0 LonR1 LonR2 LonR3 ..... LonRMax
    Ori0 Ori1 Ori2 Ori3 ..... OriMax

    each line with 'max_rows' elements. With index == 0 one wants
    to read in the all the latitudes, so we pass 'ellipseinfo'
    directly to the get_item_values() function. With index == 1
    we want to read the longitudes, so we pass the address of
    where the data for longitudes belong to, i.e. the 'max_rows'-th
    element, 'ellipseinfo + max_rows' etc. (you could replace

    ellipseinfo + max_rows

    with

    &ellipseinfo[ max_rows ]

    which is exactly the same.

    Now the function that does the actual reading becomes:

    static void
    get_item_values( xmlNode * node,
    double * dest,
    int max_rows )
    {
    int cur_row = 0;

    /* Keep reading until we either have as many items as fit into the
    array or the end of the data column has been reached */

    while ( cur_row < max_rows && node )
    {
    xmlChar *attrname;

    if ( node->type != XML_ELEMENT_NODE
    || ! xmlStrcmp( node->name, ( const xmlChar * ) "item" )
    || ! ( attrname =
    xmlGetNoNsProp( node, ( const xmlChar * ) "value" ) ) )
    return;

    dest[ cur_row++ ] = xmlXPathCastStringToNumber( attrname );
    node = node->next;
    }
    }

    This assumes that the XML file has a structure somewhat like this:

    <datacolumn name="Latitude">
    <item value="21.9408"/>
    <item value="19.7698"/>
    <item value="43.5629"/>
    </datacolumn>
    <datacolumn name="Longitude">
    <item value="13.1234"/>
    <item value="21.6229"/>
    <item value="56.2322"/>
    </datacolumn>
    ....

    And here's a version that looks more like yours but requires a
    C99 compliant compiler:

    static void
    get_item_values( xmlNode * node,
    int max_rows,
    double dest[ max_rows ] )
    {
    int cur_row = 0;

    while ( cur_row < max_rows && node )
    {
    xmlChar *attrname;

    if ( node->type != XML_ELEMENT_NODE
    || ! xmlStrcmp( node->name, ( const xmlChar * ) "item" )
    || ! ( attrname =
    xmlGetNoNsProp( node, ( const xmlChar * ) "value" ) ) )
    return;

    dest[ cur_row++ ] = xmlXPathCastStringToNumber( attrname );
    node = node->next;
    }
    }

    static void
    read_datacolumn( xmlNode * node,
    int max_rows,
    double ellipseinfo[ 5 ][ max_rows ] )
    {
    xmlChar *attrname;
    int index;
    const xmlChar * keys[ ] = { ( const xmlChar * ) "Latitude",
    ( const xmlChar * ) "Longitude",
    ( const xmlChar * ) "LatRadius",
    ( const xmlChar * ) "LonRadius",
    ( const xmlChar * ) "Orientation" };

    if ( ! xmlStrcmp( node->name, ( const xmlChar * ) "datacolumn" )
    || ! ( attrname = xmlGetNoNsProp( node, ( const xmlChar * ) "name" ) ) )
    return;

    for ( index = 0; index < 5; index++ )
    if ( xmlStrEqual( attrname, keys[ index ] ) )
    {
    get_item_values( node->children, max_rows, ellipseinfo[ index ] );
    return;
    }
    }

    static void
    print_element_names( xmlNode * node,
    int max_rows,
    double ellipseinfo[ 5 ][ max_rows ] )
    {
    while ( node )
    {
    if ( node->type == XML_ELEMENT_NODE )
    read_datacolumn( node, max_rows, ellipseinfo );
    else
    print_element_names( node->children, max_rows, ellipseinfo );

    node = node->next;
    }
    }

    void
    read_xml( int * max,
    char * filename,
    double ellipseinfo[ 5 ][ *max ] )
    {
    xmlDoc *doc;
    xmlNode *root_element;

    LIBXML_TEST_VERSION

    doc = xmlReadFile( filename, NULL, 0 );

    if ( doc == NULL ) {
    fprintf( stderr, "error: could not parse file %s\n", filename );
    return;
    }

    root_element = xmlDocGetRootElement( doc );

    if ( root_element == NULL ) {
    fprintf( stderr, "error: could not get root element\n" );
    return;
    }

    print_element_names( root_element, *max, ellipseinfo );

    xmlFreeDoc( doc );
    xmlCleanupParser( );
    }

    With this you should be able to call the read_xml() function with
    three arguments, first the number of rows, then the file name and
    finally the array you want to get filled in. But keep in mind that
    this is all untested since I don't have an XML file that I could
    use for testing. And there's also a bit more of error checking
    missing as well as returning an indication if reading worked out
    correctly...
    Regards, Jens
    --
    \ Jens Thoms Toerring ___
    \__________________________ http://toerring.de
     
    Jens Thoms Toerring, Nov 7, 2010
    #2
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Sam
    Replies:
    1
    Views:
    804
    E. Robert Tisdale
    Jun 30, 2005
  2. Luna Moon
    Replies:
    9
    Views:
    623
    Guest
    Sep 4, 2007
  3. ssylee
    Replies:
    4
    Views:
    522
    Thad Smith
    Jan 1, 2008
  4. foice
    Replies:
    4
    Views:
    1,089
    James Kanze
    Oct 14, 2010
  5. Replies:
    0
    Views:
    313
Loading...

Share This Page