"Calling Conventions" Default vs "CVF (/iface:cvf)"

"Calling Conventions" Default vs "CVF (/iface:cvf)"

Okay, just when you think you've gotten over the hurdle in a mix-language programming project something always comes out of left field to bring you to a grinding halt.

I have one project involving calling DLLs from Excel that I've been using for several years now. The Fortran was originally coded using Compaq Visual Fortran. I have strings that I pass back and forth between Exce and Fortran using the "CVF" hidden string length convention; the hidden stringth length arguments in the call to Fortran immediately follows the string arguments in the calling statement. No problem.

When I migrated to Intel Visual Fortran, the convention changed to putting all the hidden string arguments "After All Arguments" in the calling statement. Instead of making a lot of code changes at the time I discovered that I could tell IVF to use the CVF calling convention. This has worked just fine ever since.

I am now designing another Excel/Fortran DLL application modeled closely on the on above. The difference is, I need to make a call to a C routine from the Fortran DLL (yes, this is the same project that is a subject of my previous threads; I have been making progress). I've discovered in working the C/Fortran issues that C (in Visual Studio 2005 or Visual Studio itself) apparently needs to see this hidden string length using the IVF convention in the calls between C and Fortran; i.e. with all the hidden length arguments "After All Arguments" in the calling statement. I don't think I can change the calling convention on the C side.

No problem, I just went into the Fortran andput all the hidden length arguments in my call statements to the"end of the line" sort of speak and also changed "Project/Confiuration Properties/Fortran/External Procedures/Calling Convention" setting to "Default". No problem. Remember, though this was all done on the C/Fortran side; debugging from the Fortran side by having the debuggerin Visual Studio start up Excel etc. Ialso wentinto Excel and changed my declaration and call statements to reflect this, although as I point out below, teh first routine I tried calling from Excel only has one string to pass.

Now the problem. When I call any Fortran procedure that involves passing strings in the dll I get a "Run-time error '49': Bad DLL calling convention".

First of all: The first call I tried from Excel involves only ONE string. I simply cannot get the actual argument and hidden length argument in the wrong order when there is only one string to pass.

Second of all: I have discovered that the problem appears to be happening on exiting of theFortran routine, not when it's entered.

How do I know this? First of all, Ihave no problems whatsoever when I debug from the Fortran side; Excelcomes up I, start the Macro and I go right intoand past the call statement on the Fortran side and all the values of the arguments in the Fortransubroutinestatement look good.

Second: I've discovered that if I have a run-time erroron the Fortran side and run directly from Excel I get a severe Fortran error and Excel crasheswithout the "Run-time 49" error, implying that I'm getting past the Subroutine statement and into the Fortran code. So I then purposelyput a couple of lines of code right before theRETURN and END SUBROUTINE lines in theFortran code to give a run-time error: i.e. I set an integer variable to say 199 and then set another variable to somearray("integer variable=199") when somearray is only declared to have, say, 150 elements. When I run from Excel I get a severe Fortran message that tells me I can't have an array index of "199" when the array has been declared to have only 150 elements.

I comment the above run-time error code out and when I run from Excel I get the "Run-time 49" error. I appear to be gettting this error when exiting the Fortran and code on attempting to return to Excel/VBA, not when the Fortran routine is initially called by Excel.

What's up with this??

One possible solution: Under "Project/Configuration Properties/Fortran/External Procedures/Calling Convention" setting to "Default" there is a note that says: "Calling Convention Selects the default calling convention for an application (can be overridden by INTERFACE)...."

I could set the Calling Convention to CVF to satisfy Excel and I have an INTERFACE block inside a Module in my Fortran routine that interacts with the C code (thanks to IanH) but I don't have the faintest idea (and I've searched) what code to put in my interface block for "overriding" the default calling convention to satisfy the C code.

Below is the thread that contains the Module/Interface block example given to me by IanH which I'm using: search page for "Simple version".

http://software.intel.com/en-us/forums/showthread.php?t=62562

Thanks.

Mike

23 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

Hi Mike,

This may only be slightly helpful. We also recently had a DLL calling convention issue. In our case a Visual Basic program calling a Fortran DLL versus a Fortran DLL calling another Fortran DLL. It turned out that the calling convention requirements were different for the two cases. For VB calling Fortran we need to define the full set of ATTRIBUTES including DLLEXPORT, ALIAS, STDCALL, and the REFERENCE list of arguments in the call statement. But for the Fortran to Fortran DLL case, the extra ATTRIBUTES caused a problem with the default calling convention, and we only needed to define DLLEXPORT. There really isn't a way to have a single DLL accessible routine the can be accessed by both VB and Fortran. Our work-around is to defined two different routines as accessible interfaces, which in turn call the same underlying routine for calculations.

Perhaps your case is similar such that you need to define different calling conventions for the Excel and C cases, which means you could create two accessible subroutines in your DLL. And those two accessible routines would call the same routine that actually does the work.

Regards,
Greg

Quoting - sabur
I've discovered in working the C/Fortran issues that C (in Visual Studio 2005 or Visual Studio itself) apparently needs to see this hidden string length using the IVF convention in the calls between C and Fortran; i.e. with all the hidden length arguments "After All Arguments" in the calling statement. I don't think I can change the calling convention on the C side.

Frankly, I sort of stopped reading after this: this statement is not true, and there likely lies the start of your problem. Let me also address similar problem in Greg's reply:

Quoting - Greg Thorwald
There really isn't a way to have a single DLL accessible routine the can be accessed by both VB and Fortran.

Yes there is. First, note that Intel Fortran is capable of declaring routines -- using !DEC$ATTRIBUTES directives -- which conform to various calling conventions and their implementation details. Check the documentation for C, STDCALL, [NO]MIXED_STR_LEN_ARG, VALUE, REFERENCE, DEFAULT and ALIAS attributes. (Unfortunately, some of these interact in non-obvious ways, which I've ranted about before, but let's skip the details).

These directives generally override compiler settings: e.g. if you compile with /iface:cvf, and declare your routine with !DEC$ATTRIBUTES C, the routine will be C.

Generally, if you want to call a C (or VB or Delphi or...) routine from Fortran, you must do one of the following:

  1. Write a proper INTERFACE body for that routine with appropriate !DEC$ATTRIBUTES for routine and arguments
  2. Compile your code with appropriate compiler switches. In this approach, you don't change your code, but you obviously have a problem if you want to interface with 2 different languages simultaneously.

Similarly, if you want your routine to be callable from other language, you can

  1. Declare your routines with appropriate !DEC$ATTRIBUTES
  2. Compile your code with appropriate compiler switches.

So, rather than using option (2), Sabur, you can declare your routines with appropriate attributes. For CVF compatibility, if I recall correctly, you need STDCALL, MIXED_STR_LEN_ARG, REFERENCE (and ALIAS if you want to retain uppercase).

Now, on to Greg's note: if you use non-default !DEC$ATTRIBUTES, how to call those routines from fellow Fortran routines? The answer is: use explicit interfaces. That is, if you place your routines in MODULEs, they can call each other according to standard Fortran rules, no matter which combination of attributes they have. If you don't want to use MODULEs, you must stick to INTERFACE blocks.

I suppose I could elaborate further, but I hope the above helped nonetheless.

Also, there is now a standard way to interface with other languages -- ISO_C_BINDING module -- but it's somewhat less flexible than !DEC$ATTRIBUTES, so I'd skip it as well, for the sake of simplicity. Though I'd recommend to use it if you aim for portability.

Jugoslav
www.xeffort.com

Quoting - Jugoslav Dujic

Frankly, I sort of stopped reading after this: this statement is not true, and there likely lies the start of your problem. Let me also address similar problem in Greg's reply:

Quoting - Greg Thorwald
There really isn't a way to have a single DLL accessible routine the can be accessed by both VB and Fortran.

Yes there is. First, note that Intel Fortran is capable of declaring routines -- using !DEC$ATTRIBUTES directives -- which conform to various calling conventions and their implementation details. Check the documentation for C, STDCALL, [NO]MIXED_STR_LEN_ARG, VALUE, REFERENCE, DEFAULT and ALIAS attributes. (Unfortunately, some of these interact in non-obvious ways, which I've ranted about before, but let's skip the details).

These directives generally override compiler settings: e.g. if you compile with /iface:cvf, and declare your routine with !DEC$ATTRIBUTES C, the routine will be C.

Generally, if you want to call a C (or VB or Delphi or...) routine from Fortran, you must do one of the following:

  1. Write a proper INTERFACE body for that routine with appropriate !DEC$ATTRIBUTES for routine and arguments
  2. Compile your code with appropriate compiler switches. In this approach, you don't change your code, but you obviously have a problem if you want to interface with 2 different languages simultaneously.

