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

Map console output?

Bradley_P_1
Beginner
623 Views

We have a lot of legacy Fortran code that we would like to be able to continue using but we'd like to update it somewhat.  For example, we have a number of Fortran executables that send their output to the console, either with "print*" or with "write(*,10)".  We would like to turn the executable into a DLL and route the console output to a callback.  I've had no trouble, in my proof-of-concept, turning an executable into a DLL, and no trouble using a callback, but I have no idea where to begin to reroute console output to the callback.

The point of this is that I need to turn

PRINT*,OutString

into

CALL WriteToCaller( OutString )

That, of course, is the simple case.  I could do that with global search/replace.  It gets much more difficult with a "write" statement with a format statement and a list of variables:

WRITE(*,10) (var1(var2(j)),j=1,num)

So, it's impractical to replace all of our console writes.  It would be much simpler if we could redirect them somehow.  Is this possible?  If so, can someone please describe to me how to do it or point me to documentation that I could follow?

One important caveat:  the application can take a long time to run, so I'd like the output to be somewhat real-time.  So, for example, capturing all console output and presenting it at the end is not sufficient.

Thanks!

Brad.

0 Kudos
9 Replies
jimdempseyatthecove
Honored Contributor III
623 Views

Look at the MSDN article regarding DllMain. Also this.

When you detect the process attaching, you will have to use different Windows API to obtain the handle to the "Console", which may be abstract in a GUI application. Using this handle you can redirect output to this handle to code you provide.

Here is something I used when trying to debug asserts from a Fortran .dll called from a C# main.

! enable this section to get console ouput for debugging of MIC offloads
function DllMain (hInstDLL, fdwReason, lpReserved)
!DEC$ ATTRIBUTES STDCALL, DECORATE, DLLEXPORT, ALIAS:"DllMain" :: DllMain
    USE IFWINTY
    USE Kernel32
    
    IMPLICIT NONE
    integer(BOOL) :: DllMain
    integer(HANDLE), intent(IN) :: hinstDLL
    integer(DWORD), intent(IN) :: fdwReason
    integer(LPVOID), intent(IN) :: lpReserved
    
    character(500) :: eVar
    character(500) :: fName
    logical :: exists
    logical, save :: FirstTime = .true.
    
    integer(BOOL) :: b
    integer(HANDLE) :: hConFile
    interface
        INTEGER FUNCTION internal_crames_try_offload_mic(iMIC)
            integer :: iMIC
        end FUNCTION internal_crames_try_offload_mic
    end interface
    
    select case(fdwReason)
    case (DLL_PROCESS_DETACH)
       ! ...
        DllMain = TRUE
    case (DLL_PROCESS_ATTACH)
!DIR$ IF(.FALSE.)
     b = AllocConsole()
!DIR$ ELSE
        hConFile = CreateFile("C:\\DebugOutput\\ConsoleOutput.txt"C, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
        b=SetStdHandle(STD_OUTPUT_HANDLE, hConFile)
!DIR$ ENDIF
        write(*,*) "***************************"
        CALL GET_ENVIRONMENT_VARIABLE("MIC_LIBRARY_PATH",eVar)
        write(*,*) "MIC_LIBRARY_PATH=",TRIM(eVar)
        CALL GET_ENVIRONMENT_VARIABLE("MIC_LD_LIBRARY_PATH",eVar)
        write(*,*) "MIC_LD_LIBRARY_PATH=",TRIM(eVar)
        write(*,*) "***************************"
    case (DLL_THREAD_ATTACH)
        ! ...
    case (DLL_THREAD_DETACH)
        ! ...
    case default
        ! ...
    end select
    DllMain = TRUE
    write(*,*) "DllMain(",hInstDLL, ",", fdwReason, ",", lpReserved,")"
    end function DllMain

Maybe you can mung this up for your needs.

Jim Dempsey

0 Kudos
Steven_L_Intel1
Employee
623 Views

That might work for the console, but not for file units, and it doesn't really help with getting a procedure to be called for I/O.

What I was thinking was using OPEN or defining an environment variable to redirect the Fortran I/O unit to a named pipe. Then in a separate thread, have a routine that reads from the pipe and calls the desired routine. You'd probably need one for each unit.

0 Kudos
Bradley_P_1
Beginner
623 Views

jimdempseyatthecove wrote:

Look at the MSDN article regarding DllMain. Also this.

I didn't get much from those, unfortunately.

jimdempseyatthecove wrote:

Here is something I used when trying to debug asserts from a Fortran .dll called from a C# main.

That code looks very promising.  The part I'm struggling with now is creating a named pipe rather than a file.  Any ideas or pointers?

Thanks!

Brad.

0 Kudos
Bradley_P_1
Beginner
623 Views

Steve Lionel (Intel) wrote:

What I was thinking was using OPEN or defining an environment variable to redirect the Fortran I/O unit to a named pipe. Then in a separate thread, have a routine that reads from the pipe and calls the desired routine. You'd probably need one for each unit.

Apparently our comments crossed in the ether.  :-)

