I'm using C++Builder 10.3 to build a GUI for legacy F77 FORTRAN analysis software. A Win32 DLL was built and loads OK. The DLL needs to write results to an external file. The usual WRITE statement with unit=num results in an External exception 80000003. WRITEFA@ can be used for one-line writes, but existing format statements that produce multi-line writes can't be included as arguments. Opening the external file using OPEN with a specific unit number seems to work OK, so it seems that WRITE doesn't have a good handle for the external file. Is there any way that WRITE can be used?
WRITE called in a DLL results in an exception
Quoted from BK ... existing format statements that produce multi-line writes can't be included as arguments.
Format statements exist only in Fortran source code, and allow the statement label to be used for the format string. There is no way to pass a Fortran statement from your non-Fortran GUI code layer to the Fortran DLL*. You can, however, pass a properly constituted Format string as an actual argument, as long as the GUI code knows how to pass Fortran string arguments while adhering to the conventions of the Fortran compiler and runtime.
Quoted from BK Opening the external file using OPEN with a specific unit number seems to work OK, so it seems that WRITE doesn't have a good handle for the external file.
I do not see how the second statement follows from the first, and what you mean by 'good handle'. WRITE statements are converted to a series of calls to I/O routines in the Fortran RTL, and we rarely need to know the details of those calls.
- Leaving out implementations that pass the Fortran code to the compiler, wait for the compilation and linking to complete, and transfer execution to the resulting EXE/DLL.
What UNIT numbers are you using for file output?
mecej4, the FORTRAN source includes several multi-line writes such as a header and a glossary. I wasn't talking abt passing a format statement from C++builder to the DLL. I was talking abt an inability to include in WRITEFA@ a format label or multiple format statement lines. But I have an idea for a work-around.
paullaidler, I was using unit=17 in WRITE. I later used OPENF@ to open the output file. This returns identifier SID which is used in WRITEFA@. In my inexperience, I interpret SID as a handle.
Out of curiosity, I pulled my XP machine with an old FORTRAN compiler out of storage, and built the DLL with the same source code (taking out any F95 code). Running the DLL with C++Builder worked just fine, no problems.
Sad to say, I think Silverfrost is not ready for the big leagues.
BTW, when using an internal write, you have to know a priori the width of the value to write. For a one-digit integer, fmt='I1' works, fmt='I2' doesn't. Not too convenient when results are computed and you don't know what the width would be. And I didn't even try it with a floating-point number.
A very disappointed customer.
Quoted from BK ... when using an internal write, you have to know a priori the width of the value to write. For a one-digit integer, fmt='I1' works, fmt='I2' doesn't. Not too convenient when results are computed and you don't know what the width would be.
You can use the I0 format. For example,
program inwrite
implicit none
integer ijk,i
character*11 str
!
ijk = 1
do i=1,5
write(str,'(I0)')ijk
print '(1x,I2,2x,A)',i,trim(str)//' $'
ijk=ijk*10+1
end do
end program
gives
1 1 $
2 11 $
3 111 $
4 1111 $
5 11111 $
You can build up a multi-line string in your code before writing that string using WRITEFA@, if you wish.
program inwrite
implicit none
integer ijk,i
integer*2 sid,ier
character*7 str
character*80 lines
!
ijk = 1
lines = ''
do i=1,5
write(str,'(I0)')ijk
if (i .gt. 1) lines = trim(lines) // char(10)
lines = trim(lines) // str
ijk=ijk*10+1
end do
call openw@('multi.txt',sid,ier)
call writefa@(lines,sid,ier)
call closef@(sid,ier)
end program
writes the file multi.txt, containing:
1
11
111
1111
11111
mecej4, tnx for the hints, but it doesn't make sense for me to re-write hundreds of write statements that work perfectly fine when using a DLL created by an old FORTRAN compiler.
I'm hoping that my concerns abt DLL writes get passed on to the developers. I'm also hoping I get a notice abt an updated version of Silverfrost if and when they incorporate fixes.
I am still in the dark regarding (i) what features, standard or non-standard, that you use in your code and are supported by your anonymous old compiler as well as FTN95 and (ii) the code that fails with the current version of FTN95.
If you can provide a demonstrator, please do; if the bug can be reproduced, we will be half-way to a solution. In the absence of actual code, little progress can be made.
This is just to endorse mecej4's comment.
Documentation is provided in ftn95.chm in under 'Mixed language programming'.
But we need some basic sample code from you in order to reproduce the failure - a main program in C calling a subprogram in Fortran with details of how they are linked.
BK
I have put together a sample based on your description and it works OK for me. So we need a very simple sample from you that illustrates the failure.
This is my C code:
extern 'C' void SUB1(int* k);
int _tmain(int argc, _TCHAR* argv[])
{
int k = 42;
SUB1(&k);
return 0;
}
and here is my Fortran code:
SUBROUTINE sub1(k)
INTEGER k
open(10,file='output.dat')
write(10,*) k
close(10)
END SUBROUTINE sub1
Here's the C++Builder code: typedef void (__cdecl *MYPROC)(); HINSTANCE hndl; MYPROC ProcAdd; BOOL fFreeResult, fLinkSuccess = FALSE; CurrDirec = GetCurrentDir(); hndl = LoadLibraryA('INPUTS.dll'); if (hndl != NULL) { ProcAdd = (MYPROC) GetProcAddress(hndl, 'INPUTS'); if (NULL != ProcAdd) { fLinkSuccess = TRUE; ShowMessage('link success'); (ProcAdd) (); } else { ShowMessage('could not get dll addr'); } fFreeResult = FreeLibrary(hndl); }
And here's the fortran: F_STDCALL Subroutine INPUTS CFTN95$OPTIONS(FIXED,STDCALL) (misc comments) ENTRY SALFSTARTUP (the rest is F77 common blocks, local variables, & format statements.
The DLL was created using command-line slink since it didn't appear that link options did anything but create a default LibMain.
Here are the slink commands: DLL load c:\aa2\Inputs.obj load c:\aa2\FixAnt.obj load c:\aa2\GetLwr.obj load c:\aa2\Stat1.obj exportall map c:\aa2\statmap.txt file c:\aa2\INPUTS
Forgot to mention that Sub INPUTS calls three other subroutines, all starting with 'F_STDCALL Subroutine' and including 'ENTRY SALFSTARTUP.', The on-line help wasn't clear on whether the other subroutines should include F_STDCALL and the ENTRY, so I put them in. But the issues occurred in INPUTS, not the others.
After the F77 common blocks, local variables, & format statements, the fortran opens and reads a text file with integer arguments, input data file name, and output data file name. Both files are in the current working directory. I don't pass the file names because past experience shows that the file name strings get garbled. The open is typical F77: EXISTS = .FALSE. INQUIRE(FILE='GUI_data.txt',EXIST=EXISTS) IF (EXISTS) THEN OPEN (UNIT=25,FILE='GUI_data.txt',STATUS='UNKNOWN', & FORM='FORMATTED',ACCESS='SEQUENTIAL') DO 5 JJ = 1,4 LINE = ' ' READ(25,1009) LINE IF (JJ .EQ. 1) READ(LINE(1:1),fmt='(I1)') IGUI IF (JJ .EQ. 2) READ(LINE(1:1),fmt='(I1)') IOUT IF (JJ .EQ. 3) INPNAM = TRIM(LINE) IF (JJ .EQ. 4) OUTNAM = TRIM(LINE) 5 CONTINUE
There's a similar section for opening the output data file on unit 17
A typical write (where iw is set to 17) is
write(iw,*) 'igui: ',igui
BK
The first thing to note is that STDCALL is not a valid option in CFTN95$OPTIONS(FIXED,STDCALL). FTN95 does not fault this and at the moment I don't know if that is deliberate or just an oversight. But this makes no difference because you are attempting something like it with F_STDCALL.
The main concern relates to the matching of __cdel and F_STDCALL or STDCALL. The default calling convention for FTN95 is '__cdel' and presumably this is also the default for C++ Builder. In which case you should not use F_STDCALL or STDCALL in the Fortran code.
More to the point, I strongly recommend that you start with a simple test like the one that I outlined above - just a short main program with a very simple call to a Fortran routine. You will probably need to change from my _tmain() (which is for Microsoft C) to main() and it is OK to use LoadLibraryA and GetProcAddress for the dynamic linking but after that, keep it as simple as possible.
In short, my test demonstrates that you can do what you are aiming for, but you should first sort out the matching of the calling conventions (__stdcall versies __cdel).
Quoted from BK I don't pass the file names because past experience shows that the file name strings get garbled.
You can certainly pass file names (or strings in general) from C to Fortran, but you have to use the appropriate calling conventions.
File cmain.c:
#include <stdio.h>
#include <string.h>
extern 'C'{
FTN_SUB(char *str1, char *str2, int l1, int l2);
}
int main(int argc, char *argv[]){
char *s1 = 'file_abc', *s2 = 'file_pqr';
FTN_SUB(s1,s2,strlen(s1),strlen(s2));
}
File ftnsub.f90:
subroutine ftn_sub(s1,s2)
implicit none
character*(*) s1,s2
print '(2x,A,2x,A)',s1,s2
return
end subroutine
Compile, link and run:
scc cmain.c
ftn95 ftnsub.f90
slink cmain.obj ftnsub.obj
cmain
Output:
file_abc file_pqr
Alternatively, put the Fortran code into a DLL, build the DLL, link the C caller with the DLL, and run:
ftn95 ftnsub.f90
slink /dll ftnsub.obj /exportall
slink cmain.obj ftnsub.dll
cmain
Do note that STDCALL has not been used. If you think that you have a need for it in your code, please check and state why.
PaulLaidler, I took your simple fortran routine and added blocks, one at a time. I used the file on unit 10 to write debug lines. It turns out that Silverfrost is much more strict abt WRITE statements than DIGITAL Visual FORTRAN. Complicating the debugging is that once an exception occurs, nothing is written to unit 10.
BTW, TRIM doesn't appear to eliminate trailing spaces. A CHARACTER80 line is still LEN=80 after TRIM. OUTNAM = TRIM(LINE) ILEN = LEN(OUTNAM) OUTNAM (file name path) and LINE are both CHARACTER120.
PROGRAM test_trim
CHARACTER(len=20), PARAMETER :: s = 'Silverfrost '
print*, LEN(s), LEN(TRIM(s))
END PROGRAM
generates
20 11
Quoted from BK ... TRIM doesn't appear to eliminate trailing spaces. A CHARACTER80 line is still LEN=80 after TRIM. OUTNAM = TRIM(LINE) ILEN = LEN(OUTNAM) OUTNAM (file name path) and LINE are both CHARACTER120.
You are not giving a correct interpretation of what happens.
Standard Fortran has no variable length string type. The expression TRIM(LINE) may be a 11-character string, but when it is assigned as the value of OUTNAME, the string value is either padded on the right with spaces or truncated to match the length of OUTNAME.
Whether one likes this behaviour or not, this is what the language rules prescribe.
BK
You have not said whether or not you have made progress. Did you get a modified form of my simple sample to work?
There will be no output after an exception is raised and I don't see how any compiler can do otherwise. Also you don't provide evidence for your statement that FTN95 is stricter.
I guess that everyone blames the tools when things go wrong but up to now you have not demonstrated that FTN95 is at fault.