Similarly, if you want your routine to be callable from other language, you can

  1. Declare your routines with appropriate !DEC$ATTRIBUTES
  2. Compile your code with appropriate compiler switches.

So, rather than using option (2), Sabur, you can declare your routines with appropriate attributes. For CVF compatibility, if I recall correctly, you need STDCALL, MIXED_STR_LEN_ARG, REFERENCE (and ALIAS if you want to retain uppercase).

Now, on to Greg's note: if you use non-default !DEC$ATTRIBUTES, how to call those routines from fellow Fortran routines? The answer is: use explicit interfaces. That is, if you place your routines in MODULEs, they can call each other according to standard Fortran rules, no matter which combination of attributes they have. If you don't want to use MODULEs, you must stick to INTERFACE blocks.

I suppose I could elaborate further, but I hope the above helped nonetheless.

Also, there is now a standard way to interface with other languages -- ISO_C_BINDING module -- but it's somewhat less flexible than !DEC$ATTRIBUTES, so I'd skip it as well, for the sake of simplicity. Though I'd recommend to use it if you aim for portability.

Okay, this sounds promising; there is a way to have C do the "CVF" convention. I'm not sure how to implement it though.

Do I put something like "!DEC$ ATTRIBUTES STDCALL, MIXED_STR_LEN_ARG,REFERENCE::MAIN" after the "SUBROUTINE MAIN BIND(C) line in the code below?

I've tried various combinations; Just using "MIXED_STR_LEN_ARG", with and without STDCALL and REFERENCE etc. and I always seem to get the error message:

Error    1     error #7796: Only a function or subroutine subprogram may have the !DEC$ ATTRIBUTES directive [NO]MIXED_STR_LEN_ARG specifier.   [MAIN]    C:ACS147Fortran95ACSFortran95READDATAFILEACS.for    22   

Is there a problem with using this along with the BIND(C) protocol? Remember, everything is working fine with the MODULE, INTERFACE and C BINDING code. I.e. I'm getting the data I want from the C side to Fortran, I'd hate to mess with that.

Also, when I change the "Calling Convention" in properties back to CVF as the code is shown below (without the !DEC$... line after the SUBROUTINE MAIN BIND(C) statement) I get the following message:

Error	1	 error LNK2019: unresolved external symbol _MAIN@44 referenced in function _SEEDATA.	READDATAFILEACS.obj	

I'm assuming I need to change the calling convention back to CVF on the Fortran side so that I don't have the "bad DLL calling convention" issue when executing from Excel.

The code below is the Fortran subroutine that calls the C subroutine. READDATAFILE is called from another subroutine that is called from EXCEL.

Thanks for your help!

Mike

C********************************************************************
C
C	SUBROUTINE READDATAFILE.for
C
C	This subroutine reads the data file and stores the information
C	in the QAEU Common
C
C********************************************************************
      MODULE seedata 
      USE, INTRINSIC :: ISO_C_BINDING 
      IMPLICIT NONE 
      ! The data 
      REAL(C_DOUBLE), BIND(C) :: datastuffraw(20000,400)
      REAL(C_DOUBLE), BIND(C) :: datastuffeun(20000,400)
      CHARACTER(15,C_Char), BIND(C) :: grpnm
      INTEGER(C_INT), BIND(C) :: numchn,scans
c      Does the 15 need to be 1?
c      CHARACTER(KIND=C_CHAR), DIMENSION(*) :: grpnm
 
      INTERFACE  
      SUBROUTINE MAIN BIND(C) 
         ! No arguments.   
         ! Everything is global.   
         ! Array dimensions are hard coded. 
         ! All very bad.        
c       CHARACTER(KIND=C_CHAR), DIMENSION(80) :: grpnm   
       END SUBROUTINE
       END INTERFACE 
      
      END MODULE seedata 
      
      
      Subroutine READDATAFILE(fname)
      USE seedata
      USE stuff
        implicit none

	! Expose subroutine ReadDataFile to users of this DLL
	!
c	!DEC$ ATTRIBUTES DLLEXPORT::READDATAFILE


C Common for Data
ccc      COMMON/QAEU/XEU(3000),SCAN_CHAN(60000,500),SCAN_CHANAVE(10001,500)

C Common for zscan   (time)

ccc	COMMON/REALNUMBER/time
ccc	REAL time, xeu
ccc	REAL scan_chan,scan_chanave
      INTEGER*4 begz2scanz,avrgintz


C Common for Passed Parameters
      COMMON/QAARG/begin_scan, end_scan, iaverage, nopts, opts,
     &	         BegZ2scanz, avrgintz
      integer begin_scan, end_scan, iaverage, nopts, opts(10)
	
C Common for Filenames
      COMMON/QAFILES/labDAQ_path, resource_path, data_path,
     &               data_filename, info_filename, param_filename

C	Volatile /QAFILES/

	!DEC$ ATTRIBUTES DLLEXPORT::/QAFILES/


c	CHARACTER*(*) filename
	CHARACTER*80 labDAQ_Path, resource_path, data_path
	CHARACTER*80 data_filename, info_filename, param_filename

C Common for Recording Information
      COMMON/QAREC/scan_rate, total_scans, reading_number, 
     *             total_chans, ichan_list, scan_integer
	INTEGER*4 scan_rate
	INTEGER*4 total_scans
	INTEGER*2 reading_number
	INTEGER*2 total_chans
	INTEGER*2 ichan_list(196)
	INTEGER*4 scan_integer

C Common for Debug
	COMMON/QADEBUG/sim,debug
	LOGICAL sim
	LOGICAL debug

C Local Variables
	character*20 project_name
	character*4 data_keyword
	integer Get_UnitNo, iounit, iodebug
	integer iscan, jchan
	
	
	CHARACTER*(80) fname
      CHARACTER(80) OUTPUT_TEXT,str_out
      CHARACTER(LEN=80,KIND=C_CHAR) :: str1
c      CHARACTER(LEN=15,KIND=C_CHAR) :: grpnm
      INTEGER*4 INT_ARG, OUTPUT_LEN, numChannels, numscans
      CHARACTER(80) INPUT_TEXT
c      real*8 DATASTUFF(90000,500)
      str1 = C_CHAR_"TestYo..." // C_NULL_CHAR 
c      Teststring = "HelloFromMd" // C_NULL_CHAR
      fname = fname // C_NULL_CHAR
       int_arg = 123
       Call MAIN(datastuffraw, datastuffeun, int_arg, numChannels,
     &  numscans, fname,str1,grpnm)	
	
	scan_chan = datastuffeun


c	RETURN
	END

Quoting - sabur

Okay, this sounds promising; there is a way to have C do the "CVF" convention. I'm not sure how to implement it though.

Do I put something like "!DEC$ ATTRIBUTES STDCALL, MIXED_STR_LEN_ARG,REFERENCE::MAIN" after the "SUBROUTINE MAIN BIND(C) line in the code below?

I've tried various combinations; Just using "MIXED_STR_LEN_ARG", with and without STDCALL and REFERENCE etc. and I always seem to get the error message:

Error    1     error #7796: Only a function or subroutine subprogram may have the !DEC$ ATTRIBUTES directive [NO]MIXED_STR_LEN_ARG specifier.   [MAIN]    C:ACS147Fortran95ACSFortran95READDATAFILEACS.for    22   

Is there a problem with using this along with the BIND(C) protocol? Remember, everything is working fine with the MODULE, INTERFACE and C BINDING code. I.e. I'm getting the data I want from the C side to Fortran, I'd hate to mess with that.

Also, when I change the "Calling Convention" in properties back to CVF as the code is shown below (without the !DEC$... line after the SUBROUTINE MAIN BIND(C) statement) I get the following message:

Error	1	 error LNK2019: unresolved external symbol _MAIN@44 referenced in function _SEEDATA.	READDATAFILEACS.obj	

I'm assuming I need to change the calling convention back to CVF on the Fortran side so that I don't have the "bad DLL calling convention" issue when executing from Excel.

The code below is the Fortran subroutine that calls the C subroutine. READDATAFILE is called from another subroutine that is called from EXCEL.

Thanks for your help!

Mike

C********************************************************************
C
C	SUBROUTINE READDATAFILE.for
C
C	This subroutine reads the data file and stores the information
C	in the QAEU Common
C
C********************************************************************
      MODULE seedata 
      USE, INTRINSIC :: ISO_C_BINDING 
      IMPLICIT NONE 
      ! The data 
      REAL(C_DOUBLE), BIND(C) :: datastuffraw(20000,400)
      REAL(C_DOUBLE), BIND(C) :: datastuffeun(20000,400)
      CHARACTER(15,C_Char), BIND(C) :: grpnm
      INTEGER(C_INT), BIND(C) :: numchn,scans
