Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

C Header file for Fortran DLL using BIND(C)/cdecl and functions returning structs

dwwade
Beginner
2,336 Views

Okay after working on it over the weekend, I realize that I'm at the limit of my understanding of how to make things interoperable with C specifically in reference to MATLAB.  My familiarity with C is passing and this involves things I've never tried to do in C.

I have a series of functions that I have made that are all bind(c) and have a specific user defined type for the return argument.  I want the return argument to be flexible, as I want it to be able to return whatever it is the library wants to give it as well as be able to return the error messages/status that are returned.

Here is an example of the user defined type:

[fortran]type, bind(c) :: c_return
  integer(c_int)                :: ncolumns = 0
  integer(c_int)                :: nrows = 0
  integer(c_int)                :: lstring = 0
  integer(c_int)                :: status = 0
  type(c_ptr)                   :: valueptr
endtype c_return[/fortran]

The idea is to locally control the valueptr such that all memory gets freed after MATLAB is done with the command.

Here is an example of the function declaration that I want.

[fortran]function c_bodyn(jd, is, ip) bind(c, name='c_bodyn')
!DEC$ ATTRIBUTES DLLEXPORT :: c_bodyn
  !
  !
  ! Passed Parameters
  real(c_double), intent(in) :: jd  ! Julian date
  real(c_double), intent(in) :: is  ! satellite integer
  real(c_double), intent(in) :: ip  ! master central body integer
 
  type(c_return)     :: c_bodyn [/fortran]

I have hacked around trying to get a C header file that would be compatible, and haven't been successful.  If someone can help me I'd really appreciate it.

Here is one of the many different header files that I have tried for these:

[cpp]#define EXPORTED_FUNCTION __declspec(dllexport)
#ifdef __cplusplus
extern "C" {
#endif

    typedef struct
{
    int    ncolumns, nrows, lstring, status;
    void * valueptr;
} c_return;
    extern "C" __declspec struct c_return c_bodyn(double jd, int is, int ip);

#ifdef __cplusplus
}
#endif[/cpp]

0 Kudos
8 Replies
IanH
Honored Contributor III
2,336 Views

Your C declaration says that you are passing the parameters by value (double jd, as opposed to double *jd or perhaps double &jd in C++).  Your Fortran declaration says you are passing the arguments by reference (no VALUE attribute).  Never the twain shall meet.

Pick an appropriate parameter/argument passing approach and adjust either the C code or the Fortran declaration to suit (adding VALUE to the Fortran argument declarations would probably be the easiest and most "natural" approach).

0 Kudos
dwwade
Beginner
2,336 Views

So I can see jd, is and ip all requiring that.  But how do I fix the returned struct since the compiler isn't very friendly with using value for c_bodyn or do I need to return a different variable?

0 Kudos
IanH
Honored Contributor III
2,336 Views

Is there a stray __declspec in your C function prototype?  Perhaps the declaration should be:

[cpp]EXPORTED_FUNCTION c_return c_bodyn(double jd, int is, int ip);[/cpp]

given the declaration is already inside an extern "C" block; you've got a #define for the "please export this symbol in the DLL" extension earlier in the code and the struct is a typedef (there's a difference between C/C++ here).  If that's not it, then be more specific about why you think you need to fix something (Are you getting error messages?  If so, what are they, etc...?).