As you can see from my most recent post, I was thinking, too, that I need to "redirect the Fortran I/O unit to a named pipe​".  The problem is that I have no idea how to do that, especially not on the Fortran side.

Brad.

0 Kudos
Steven_L_Intel1
Employee
623 Views

I've never done this, but I think the sequence would be:

Call the Windows API routine CreateNamedPipe, giving a name that you hope will be unique on the system. You can construct it using the parent process ID as part of the name, for example. Let's say you call it 'CONSOLE0043'. Save the handle it returns.

In an OPEN statement, specify FILE='\\.\pipe\CONSOLE0043'. (See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365783(v=vs.85).aspx ) For the console, open unit 6 and make sure you build with /standard-semantics or /assume:noold_unit_star.

After you close the unit, call CloseHandle on the saved handle.

This takes care of the writing. You'd then need to use CreateThread to create a thread per pipe that opens the pipe (no need to create it again), reads from it and does whatever processing you want.

0 Kudos
Bradley_P_1
Beginner
623 Views

Steve Lionel (Intel) wrote:

I've never done this, but I think the sequence would be:

Call the Windows API routine CreateNamedPipe, giving a name that you hope will be unique on the system. You can construct it using the parent process ID as part of the name, for example. Let's say you call it 'CONSOLE0043'. Save the handle it returns.

In an OPEN statement, specify FILE='\\.\pipe\CONSOLE0043'. (See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365783(v=vs.85).aspx ) For the console, open unit 6 and make sure you build with /standard-semantics or /assume:noold_unit_star.

After you close the unit, call CloseHandle on the saved handle.

This takes care of the writing. You'd then need to use CreateThread to create a thread per pipe that opens the pipe (no need to create it again), reads from it and does whatever processing you want.

OK, I think we're getting closer, but unfortunately I have no idea how to do these things in Fortran.  Anyone out there that has done this and can provide some sample code?

Thanks!

Brad.

0 Kudos
IanH
Honored Contributor II
623 Views

Be very careful what you do in DLLMain.  The practical limitations are severe - e.g. it is possible for a mere variable declaration to result in the the Fortran compiler generating code that requires an allocation from the heap, which may require the C runtime library to be loaded and initialized, and you may not be able to count on that in DLLMain.  There be dragons, with sharp teeth and bad breath.

This post has some example code of handle redirection in Fotran, using the IFWIN stuff, when calling a child executable.  In the file attached to that post see the check_program_output internal procedure.  With the large caveat that it has been a while since I played with this stuff, so I might be spruiking nonsense (well, there's always the risk of that regardless of the passage of time), but you may be able to adapt something like this.  However I have recollection of trying that from in-process, using SetStdHandle or similar, and the results being variable and not being satisfactory.

Philosophically, there are benefits and costs to DLL'ising something.  Depending on your situation (how tightly integrated you need things), you may find that it is better to keep the Fortran code as a separate process anyway, in which case the example is directly relevant.  You can still have quite efficient interchange of things like inputs and outputs through things like memory mapped files and the like.  One large benefit is if the child process carks it for whatever arbitrary reason, then your parent process can relatively easily recover.

 

 

0 Kudos
jimdempseyatthecove
Honored Contributor III
623 Views

IanH,

Thanks for the warning about what can/cannot be performed in DllMain. I should have included that in the comments of the code sample.

>> You can still have quite efficient interchange of things like inputs and outputs through things like memory mapped files and the like.  One large benefit is if the child process carks it for whatever arbitrary reason, then your parent process can relatively easily recover.

That was one of the major benefits I "loved" having with the Intel Array Visualizer (long defunct). This is the API you use from the program and not the "toy" feature of using it from the debugger. Once configured, your compute process can churn away, with occasional updating of the memory mapped file (database), with exceptionally low overhead. The display process, or multiple processes, can be started at will. For example, it was handy for me to launch three viewers from my simulation program (no extra code in the simulation program), with one view of a 3D graph from the X viewpoint, and one from the Y viewpoint, and then an animated 2D chart and/or spreadsheet-like tables (all browseable). Occasionally when a viewer crashes (bug in viewer), it would not take down the simulation, which may take days to complete.

That product put the "Visual" into Intel Visual Fortran.

Jim Dempsey

0 Kudos
Bradley_P_1
Beginner
623 Views

I think at this point that I'll put this on hold.  My Fortran is so rusty that I'd need a more complete sample to get me where I need to go.

Thanks anyway!

Brad.

0 Kudos
Reply