c      Does the 15 need to be 1?
c      CHARACTER(KIND=C_CHAR), DIMENSION(*) :: grpnm

      INTERFACE  
      SUBROUTINE MAIN BIND(C) 
         ! No arguments.   
         ! Everything is global.   
         ! Array dimensions are hard coded. 
         ! All very bad.        
c       CHARACTER(KIND=C_CHAR), DIMENSION(80) :: grpnm   
       END SUBROUTINE
       END INTERFACE 

      END MODULE seedata 


      Subroutine READDATAFILE(fname)
      USE seedata
      USE stuff
        implicit none

	! Expose subroutine ReadDataFile to users of this DLL
	!
c	!DEC$ ATTRIBUTES DLLEXPORT::READDATAFILE


C Common for Data
ccc      COMMON/QAEU/XEU(3000),SCAN_CHAN(60000,500),SCAN_CHANAVE(10001,500)

C Common for zscan   (time)

ccc	COMMON/REALNUMBER/time
ccc	REAL time, xeu
ccc	REAL scan_chan,scan_chanave
      INTEGER*4 begz2scanz,avrgintz


C Common for Passed Parameters
      COMMON/QAARG/begin_scan, end_scan, iaverage, nopts, opts,
     &	         BegZ2scanz, avrgintz
      integer begin_scan, end_scan, iaverage, nopts, opts(10)

C Common for Filenames
      COMMON/QAFILES/labDAQ_path, resource_path, data_path,
     &               data_filename, info_filename, param_filename

C	Volatile /QAFILES/

	!DEC$ ATTRIBUTES DLLEXPORT::/QAFILES/


c	CHARACTER*(*) filename
	CHARACTER*80 labDAQ_Path, resource_path, data_path
	CHARACTER*80 data_filename, info_filename, param_filename

C Common for Recording Information
      COMMON/QAREC/scan_rate, total_scans, reading_number, 
     *             total_chans, ichan_list, scan_integer
	INTEGER*4 scan_rate
	INTEGER*4 total_scans
	INTEGER*2 reading_number
	INTEGER*2 total_chans
	INTEGER*2 ichan_list(196)
	INTEGER*4 scan_integer

C Common for Debug
	COMMON/QADEBUG/sim,debug
	LOGICAL sim
	LOGICAL debug

C Local Variables
	character*20 project_name
	character*4 data_keyword
	integer Get_UnitNo, iounit, iodebug
	integer iscan, jchan


	CHARACTER*(80) fname
      CHARACTER(80) OUTPUT_TEXT,str_out
      CHARACTER(LEN=80,KIND=C_CHAR) :: str1
c      CHARACTER(LEN=15,KIND=C_CHAR) :: grpnm
      INTEGER*4 INT_ARG, OUTPUT_LEN, numChannels, numscans
      CHARACTER(80) INPUT_TEXT
c      real*8 DATASTUFF(90000,500)
      str1 = C_CHAR_"TestYo..." // C_NULL_CHAR 
c      Teststring = "HelloFromMd" // C_NULL_CHAR
      fname = fname // C_NULL_CHAR
       int_arg = 123
       Call MAIN(datastuffraw, datastuffeun, int_arg, numChannels,
     &  numscans, fname,str1,grpnm)	

	scan_chan = datastuffeun


c	RETURN
	END

Okay, let's try and simplify things. I created a very simple Fortran/VBA code combination. Below is the Excel/VBA code: ("the Public Declare...." statement is actually all on one line)

Option Explicit
Option Base 1

Public Declare Sub BADD Lib "C:TestFortranBADDLLTESTBADDLLTESTDebugbaddlltest.dll" (inum As Integer)

Dim inum As Integer
Sub testout()

inum = 4

Call BADD(inum)

End Sub

Here is the Fortran DLL:

Subroutine BADD(int)

!DEC$ ATTRIBUTES DLLEXPORT::BADD

INTEGER*4 int
int = 6

End

No C/C++ code is involved at all. The same thing happens:

1) With "Properties/Fortran/External Procedures/Calling Convention" set to "CVF (/iface:cvf)" this works like a charm.

2) If I do not pass any argument it also worked like a charm whether or not "...Calling Convention" was set to "CVF (/iface:cvf)" or "Default". (Not suprising)

3) If I try to pass an argument (an integer in this example) and "...Calling Convention" is set to "Default" I get a "Bad DLL calling convention" error. This error, as I've described above, occurs when leaving the Fortran subroutine.

I just don't get it. (sigh)

Mike

Quoting - sabur
3) If I try to pass an argument (an integer in this example) and "...Calling Convention" is set to "Default" I get a "Bad DLL calling convention" error. This error, as I've described above, occurs when leaving the Fortran subroutine.

Mike,

read Jugoslav's reply carefully (as he's always right).

.... if I recall correctly, you need STDCALL, MIXED_STR_LEN_ARG, REFERENCE (and ALIAS if you want to retain uppercase).

You have bad calling convention error, most probably, because you're not aliasing (and have name suffix issue) once switched to IVF defaults (CVF uses STDCALL and that's what VB expects in your declaration).

I have to admit that I don't quite understand your explanation on that you receive that error message on exit (from Fortran). To my knowledge the situation is that VB expects something else (STDCALL) than you're providing (using IVF defaults), and that means: (1) naming convention, (2) passing by reference/value (I guess VBA uses by value) and also (3) stack cleanup (Callee/Caller).

See this Steve's reply to other thread on how to call Fortran dll from VB.NET. This should work for you. This KB article also clarifies couple of things (for the future). Refer to a nice description on the calling convention differences in ATTRIBUTES Properties and Calling Conventions in IVF Help.

I believe that a good practise (in mixed language programming) is to explicitly define alias, calling convention, reference/value atributes for arguments, as this saves you pain of adjusting once defaults change (what happens sometimes, as you observe yourself, and in painful way -> CVF and IVF).

A.

Just out of curiosity I tried to run example from IVF Samples directory, in which ...... Excel calls Fortran dll. I used IVF 11 and Excel 2007. Everything seems to work.

VBA

Declare Sub FortranCall Lib "D:UsersArturRunsTestsExcelFcall.dll" (r1 As Long, ByVal num As String)

FORTRAN side

subroutine FortranCall (r1, num)
!DEC$ ATTRIBUTES DLLEXPORT, STDCALL, REFERENCE, ALIAS:"FortranCall" :: FortranCall
integer, intent(in) :: r1
character(10), intent(out) :: num
!DEC$ ATTRIBUTES REFERENCE :: num

num = ''
write (num,'(i0)') r1 * 2

return
end subroutine FortranCall

And one more (and the last thing). Seeing your declaration: you use integer on VBA side and on Fortran integer*4. This can't go together, I believe.

As you can see, the Intel sample uses Long (VBA) and integer (Fortran) (default, which is integer kind = 4)), and this is OK. You're passing integer (kind =2) and tell Fortran to expect integer (kind = 4).

From MSDN

Three data types in Microsoft Visual Basic for Applications (VBA) can represent integers, or whole numbers: the Integer, Long, and Byte data types. Of these, the Integer and Long types are the ones you are most likely to use regularly. The difference between them is their size: Integer variables can hold values between -32,768 and 32,767, while Long variables can range from -2,147,483,648 to 2,147,483,647.

So, read Jugoslav's reply carefully (and follow it), and check the small things.

A.

Quoting - sabur
Okay, let's try and simplify things. I created a very simple Fortran/VBA code combination. Below is the Excel/VBA code: ("the Public Declare...." statement is actually all on one line)

Option Explicit
Option Base 1

Public Declare Sub BADD Lib "C:TestFortranBADDLLTESTBADDLLTESTDebugbaddlltest.dll" (inum As Integer)

Dim inum As Integer
Sub testout()

inum = 4

Call BADD(inum)

End Sub

Here is the Fortran DLL:

Subroutine BADD(int)

!DEC$ ATTRIBUTES DLLEXPORT::BADD

INTEGER*4 int
int = 6

End

No C/C++ code is involved at all. The same thing happens:

1) With "Properties/Fortran/External Procedures/Calling Convention" set to "CVF (/iface:cvf)" this works like a charm.

2) If I do not pass any argument it also worked like a charm whether or not "...Calling Convention" was set to "CVF (/iface:cvf)" or "Default". (Not suprising)

3) If I try to pass an argument (an integer in this example) and "...Calling Convention" is set to "Default" I get a "Bad DLL calling convention" error. This error, as I've described above, occurs when leaving the Fortran subroutine.

For the start, let's assume that you don't use /iface:cvf, i.e. that you have the default compiler options.

VBA expects STDCALL (CVF) calling convention. STDCALL and Ifort default (C) do the stack cleanup differently. Stack cleanup comes into play when there are arguments. With or without arguments, it's highly recommended that you match calling conventions. That means you need at least to add within subroutine BADD:

!DEC$ATTRIBUTES STDCALL, ALIAS: "BADD":: BADD