(Personally (i.e. my personal preference - I know others have different views) I don't like littering my code with __declspec and !DEC$ unless I absolutely have to.  Consequently, for DLLEXPORT I prefer to list the exported names of the relevant procedures in the EXPORTS section of a separate linker .DEF file.  This allows me to keep the source code as standard-clean as possible.)

0 Kudos
dwwade
Beginner
2,336 Views

At one time, I had "EXPORTED_FUNCTION" in there, but it went by the wayside.  Sorry this version is really mucked up.  Let me see if this is right.

[cpp]#define EXPORTED_FUNCTION __declspec(dllexport)
#ifdef __cplusplus
extern "C" {
#endif

    typedef struct
{
    int    ncolumns, nrows, lstring, status;
    void * valueptr;
} c_return;
    EXPORTED_FUNCTION c_return c_bodyn(double jd, int is, int ip);

#ifdef __cplusplus
}
#endif[/cpp]

I get this error now.  I had this at one point this weekend and tried dressing up the function in fortran, but I just don't get it.  I also tried to change the dllexport to dllimport.  I can't really tell which belongs in the header file that is trying to pull in a fortran DLL.

test.obj : error LNK2019: unresolved external symbol _c_bodyn referenced in function _main

I noticed in the HDF5 files the fortrandll.def files, but I didn't know how to read it or figure out how it applied.

0 Kudos
IanH
Honored Contributor III
2,336 Views

Apologies - I may have incoherently confabulated export/import and your use case.  You are right - if the C header file is to describe to clients of the DLL (Matlab perhaps (if you are using this DLL in Matlab I prefer to use a prototype m file because it gives me more control)) then the __declspec(dllexport) in the C header is erroneous (it should be __declspec(dllimport)), - though more likely it is completely superfluous in this specific case - Matlab just wants to parse out the function's arguments, calling convention etc - you don't actually compile the C code.

The error message is because the linker cannot find your Fortran procedure. 

Is this a message that you get when building your Fortran DLL or when building something that uses the DLL? 

Based on your description - I think the former is all that you should be doing.  If so, I am a little unsure what's gone wrong - so show the command line commands used/Visual Studio build log for building the Fortran DLL.

If the latter (which woudn't quite fit in with your original description - Matlab can use your Fortran DLL without needing to build something else), what are you building?  Are you doing something with mex files etc?  Typically when a DLL is built the linker writes a so called export library (whatever_your_dll_was_called.lib) that you need to provide to the linker when you build the client of your DLL - failing to do that could perhaps result in that sort of error message.

Perhaps this is a lengthy way of asking for the specific steps that you have undertaken.

I have vague recollections of posting example code for interfacing a Fortran DLL with matlab (including the use of a prototype m file) on this forum some years ago.  Then again, I also have vague recollections of pegging out the laundry yesterday, but the basket full of wet clothes that I just encountered would perhaps indicate that I am quite capable of deluding myself. 

0 Kudos
dwwade
Beginner
2,336 Views

Probably your biggest incorrect assumption was that I have any clue what I'm doing.  You've got a lot more experience than me in this scary cross language + dll business, and every thing I've been using is what I've found on the internet with searches that are nerfed by a firewall that refuses to let me see a great many coding websites.  I was originally trying to just make a header file and load it with loadlibrary, but since MATLAB's only indication that you failed to get everything correct is if you use libfunctions command to show the functions that are in the library.

So that output came about because I wanted to test out the header file and dll to make sure I had everything right.  To do that I created a small program to try and use one of the functions and look at the status flag that it produces.  That is what I'm trying to compile right now using the .h file.  I don't have the entire suite, so I'm just using the VS2008 cl command to compile the c.  Here is the program and the commandline.

[cpp]#include <stdio.h>
#include "c_mdlib.h"

void main ()
{
    int ip,is;
    double jd;
    c_return valreturn;
    ip = 0;
    is = 3;
    jd = 0.0;
    valreturn = c_bodyn(jd, is, ip);
    printf("status : %d", valreturn.status);
}[/cpp]

cl test.c

The prototype .m file gets produced from the header file supposedly from what I read on the MATLAB forums, thus the reason that I'm using this.  Also according to the help files, loadlibrary requires a C compiler, so I typed in mex -setup and picked my MS2008 C++ compiler.  I have this nagging feeling that for some reason it isn't dressing the function up correctly as the "cl" command produces this line when compiled with the dllimport:

test.obj : error LNK2019: unresolved external symbol __imp__c_bodyn referenced in function _main

The commandline for the Fortran dll that I use is:

ifort /dll c_mdlib.f90

That seems to compile correctly and dumpbin actually shows the function in it.  I made a vastly abbreviated version of my interface module and this produced a dumpbin with this:

Dump of file c_mdlib.dll

File Type: DLL

  Section contains the following exports for c_mdlib.dll

    00000000 characteristics
    512BF3D5 time date stamp Mon Feb 25 16:29:25 2013
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 000012C0 c_bodyn

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        2000 .text

0 Kudos
IanH
Honored Contributor III
2,336 Views

Ahhhhh, it all makes sense now.  I should have twigged with the reference to _main in the linker error.

When you compiled the dll, the linker (invoked by the ifort driver) should have spat out an import library (if it didn't that may be because Visual Studio project properties are suppressing it, or because the linker didn't think there was anything exported from the DLL).  Add the filename of that library to your c compiler link line.  The __declspec ... stuff in the C header for the function can either be dllimport or, given we're importing functions and not variables, you can get rid of it entirely.

[plain]cl test.c c_mdlib.lib[/plain]

If you describe the contents of your DLL to Matlab using a header file, then Matlab uses the C (++?) compiler that it comes with to parse the header file.  If you use a prototype m file I don't believe there's any C compiler involved at all.  Either way, you don't need to *provide* such a compiler.

Perhaps this has changed since the release I use (R2009b), but you should check whether Matlab supports calling library functions that return structures.  If it doesn't you may have to make the return value an INTENT(OUT) argument passed by reference.

0 Kudos
dwwade
Beginner
2,336 Views

I have to make sure that whatever I do works with the 32-bit version (for verification & validation purposes) and the 64-bit version, because we are transitioning to 64-bit windows (thus the reason I'm rewriting DOS software at all since it already works as IT has stamped its foot down on us requesting to use a virtual machine or interpreter to run the old DOS programs we have).  The 64-bit version of MATLAB for Windows doesn't come with a compiler, and for the 32-bit version I had tried to force it to like the Intel Fortran 13 compiler which seemed to confuse it more than I feel it should have.

http://software.intel.com/en-us/forums/topic/279910

Was the only post I found of yours that mentions the matlab prototype file.  Maybe I'm just exhausted, but after staring at it for a while I have to say it doesn't mean anything to me.

As for MATLAB accepting returned structs... I haven't been able to find any definitive information.  I was afraid of what you mention, but hadn't been successful enough yet to get it going.  If I have to pass it through the argument list, then I guess my status can be a returned integer.

I do want to say that the test.c compiled, but asked for msvcr90.dll and after putting that in there it gave me some rather odd messages about DLLs not being used correctly in a runtime error.

0 Kudos
Reply