(Attribute STDCALL has nasty side effect that it lowercases the name by default (bleh) so we need to uppercase it back via alias). In this way, it will be again callable from VBA.

If you want to call a C routine from your code, you probably need an INTERFACE block, with correct prototype. At this stage, I'd recommend that you don't use BIND(C) -- it may interact with !DEC$ATTRIBUTES and confuse the matters further. Offhand, your interface block for the code given above should read (I don't have C prototype so I'm half-guessing):

INTERFACE   
   SUBROUTINE MAIN(datastuffraw, datastuffeun, int_arg, & 
       numChannels, numscans, fname,str1,grpnm)
   !DEC$ATTRIBUTES C, DECORATE, ALIAS: "MAIN":: MAIN
   REAL(8):: datastuffraw(90000,500)
   ???:: datastuffeun
   INTEGER:: int_arg, numChannels, numscans
   !DEC$ATTRIBUTES REFERENCE:: fname
   CHARACTER(*):: fname
   !DEC$ATTRIBUTES REFERENCE:: str1
   CHARACTER(*):: str1
   !DEC$ATTRIBUTES REFERENCE:: grpnm
   CHARACTER(*):: grpnm
   END SUBROUTINE 
END INTERFACE

(P.S. Please, don't call your routines "main" :-). That's likely to confuse linker, as well as readers)

Also, as Artur said, there's likely a VBA:Fortran integer size mismatch (frankly, I also thought that VBA Integer=Fortran Integer(4), but obviously not).

Jugoslav
www.xeffort.com

Quoting - Jugoslav Dujic

For the start, let's assume that you don't use /iface:cvf, i.e. that you have the default compiler options.

VBA expects STDCALL (CVF) calling convention. STDCALL and Ifort default (C) do the stack cleanup differently. Stack cleanup comes into play when there are arguments. With or without arguments, it's highly recommended that you match calling conventions. That means you need at least to add within subroutine BADD:

!DEC$ATTRIBUTES STDCALL, ALIAS: "BADD":: BADD

(Attribute STDCALL has nasty side effect that it lowercases the name by default (bleh) so we need to uppercase it back via alias). In this way, it will be again callable from VBA.

If you want to call a C routine from your code, you probably need an INTERFACE block, with correct prototype. At this stage, I'd recommend that you don't use BIND(C) -- it may interact with !DEC$ATTRIBUTES and confuse the matters further. Offhand, your interface block for the code given above should read (I don't have C prototype so I'm half-guessing):

INTERFACE   
   SUBROUTINE MAIN(datastuffraw, datastuffeun, int_arg, & 
       numChannels, numscans, fname,str1,grpnm)
   !DEC$ATTRIBUTES C, DECORATE, ALIAS: "MAIN":: MAIN
   REAL(8):: datastuffraw(90000,500)
   ???:: datastuffeun
   INTEGER:: int_arg, numChannels, numscans
   !DEC$ATTRIBUTES REFERENCE:: fname
   CHARACTER(*):: fname
   !DEC$ATTRIBUTES REFERENCE:: str1
   CHARACTER(*):: str1
   !DEC$ATTRIBUTES REFERENCE:: grpnm
   CHARACTER(*):: grpnm
   END SUBROUTINE 
END INTERFACE

(P.S. Please, don't call your routines "main" :-). That's likely to confuse linker, as well as readers)

Also, as Artur said, there's likely a VBA:Fortran integer size mismatch (frankly, I also thought that VBA Integer=Fortran Integer(4), but obviously not).

First, in reply to ArturGuzik's 8:22 pm PST reply:

Yes, I know Jugoslav's alrights right, the problem is, is that he and most everyone else here is a bit over my head! :) I'm still in the "this is what works, I don't know why stage", although it's getting a little better. I just not that famililar with putting !DEC$ statements together, although after the last couple of days it has become much clearer. I'm obviously a high level programmer. The ideal solution is to have someone in the computer services division do this (show me) for me but I don't think we have anybody that can do this and we don't seem to have anyone who's kept up with the latest Fortran. Heck, right now I'd pay a consultant, if I could find one locally, to hand-hold me through this. As it is, I'm stuck doing it bit by painful bit. I try to do a lot of googling and reading, which gives me a lot of bits and pieces but it is very difficult to put it together, especially for my particular situation.

Anyway, you're reference to Steve's reply was very helpful. However, if I include REFERENCE as in:

!DEC$ ATTRIBUTES DLLEXPORT,STDCALL,REFERENCE,Alias:'READDATAFILE'::READDATAFILE

I get a Run-time error '453': "Can't find DLL entry point READDATAFILE in C:...."

If I take out REFERENCE things seem to work okay. However, I don't need the hidden argument in the VBA declaration and when I'm in the Fortran dll the string I'm passing comes up garbage in the dubugger but reads fine in the debugger in the C code and in VBA and I seem to get the correct results in the Fortran dll. I'm also at the moment having trouble getting an integer to pass from the Fortran back to VBA.

I did this before I saw the posts following your're 8:22 pm one. I will now look more closely at those and see what I can come up with.

Jugoslav,

A couple of things real quick before I look into your last post. I take it that if I just had a Fortran/C issue, the BINDING stuff would be the way to go, but with Excel/VBA involved and requireing !DEC$ directives that this might cause a conflict between the !DEC$ directives and the BINDING stuff?

Yes, I agree with the "MAIN" naming thing, but somehow I was under the impression that the main function in a C routine had to be called "MAIN"? Or am I wrong. In either case, I was not the one who named it. Or are you suggesting I call it something else using "Alias"?

I will now look into your coding example. Thanks.

Mike

The reason you get an error by adding REFERENCE is that you're using fixed-form source (bad!) and the directive now extends past column 72. You can get around this by adding a second directive line:

!DEC$ ATTRIBUTES REFERENCE :: READDATAFILE

Steve - Intel Developer Support

Quoting - Steve Lionel (Intel)
The reason you get an error by adding REFERENCE is that you're using fixed-form source (bad!) and the directive now extends past column 72. You can get around this by adding a second directive line:

!DEC$ ATTRIBUTES REFERENCE :: READDATAFILE

Thanks Steve! Of course, those directive lines don't "shade out" when you pass column 72 as do regular code statements. After a bit of thrashing around with the help of the above hint I managed to get things to work. The string reads okay in the DLL, I got my integer value to show up in VBA and my head hasn't exploded yet.

Yes, I know, I know, fixed-form bad. New stuff I do in free-form.

However, I'm still getting warnings such as:

"LNK4217: locally defined symbol READDATAFILE imported in function MYPCALCS"

These are my delcarations in READDATAFILE: (for some reason the "Pencil" insert thing kept breaking these lines up)

!DEC$ ATTRIBUTES DLLEXPORT,STDCALL,Alias:'READDATAFILE'::READDATAFILE
!DEC$ ATTRIBUTES REFERENCE::READDATAFILE

This is what is in MYPCALCS:

!DEC$ ATTRIBUTES DLLEXPORT,STDCALL,ALIAS:'MYPCALCS'::MYPCALCS
!DEC$ ATTRIBUTES REFERENCE::MYPCALCS
.
.
.
CALL READDATAFILE(fname,numchannels)

Don't know if this is significant or not.

Thanks again

Mike


Mike,

believe me, I understand the pain you're going through. The mixed-language programming is that sort of thing, where you can't take anything for granted. I mean, even the approach you took ("If I set this it works, so if I set that it should work too (but usually don't.... why, why, why??!!!!)" is due to the fact that some issues/setting/arguments "interplay" with each other in not obvious way, so naturally you need a guidance. I always go back to Chapter ATTRIBUTES Properties and Calling Conventions in Help files, which is clear on all you need to know about calling other languages. Actually entire Mixed language programming Section is very good. As far as googling goes, well, from time to time you might find a wrong suggestion leading in wrong way (for example regarding .Net programming, everybody seems to be an expert suggesting sometimes very strange things).

So if you want correct suggestion on Fortran this Forum is the place. Steve or Jugoslav can answer any question, as they know everything (I believe that Steve after answering thousands of questions actually can read your mind:-))

Quoting - sabur
I'm also at the moment having trouble getting an integer to pass from the Fortran back to VBA.

Attributes (as Steve already pointed out) can be in separate lines, say:

!DEC$ ATTRIBUTES DLLEXPORT, C :: MyRoutine
!DEC$ ATTRIBUTES DECORATE, ALIAS :'MyRoutine':: MyRoutine
!DEC$ ATTRIBUTES REFERENCE :: i
!DEC$ ATTRIBUTES REFERENCE :: a
!DEC$ ATTRIBUTES REFERENCE :: status

Passing back your integer.

You need to pass it by reference (on Fotran side):

!DEC$ ATTRIBUTES REFERENCE :: i

(this means argument INTENT (INOUT) or (OUT)).

making changes on Fortran side usually means you need to adjust also VBA side (ByRef/ByVal).

A.

(Mike, you posted at the same time).

Quoting - sabur
!DEC$ ATTRIBUTES DLLEXPORT,STDCALL,Alias:'READDATAFILE'::READDATAFILE
!DEC$ ATTRIBUTES REFERENCE::READDATAFILE

This is what is in MYPCALCS:

!DEC$ ATTRIBUTES DLLEXPORT,STDCALL,ALIAS:'MYPCALCS'::MYPCALCS
!DEC$ ATTRIBUTES REFERENCE::MYPCALCS
CALL READDATAFILE(fname,numchannels)

Mike,

for clarity separate your attributes like that:
!DEC$ ATTRIBUTES DLLEXPORT,STDCALL :: READDATAFILE
!DEC$ ATTRIBUTES ALIAS : .............
!DEC$ ATTRIBUTES REFERENCE :: argument1, argument2

docs:
The ATTRIBUTES directive options REFERENCE and VALUE specify how a dummy argument is to be passed.

A.

The messages about locally defined imported symbols can be ignored. This happens when you USE a module containing DLLEXPORT directives but you're building the DLL.

REFERENCE does not imply anything about INTENT, though I'll agree that INTENT(OUT) and pass by value don't mix.

Steve - Intel Developer Support

Quoting - sabur
A couple of things real quick before I look into your last post. I take it that if I just had a Fortran/C issue, the BINDING stuff would be the way to go, but with Excel/VBA involved and requireing !DEC$ directives that this might cause a conflict between the !DEC$ directives and the BINDING stuff?

Yes, I agree with the "MAIN" naming thing, but somehow I was under the impression that the main function in a C routine had to be called "MAIN"? Or am I wrong. In either case, I was not the one who named it. Or are you suggesting I call it something else using "Alias"?

I will now look into your coding example. Thanks.

No, C main function is equivalent to Fortran PROGRAM. If you write a library or dll, you certainly don't want or need one. It is possible to call any routine "MAIN" or "Main" (not so sure about "main"), but it's plainly confusing. I'm not suggesting that you use ALIAS, just... give it some other sensible name (if you have access to the sources, of course; if not, live with it).

ISO_C_BINDING is standardized (Fortran 2003) way to interact with C (and other compatible language) routines; that means that you can build these sources with other F2003 compatible compilers. It is an alternative to !DEC$ATTRIBUTES, but the semantics and syntax are different. I'd simply avoid using them in combination, especially for the same subset of routines. ISO_C_BINDING is somewhat less flexible than !DEC$ATTRIBUTES.

In theory, it's possible that you use BIND(C) throughout, but you need STDCALL to interact with VBA, and I'm not sure whether you can mix BIND(C, ALIAS="Foobar") and !DEC$ATTRIBUTES STDCALL, and what exactly the combination yields. That's why I recommended to use !DEC$ATTRIBUTES throughout (and you don't seem to need portability).

Jugoslav
www.xeffort.com

Quoting - Jugoslav Dujic

In theory, it's possible that you use BIND(C) throughout, but you need STDCALL to interact with VBA, and I'm not sure whether you can mix BIND(C, ALIAS="Foobar") and !DEC$ATTRIBUTES STDCALL, and what exactly the combination yields.

We do not allow you to use these together for exactly this reason - there is no agreement on what the combination means.

Steve - Intel Developer Support

The irony here is that (i) in Win32 some forty odd IVF modules make use of SDTCALL (for calling the Win32 API) and (ii) IVF is for writing code that runs on Windows, portability be d...d.

Gerry

Quoting - Steve Lionel (Intel)

We do not allow you to use these together for exactly this reason - there is no agreement on what the combination means.

"Okay, just when you think you've gotten over the hurdle...."

(pause to go search for deals on "StraitJackets" on Amazon.com - hmmm, here's one for $169.95 - don't seem to have one in a medium tall though - I happen to take a longer than average sleeve length... oh wait, nevermind, it's a straitjacket, arm length is not a problem.)

Alright, I will be coming back with questions concerning Jugoslav's suggestions. I think I was able to get rid of all but one type of error message the other night.

Mike

Perhaps you should undo my "Question is answered Yes" line at the beginning of the thread? :) :) :)

For this purpose, just ignore the existence of BIND(C) as it does not meet your needs. Everything you want to do can be done with !DEC$ ATTRIBUTES.

Steve - Intel Developer Support

Quoting - Jugoslav Dujic

No, C main function is equivalent to Fortran PROGRAM. If you write a library or dll, you certainly don't want or need one. It is possible to call any routine "MAIN" or "Main" (not so sure about "main"), but it's plainly confusing. I'm not suggesting that you use ALIAS, just... give it some other sensible name (if you have access to the sources, of course; if not, live with it).

ISO_C_BINDING is standardized (Fortran 2003) way to interact with C (and other compatible language) routines; that means that you can build these sources with other F2003 compatible compilers. It is an alternative to !DEC$ATTRIBUTES, but the semantics and syntax are different. I'd simply avoid using them in combination, especially for the same subset of routines. ISO_C_BINDING is somewhat less flexible than !DEC$ATTRIBUTES.

In theory, it's possible that you use BIND(C) throughout, but you need STDCALL to interact with VBA, and I'm not sure whether you can mix BIND(C, ALIAS="Foobar") and !DEC$ATTRIBUTES STDCALL, and what exactly the combination yields. That's why I recommended to use !DEC$ATTRIBUTES throughout (and you don't seem to need portability).

Jugaslov,

Here's the code that I've got so far. This is the routine READDATAFILE (along with a MODULE and your INTERFACE example from above) that is called by Excel and itself calls a C program called MUD (changed from MAIN like you suggested). I've taken out all the Binding stuff.

Observations:

1) If I don't put CHARACTER statmements in for "str1" and "grpnm" such as below:

       CHARACTER(80 or *) :: str1
       CHARACTER(15 or *) :: grpnm

I get the following error: (for "gprnm" also)

Error 1 error #6404: This name does not have a type, and must have an explicit type. [STR1] C:ACS147Fortran95ACSFortran95READDATAFILEACS.for 86

2) When I put in:

       CHARACTER(*) :: str1
       CHARACTER(*) :: grpnm

I get the error:  (also for grpnm of course)

Error 1 error #6832: This passed length character name has been used in an invalid context. [STR1] C:ACS147Fortran95ACSFortran95READDATAFILEACS.for 42

3) If I put in:

       CHARACTER(80) :: str1
       CHARACTER(15) :: grpnm

The code compiles okay buy when I debug from MUD, I get alignment problems between the arguments in the Fortran call statement and the MUD entry point and can't figure out why; I think the Binding stuff took care of this type of thing before I got rid of it.

On the MUD side, the first two arguemtns, the arrays, seem okay, the next 3 integers, (int *argc...) "cannot be evaulated" and "ivf_fname_len" is equal to the integer 123 which is what "argc" should be.

Beginning of C code follows the Fortran code below.

      MODULE seedata 

      IMPLICIT NONE 
        REAL(8):: datastuffraw(2000,400) 
        REAL(8):: datastuffeun(2000,400)
         
c       CHARACTER(*) :: str1
c       CHARACTER(*) :: grpnm
        INTERFACE  

          SUBROUTINE MUD(datastuffraw, datastuffeun, int_arg,  
     &       numChannels, numscans, fname, str1, grpnm) 
            !DEC$ATTRIBUTES C, DECORATE, ALIAS: "MUD":: MUD
            REAL(8):: datastuffraw(2000,400) 
            REAL(8):: datastuffeun(2000,400) 
            INTEGER(4):: int_arg, numChannels, numscans 
            !DEC$ATTRIBUTES REFERENCE:: fname 
            CHARACTER(*):: fname 
            !DEC$ATTRIBUTES REFERENCE:: str1 
            CHARACTER(*):: str1 
            !DEC$ATTRIBUTES REFERENCE:: grpnm 
            CHARACTER(*):: grpnm

          END SUBROUTINE
        
       END INTERFACE
      END MODULE seedata 
       
      Subroutine READDATAFILE(fname,numchannels,numscans)
      USE seedata
      USE stuff
        implicit none
        
       CHARACTER(80) :: str1
       CHARACTER(15) :: grpnm
        
	! Expose subroutine ReadDataFile to users of this DLL
	!
	!DEC$ ATTRIBUTES DLLEXPORT,STDCALL,Alias:'READDATAFILE'::READDATAFILE
	!DEC$ ATTRIBUTES REFERENCE::READDATAFILE


      INTEGER*4 begz2scanz,avrgintz


C Common for Passed Parameters
      COMMON/QAARG/begin_scan, end_scan, iaverage, nopts, opts,
     &	         BegZ2scanz, avrgintz
      integer begin_scan, end_scan, iaverage, nopts, opts(10)

c	CHARACTER*80 labDAQ_Path, resource_path, data_path
c	CHARACTER*80 data_filename, info_filename, param_filename

C Common for Recording Information

      COMMON/QAREC/scan_rate, total_scans, total_chans,
     *              scan_integer,reading_number,ichan_list     
	INTEGER*4 scan_rate
	INTEGER*4 total_scans
	INTEGER*2 reading_number
	INTEGER*4 total_chans
	INTEGER*2 ichan_list(196)
	INTEGER*4 scan_integer

	CHARACTER(80) fname
	
      CHARACTER(80) OUTPUT_TEXT,str_out
ccc     CHARACTER(LEN=80,KIND=C_CHAR) :: str1
ccc      CHARACTER(LEN=15) :: grpnm
      INTEGER*4 int_arg, numChannels, numscans
ccc      str1 = C_CHAR_"TestYo..." // C_NULL_CHAR 
ccc      fname = fname // C_NULL_CHAR

      fname(len(fname):len(fname)) =char(0)
       int_arg = 123
       
       Call MUD(datastuffraw, datastuffeun, int_arg, numChannels,
     &  numscans, fname,str1,grpnm)	
	
	total_chans = numchannels
	total_scans = numscans
	
	scan_chan = datastuffeun



c	RETURN
	END

C code: (only beginning part through MUD function)

// Include files
//-----------------------------------------------------------------------------
#include "nilibddc.h"
#include 
#include 
#include  

//#define Dllimport   __declspec( dllimport )   //MJB
//#define DllExport   __declspec( dllexport )   //MJB
//#define _main   main						  //MJB

//DllExport MAIN();    //MJB
extern MUD();  //MJB Doing this allows debugging from FortRead to go into CDLL
//-----------------------------------------------------------------------------
// Macros
//-----------------------------------------------------------------------------
#define ddcChk(f) if (ddcError = (f), ddcError < 0) goto Error; else
#ifdef nullChk
#undef nullChk
#endif
#define nullChk(p) if (!(p)) { ddcError = DDC_OutOfMemory; goto Error; } else


//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
//static const char * FILE_PATH		= "testtdms.tdms";
static const char * FILE_PATH		= "C:ACS11_3_2008_2_57.tdms      ";
//static const char * FILE_PATH		= "C:ACS2_6_2008_9_58 AM.tdms";
//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
static int	ReadFile		(void);
static int	ReadGroups		(DDCFileHandle file);
static int	ReadChannels 	(DDCChannelGroupHandle group);
double 		GetAvgDataValue (unsigned __int64 numDataValues, double *data);
//extern char stuff_mp_FILE_PATH[81];  //MJB
//extern char stuff_mp_str2;  //MJB
extern char Teststring[12];  //MJB


#define cols 400       //MJB
#define rows 2000     //MJB


void fix_string_for_f90(char output_text[], size_t slen);  //MJB
double datastuffraw[cols][rows];      //MJB
double datastuffeun[cols][rows];   //MJB
extern int numChannels;  //MJB
int numchn; //MJB
int scans;  //MJB
//unsigned __int64 scans; //MJB
int count;  //MJB
unsigned int groupn=0;
char *grpnm;							//MJB  *************************************
/*12/23:  This is where I think I'm declaring grpnm to be global    */



//int ivf_grpnm_len;				  //MJB
//int *argc
//-----------------------------------------------------------------------------
// Program entry-point function
//-----------------------------------------------------------------------------
//int MAIN (int argc, char *argv[])    //Original commented out by MJB
//MAIN (double *array2)                 //MJB


MUD (double *datastuffraw, double *datastuffeun, int *argc, int *numc, int *scansn, char*fname, char* str1, char* grpnm,int ivf_fname_len, int ivf_str1_len, int ivf_grpnm_len)   //MJB


{
	int	ddcError = 0;
	*argc = 2;  //MJB Simply added in attempt to pass an integer. Not integral to original code
	FILE_PATH = fname;
	ddcChk (ReadFile());

	*numc = numchn;
	*scansn = scans;
Error:
	if (ddcError < 0)
		printf ("nError: %sn", DDC_GetLibraryErrorDescription(ddcError));
	else
		printf ("nNo errors.n");
	printf("End of program, press Enter key to quitn");
	getchar();
	return 0;
}

//-----------------------------------------------------------------------------
// Helper functions
//-----------------------------------------------------------------------------
.
.
.
.

Thanks

Mike

OK, let's focus on MUD for the moment, one error by one. Before you proceed, here's a bargain :-P.

MUD (double *datastuffraw, 
double *datastuffeun, 
int *argc,    //a)
int *numc,    //a)
int *scansn,  //a) 
char*fname,   //b)
char* str1,   //b)
char* grpnm  //b)
 )   //MJB

The arguments 3-5 are int* (by reference) rather than int (by value). However, !DEC$ATTRIBUTES C implies call by value (yukk!). So you need to specify !DEC$ATTRIBUTES REFERENCE:: argc, numc, scansn in the INTERFACE.

The arguments 6-8 are strings. A general observation about strings is that compilers need to know how long they are. Fortran and C do that differently in general: Fortran passes the buffer length all along, while C relies on the string being (CHAR(0))-terminated.

So you can choose either Fortran-style (with hidden length arguments) or C-style string passing. Above, I removed the lengths. That is paired with !DEC$ATTRIBUTES REFERENCE in the interface (yukk: for strings, it means "don't pass hidden length" rather than "don't pass by value"):

!DEC$ATTRIBUTES REFERENCE:: fname 
CHARACTER(*):: fname 
!DEC$ATTRIBUTES REFERENCE:: str1 
CHARACTER(*):: str1 
!DEC$ATTRIBUTES REFERENCE:: grpnm 

On Fortran side, you should terminate the strings with CHAR(0) so that they're C-style, e.g.:

call MUD(..., trim(fname)//char(0), ...)

(I didn't pay attention whether the strings are input or output from MUD. Pay attention that, as in Fortran, an expression such as trim(fname)//char(0) above may not correspond to an output argument. You can put INTENT() into interface to ensure compile-time checking).

Alternatively, you can keep the length arguments in C. In that case, remove !DEC$ATTRIBUTES REFERENCE from the interface.

Your observations are also string-length related, but let's skip that for the moment...

Jugoslav
www.xeffort.com

Quoting - Jugoslav Dujic

OK, let's focus on MUD for the moment, one error by one. Before you proceed, here's a bargain :-P.

MUD (double *datastuffraw, 
double *datastuffeun, 
int *argc,    //a)
int *numc,    //a)
int *scansn,  //a) 
char*fname,   //b)
char* str1,   //b)
char* grpnm  //b)
 )   //MJB

The arguments 3-5 are int* (by reference) rather than int (by value). However, !DEC$ATTRIBUTES C implies call by value (yukk!). So you need to specify !DEC$ATTRIBUTES REFERENCE:: argc, numc, scansn in the INTERFACE.

The arguments 6-8 are strings. A general observation about strings is that compilers need to know how long they are. Fortran and C do that differently in general: Fortran passes the buffer length all along, while C relies on the string being (CHAR(0))-terminated.

So you can choose either Fortran-style (with hidden length arguments) or C-style string passing. Above, I removed the lengths. That is paired with !DEC$ATTRIBUTES REFERENCE in the interface (yukk: for strings, it means "don't pass hidden length" rather than "don't pass by value"):

!DEC$ATTRIBUTES REFERENCE:: fname 
CHARACTER(*):: fname 
!DEC$ATTRIBUTES REFERENCE:: str1 
CHARACTER(*):: str1 
!DEC$ATTRIBUTES REFERENCE:: grpnm 

On Fortran side, you should terminate the strings with CHAR(0) so that they're C-style, e.g.:

call MUD(..., trim(fname)//char(0), ...)

(I didn't pay attention whether the strings are input or output from MUD. Pay attention that, as in Fortran, an expression such as trim(fname)//char(0) above may not correspond to an output argument. You can put INTENT() into interface to ensure compile-time checking).

Alternatively, you can keep the length arguments in C. In that case, remove !DEC$ATTRIBUTES REFERENCE from the interface.

Your observations are also string-length related, but let's skip that for the moment...

Jugoslav,

Alright, I chose to go with the C-style string passing. Seems to work much better. Had a littlel trouble with the integers at first until I re-read your post and saw need to specify passing them as reference. By the way, I think you meant !DEC$ATTRIBUTES REFERENCE:: argc, numChannels, numscans instead of ...argc, numc, scansn. "numChannels" and "numscans" are the names on the Fortran side; "numc" and "scansn" are the corresponding dummy arguments on the C side. IF not, let me know.

I still have to declare the strings "fname", "str1" and "grpnm" with CHARACTER statements with specific lengths on the Fortran side or the compiler complains. The same thing with the "datastuff..." arrays as can be seen in Subroutine READDATAFILE.

Things look okay now except that I'm having trouble with the datastuff... arrays on the C side. Actually before I made the changes above I was having trouble on the C side getting the values of the integers "numc" and "scansn" from a "helper" function into the MUD function so that they could be passed to Fortran (I'm a very newbie C person). The arrays were passing okay. Since I made the above changes, things have switched; the values of the integers show up in MUD and are transferred but the two arrays go blank when control is passed to MUD.

This is not a big deal since I have found a very knowledgeble C/C++ person at work (he's actually a former Fortran programer from the 60s/70s who has been working for years with getting C/C++ code work with NASA legacy Fortran codes. He is not familiar with Intel Fortran however). He should be able to help me with anything on the C side.

I will copy the two codes as they now stand below. Feel free to take a stab at the C issues if you want/have the time. Not necessary though (I know this is not a C forum). When I get the C side cleared up (probably Monday) I will see if the arrays are passed successfully to READDATAFILE. If not, and if after a certain amount of thrashing around I still have no luck I will post again. (I'll try and post again regardless of course just to update things).

Fortan:

Note: I have tried to read some of the documentation and learned (actually re-learned) that arrays are only passed by reference so I have added a !DEC directive in the INTERFACE to declare the "datastuff..." arrays as REFERENCE even though that is probably the default. Also, I suspect that my difficulty with getting the vaules of variables in MUD probably has to do with a conflict with how the arguments are declared in the MUD statement. But I have not been able to figure it out.

C********************************************************************
C
C	SUBROUTINE READDATAFILE.for
C
C	This subroutine reads the data file and stores the information
C	in the QAEU Common
C
C********************************************************************
      MODULE seedata 

      IMPLICIT NONE 
c        REAL(8):: datastuffraw(27000,350) 
c        REAL(8):: datastuffeun(27000,350)
         
c       CHARACTER(*) :: str1
c       CHARACTER(*) :: grpnm
        INTERFACE  

          SUBROUTINE MUD(datastuffraw, datastuffeun,int_arg,  
     &       numChannels, numscans, fname, str1, grpnm) 
            !DEC$ATTRIBUTES C, DECORATE, ALIAS: "MUD":: MUD
            REAL(8):: datastuffraw(27000,350) 
            REAL(8):: datastuffeun(27000,350) 
c            INTEGER(4):: numChannels, numscans
            !DEC$ATTRIBUTES REFERENCE:: fname 
            CHARACTER(*):: fname 
            !DEC$ATTRIBUTES REFERENCE:: str1 
            CHARACTER(*):: str1 
            !DEC$ATTRIBUTES REFERENCE:: grpnm 
            CHARACTER(*):: grpnm
            !DEC$ ATTRIBUTES REFERENCE::int_arg,numChannels,numscans
            !DEC$ ATTRIBUTES REFERENCE::datastuffraw,datastuffeun
          END SUBROUTINE
        
       END INTERFACE
      END MODULE seedata 
       
      Subroutine READDATAFILE(fname,numchannels,numscans)
      USE seedata
      USE stuff
        implicit none
        REAL(8):: datastuffraw(27000,350) 
        REAL(8):: datastuffeun(27000,350)
       CHARACTER(80) :: fname 
       CHARACTER(80) :: str1
       CHARACTER(80) :: grpnm
       INTEGER*4 int_arg, numChannels, numscans 
        
	! Expose subroutine ReadDataFile to users of this DLL
	!
	!DEC$ ATTRIBUTES   DLLEXPORT,STDCALL,Alias:'READDATAFILE'::READDATAFILE
	!DEC$ ATTRIBUTES REFERENCE::READDATAFILE


      INTEGER*4 begz2scanz,avrgintz


C Common for Passed Parameters
      COMMON/QAARG/begin_scan, end_scan, iaverage, nopts, opts,
     &	         BegZ2scanz, avrgintz
      integer begin_scan, end_scan, iaverage, nopts, opts(10)

c	CHARACTER*80 labDAQ_Path, resource_path, data_path
c	CHARACTER*80 data_filename, info_filename, param_filename

C Common for Recording Information

      COMMON/QAREC/scan_rate, total_scans, total_chans,
     *              scan_integer,reading_number,ichan_list     
	INTEGER*4 scan_rate
	INTEGER*4 total_scans
	INTEGER*2 reading_number
	INTEGER*4 total_chans
	INTEGER*2 ichan_list(196)
	INTEGER*4 scan_integer

	
	
      CHARACTER(80) OUTPUT_TEXT,str_out
ccc     CHARACTER(LEN=80,KIND=C_CHAR) :: str1
ccc      CHARACTER(LEN=15) :: grpnm
      
ccc      str1 = C_CHAR_"TestYo..." // C_NULL_CHAR 
ccc      fname = fname // C_NULL_CHAR

ccc      fname(len(fname):len(fname)) =char(0)
       int_arg = 123
       
       Call MUD(datastuffraw, datastuffeun,int_arg, numChannels,
     &  numscans, trim(fname)//char(0),trim(str1)//char(0),
     &  trim(grpnm)//char(0))	
	
c	datastuffraw, datastuffeun, 
	total_chans = numchannels
	total_scans = numscans
	
	scan_chan = datastuffeun



c	RETURN
	END

C code:

//-----------------------------------------------------------------------------
//
//	This sample program shows how to read a DIAdem file.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------
#include "nilibddc.h"
#include 
#include 
#include  

//#define Dllimport   __declspec( dllimport )   //MJB
//#define DllExport   __declspec( dllexport )   //MJB
//#define _main   main						  //MJB

//DllExport MAIN();    //MJB
extern MUD();  //MJB Doing this allows debugging from FortRead to go into CDLL
//-----------------------------------------------------------------------------
// Macros
//-----------------------------------------------------------------------------
#define ddcChk(f) if (ddcError = (f), ddcError < 0) goto Error; else
#ifdef nullChk
#undef nullChk
#endif
#define nullChk(p) if (!(p)) { ddcError = DDC_OutOfMemory; goto Error; } else

//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
//static const char * FILE_PATH		= "testtdms.tdms";
static const char * FILE_PATH		= "C:ACS11_3_2008_2_57.tdms      ";
//static const char * FILE_PATH		= "C:ACS2_6_2008_9_58 AM.tdms";
//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
static int	ReadFile		(void);
static int	ReadGroups		(DDCFileHandle file);
static int	ReadChannels 	(DDCChannelGroupHandle group);
double 		GetAvgDataValue (unsigned __int64 numDataValues, double *data);
//extern char stuff_mp_FILE_PATH;  //MJB
//extern char stuff_mp_str2;  //MJB
extern char Teststring[12];  //MJB

#define cols 350       //MJB
#define rows 27000     //MJB

void fix_string_for_f90(char output_text[], size_t slen);  //MJB
double datastuffraw[cols][rows];      //MJB
double datastuffeun[cols][rows];   //MJB
int numChannels;  //MJB
int numchn; //MJB
int scans;  //MJB
int argc;  //MJB
//unsigned __int64 scans; //MJB
int count;  //MJB
unsigned int groupn=0;
char *grpnm;							//MJB  *************************************
/*12/23:  This is where I think I'm declaring grpnm to be global    */

//-----------------------------------------------------------------------------
// Program entry-point function
//-----------------------------------------------------------------------------
//int MAIN (int argc, char *argv[])    //Original commented out by MJB
//MAIN (double *array2)                 //MJB


//MUD (double *datastuffraw, double *datastuffeun, int *argc, int *numc, int *scansn, char*fname, char* str1, char* grpnm,int ivf_fname_len, int ivf_str1_len, int ivf_grpnm_len)   //MJB
MUD (double *datastuffraw[cols][rows], double *datastuffeun[cols][rows], int *argc, int *numc, int *scansn, char*fname, char* str1, char* grpnm)

{
	int	ddcError = 0;
	*argc = 2;  //MJB Simply added in attempt to pass an integer. Not integral to original code
	FILE_PATH = fname;
	ddcChk (ReadFile());

	*numc = numchn;
	*scansn = scans;
Error:
	if (ddcError < 0)
		printf ("nError: %sn", DDC_GetLibraryErrorDescription(ddcError));
	else
		printf ("nNo errors.n");
	printf("End of program, press Enter key to quitn");
	getchar();
	return 0;
}

//-----------------------------------------------------------------------------
// Helper functions
//-----------------------------------------------------------------------------

// Reads the file
static int ReadFile (void)
{
	int				ddcError = 0; 
	unsigned int length;
	DDCFileHandle	file = 0;
	char			*property = 0;

//	unsigned int *year=0, *month=0, *day=0, *hour=0, *minute=0, *second=0, *milliSecond=0, *weekDay=0;


//	char* grpnm;
	// Read file name
	ddcChk (DDC_OpenFile (FILE_PATH, "TDMS", &file));
	ddcChk (DDC_GetFileStringPropertyLength (file, DDC_FILE_NAME, &length));
	nullChk (property = (char *)malloc (length + 1));
	ddcChk (DDC_GetFileProperty (file, DDC_FILE_NAME, property, length + 1));	
//	printf ("File name property: %sn", property);  //MJB Don't need for my app
	free (property);
	property = 0;


//	ddcChk (DDC_GetFileStringPropertyLength (file, DDC_FILE_DATETIME, &length));
//	nullChk (property = (char *)malloc (length + 1));
//	ddcChk (DDC_GetFileProperty (file, DDC_FILE_DATETIME, property, length + 1));	
//	free (property);
//	property = 0;
	
//    ddcChk (DDC_GetFilePropertyTimestampComponents (file, DDC_FILE_DATETIME, property, year,
//		month,day,hour,minute,second,milliSecond));





	// Read file description, if present
	if (DDC_GetFileStringPropertyLength (file, DDC_FILE_DESCRIPTION, &length) >= 0)
		{
		nullChk (property = (char *)malloc (length + 1));
		ddcChk (DDC_GetFileProperty (file, DDC_FILE_DESCRIPTION, property, length + 1));

//		printf ("File description property: %sn", property);  //MJB Don't need for my app
		free (property); 
		property = 0;
		}
	
	// Read the channel groups
	ddcChk (ReadGroups (file));
	
Error:
	if (property)
		free (property);
	if (file)
		DDC_CloseFile (file);
	return ddcError;
}

// Reads all the channel groups in a file
static int ReadGroups (DDCFileHandle file)
{
	int						ddcError = 0;
	unsigned int		length;
	unsigned int			i, numGroups;
	DDCChannelGroupHandle	*groups = 0;
	char					*property = 0;
//	grpnm=0;
	// Get all the channel groups
	ddcChk (DDC_GetNumChannelGroups (file, &numGroups));
	nullChk (groups = (DDCChannelGroupHandle *)calloc (numGroups, sizeof (DDCChannelGroupHandle)));
	ddcChk (DDC_GetChannelGroups (file, groups, numGroups));
	
	for (i = 0; i < numGroups; ++i)
		{
		groupn = i;
		// Read the channel group name
		ddcChk (DDC_GetChannelGroupStringPropertyLength (groups[i], DDC_CHANNELGROUP_NAME, 
			&length));
		nullChk (property = (char *)malloc (length + 1));
		ddcChk (DDC_GetChannelGroupProperty (groups[i], DDC_CHANNELGROUP_NAME, 
			property, length + 1));
 
/* *********************************************************** */
/* Here's where I want to set the character string that "property" points to someplace "permanent", like
grpnm, so I can sent it back to Fortran. Before the next line when I'm debugging, "grpnm "cannot be evaulated".
*/
		grpnm = property;  /*I know, grpnm is just looking at what property is pointing too, when
						   property is freed below, so is grpnm, but this is the only way to run the code through
						   without choking.    property has (points to) the correct string and has a total 
						   length of 17; the "null" character is at array index 16.*/
//		sprintf(grpnm, "%s", property);
//		strcpy(grpnm, property);  /* I believe strcopy is the way to set a value to a variable as opposed
//								  to an address but I can't get it to work here*/
		fix_string_for_f90(grpnm, 15  /* ivf_str4_len */ );

/* I know that I have some kind of string length issue.  property is of variable length and I somehow have to
get grpnm's length to agree????   I know at some point I need to determine the length of property (and add 1
and have it replace the "15" in the "fix_string_...." line.  And I believe that if property were to be less
than 15 (+or-1) I would have problems (for the moment I know that the two values (this part of the code loops
twice)that go into property are more than 15 characters)*/
/* *********************************************************** */

	//	printf ("n");													//Original code
	//	printf ("Channelgroup #%d name property: %sn", i+1, property); //Original code
		free (property); 
		property = 0;

		// Read the channel group description, if present
		if (DDC_GetChannelGroupStringPropertyLength (groups[i], 
			DDC_CHANNELGROUP_DESCRIPTION, &length) >= 0)
			{
			nullChk (property = (char *)malloc (length + 1));
			ddcChk (DDC_GetChannelGroupProperty (groups[i], DDC_CHANNELGROUP_DESCRIPTION, 
				property, length + 1));

			printf ("Channelgroup #%d description property: %sn", i+1, property);
			free (property); 
			property = 0;
			}
		
		// Read the channels in this group
		ddcChk (ReadChannels (groups[i]));
		}

Error:
	// Cleanup
	if (groups)
		free (groups);
	if (property)
		free (property);
	return ddcError;
}

// Reads all the channels in a channel group
static int ReadChannels (DDCChannelGroupHandle group)
{
	int						ddcError = 0;
	unsigned int			i, length;
    unsigned static int numChannels;
	unsigned __int64		numDataValues;
	DDCChannelHandle		*channels = 0;
	char					*property = 0;
	double					*data = 0; //avgDataValue;
	// Read all the channels in this channel group
	ddcChk (DDC_GetNumChannels (group, &numChannels));
	nullChk (channels = (DDCChannelHandle *) calloc (numChannels, sizeof (DDCChannelHandle)));
	ddcChk (DDC_GetChannels (group, channels, numChannels));
	
	numchn = numChannels;

	for (i = 0; i < numChannels; ++i)
		{
		// Read the channel name
		ddcChk (DDC_GetChannelStringPropertyLength (channels[i], DDC_CHANNEL_NAME, &length));
		nullChk (property = (char *) malloc (length + 1));
		ddcChk (DDC_GetChannelProperty (channels[i], DDC_CHANNEL_NAME, property, length + 1));
		
		printf ("n");
	//	printf ("Channel #%d name property: %sn", i+1, property);
		free (property); 
		property = 0;
		
		// Read the channel description, if present
		if (DDC_GetChannelStringPropertyLength (channels[i], DDC_CHANNEL_DESCRIPTION, &length) >= 0)
			{
			nullChk (property = (char *)malloc (length + 1));
			ddcChk (DDC_GetChannelProperty (channels[i], DDC_CHANNEL_DESCRIPTION, 
				property, length + 1));

		//	printf ("Channel #%d description property: %sn", i+1, property);
			free (property); 
			property = 0;
			}
		
		// Read the channel units, if present
		if (DDC_GetChannelStringPropertyLength (channels[i], DDC_CHANNEL_UNIT_STRING, &length) >= 0)
			{
			nullChk (property = (char *)malloc (length + 1));
			ddcChk (DDC_GetChannelProperty (channels[i], DDC_CHANNEL_UNIT_STRING, 
				property, length + 1));

			printf ("Channel #%d unit string property: %sn", i+1, property);
			free (property); 
			property = 0;
//			return numchn;
			}

		// Read the channel data
		ddcChk (DDC_GetNumDataValues (channels[i], &numDataValues));
		nullChk (data = (double *) malloc (sizeof (double) * (unsigned int)numDataValues));
		ddcChk (DDC_GetDataValues (channels[i], 0, (unsigned int)numDataValues, data));

		if (i == 0)
		{
			scans = numDataValues;
		}

		if (groupn == 0)
		{for (count =0; count<=numDataValues; count++)      //MJB
			datastuffraw[i][count] = data[count];			   //MJB
		}
		if (groupn == 1)
		{for (count =0; count<=numDataValues; count++)      //MJB
			datastuffeun[i][count] = data[count];			   //MJB
		}



//		avgDataValue = GetAvgDataValue (numDataValues, data);  //MJB Orig code, don't need

//		printf ("Channel #%d number of data values: %I64dn", i+1, numDataValues); //MJB Orig code, don't need
//		printf ("Channel #%d average data value: %.2fn", i+1, avgDataValue);      //MUB  "    "      "    "
	
	if (count==5 && i == 8) 
	{

	int	*argc = 3;
		
	}


	}
//	return numchn;
//	return scans;
	

Error:
	// Cleanup
	if (data)
		free (data);
	if (channels)
		free (channels);
	if (property)
		free (property);
	return ddcError;

}


double GetAvgDataValue (unsigned __int64 numDataValues, double *data)
{
	int 	i;
	double 	sum = 0.0;
	
	for (i = 0; i < numDataValues; i++)
		sum += data[i];
	
	return sum / (unsigned int)numDataValues;
}
   void fix_string_for_f90(char s[], size_t len)   
   {   
      size_t i;     /* Avoid size_t != int warnings */ 
      for (i = strlen(s); i < len; i++)  s[i] = ' ';   
   }    


Thanks again.

Mike

Leave a Comment

Please sign in to add a comment. Not a member? Join today