Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

FORTRAN I/O to USB devices 1

Status
Not open for further replies.

PaulWDent

Technical User
May 18, 2012
13
US
I have seen a lot of unresolved questions on how to do FORTRAN I/O to a USB device.
All suggested solutions are for your FORTRAN program to call a mass of ugly-C code

that in turn calls a mass of ugly Windows APIs. But it ought to be as simple as

executing a FORTRAN OPEN statement to get a FORTRAN unit number.
I am well on the way to figuring out how to do that, but there are still a few
gaps in my knowledge. So I post what I have found so far in the hope that it may
inspire other FORTRAN programmers to persevere and help fill in the blanks.

It is already possible in FORTRAN to do I/O to a serial or parallel port simply by

OPEN(UNIT=12,FILE="COM1") or OPEN(UNIT=12,FILE="LPT1)

"COM1" and "LPT1" are aliasses known to the system for the serial and parallel
ports respectively.
Compaq Visual Fortran and I guess IVF too publish the following list of I/O device

names:

Device Description
CON Console (standard output)
PRN Printer
COM1 Serial port #1
COM2 Serial port #2
COM3 Serial port #3
COM4 Serial port #4
LPT1 Parallel Port #1
LPT2 Parallel Port #2
LPT3 Parallel Port #3
LPT4 Parallel Port #4
NUL NULL device. Discards all output; contains no input
AUX Serial port #1
LINE 1 Serial port #1
USER 1 Standard output
ERR 1 Standard error
CONOUT$ Standard output
CONIN$ Standard input

You see nothing about USB devices there.
The reason is, different things can be plugeed into a USB port at different times.
So names for USB devices have to be created ad-hoc, and are very long strings.

You can find the first part of the required string to be used as a filename in
a FORTRAN OPEN statement as follows (at least in XP):

Go to Start, right click on My Computer, and select Properties from the dropdown

list. Select Hardware in the window that opens, then Device Manager.
The screen that opens shows all hardware devices with human-recognizable names.
Double click on your desired USB device and then choose "details" in the window
that opens next.
You will then see a "Device Instance ID" displayed.
This is the first part of the string you will need.
You can't cut and paste it, so I took a screenshot and trimmed it in Paint before
saving it as a .jpg.

The second part of the string that has to be appended to the first part,
I believe is the address of a registry entry that contains the driver filename.
You can get the second part of the string as follows:

Download Winobj.exe from here


then run Winobj.exe

In the left pane of the window that opens, select the folder "Globals"

In the right pane you will now have all the names in the system nametable listed.

Scroll down until you find the string (or strings) whose first part matches the

Device Instance Id you found above. You now have the whole string that has to be

assigned to a Fortran character variable to be used as the filename in an OPEN

statement.
I find two matches, and I believe one is for I and one is for O. You have to

replace the # characters that separate parts of the string by the \ character.
You also may have to preface the string with \\.\ or \\?
So Fortran I/O to a USB device should boil down to something as simple as

CHARACTER*64 USBIN,USBOUT
USBIN="\\.\USB\...........\............\{;;;;;;;;;;;;;;;;;;;}"
USBOUT="\\.\USB\...........\............\{;;;;;;;;;;;;;;;;;;;}"
OPEN(UNIT=12,FILE=USBOUT)
OPEN(UNIT=13,USBIN)
WRITE(12,100)BUFOUT
READ(13,200)BUFIN

where of course the formats 100 and 200 and the structure of BUFIN and BUFOUT will

be dependent on what your USB device is expecting.

I write the strings as I am seeing them. You do not have to add additional {}

brackets; they should be in the namestring already. But you do have to switch the #

characters to \ characters.

I have also determined that the entries in the nametable only exist as long as your

USB device is connected, and disappear when it is diconnected. However, when it is

reconnected, the whole namestring fortunately is identical to what it was before.

However, I can't guarantee that it would be the same on a different machine.

So a big question, if you want to write portable programs
(which I don't need to do at present), is how the program
itself could find these strings. Even more fundamental:
How do you even describe to the program what to look for?
I can't imagine needing a program so smart that you could
just ask it to "find me a GPIB-USB adapter".
This sounds like the "service discovery" operation that Bluetooth
performs, that enables it look for a device in a predefined class,
such as "Printer".
Are there such device classes, defined by predetermined strings,
and is "GPIB adapter" one of them?
This is the gap in my knowledge that I hope others can fill in.
Preferably with code (in FORTRAN!)
 
To build in C
Code:
#include <stdio.h>                  //gets C's non-native I/O library
#include "sicl.h"                   //Gets some vendor-supplied 
declarations
int main ()
{
   INST id;                            //Wht's the type? Integer? What length?
   char buf[256] = { 0 };
   id = iopen("gpib0,0");              //This string is not a Windows device name listed
                                    // in the name table
                                    //If I write OPEN(13,   file="gpib0,4") (because my GPIB-USB
                                    //adapter is port 4),  it still doesn't work
   itimeout(id, 1000);
   iprintf(id, "*RST\n");
   ipromptf(id, "*IDN?\n", "%t", buf);
   printf("%s\n", buf);
   iclose(id);
   return 0;
}
The routines with prefix I are specific to sicl. If you have the templates for

iopen
itimeout
ipromptf
iprintf
iclose

The latest versions of Fortran have a C/Fortran interface which supports the library directly. Have a look at Example from Intel
The problem you might have is when the parameter to the routine is ... Are there any such routines (I suspect iprintf is one of them)
 
A C-program is somewhat strange, as you allready found out. it needs a lot of declarations up front, which is contained in this header file sicl.h (and a lot of others).

If you want to use this - I have no idea how much work would be involved here and if you have any other options - the steps would be about this:

(1) Transscribe sicl.h to a fortran module.
if you have the Compaq-compiler available refer to the document about mxed language programming. There you find a table which c-type corresponds to which fortran type and how to do the function prototyping

(2) Compile this module and refrence it in a use-statement to your fortran code.

(3) call the c-functions in your fortran program.

The problem is the transcription of the headerfile. But you can find a lot of examples on how this is done in your Compaq-compiler directory. In my installation this is

c:\program files\microsoft visual studio\df98\include

There you find some c-headerfiles and their corresponding fortran modules. This should show how it is done.
If sicl.h is not too big - and does not include other headers again - this should be feasible.

Norbert



The optimist believes we live in the best of all possible worlds - the pessimist fears this might be true.
 
Thanks for all the suggestions above.
I am trying another thing. I got some Pascal code from Ulrich Bangert in Germany that he used in his free program EXGPIB, which is great and I had it working in 5 minutes. The Pascal code gave e a clue how I might proceed in Fortran.

What he does is

DLLaddress = LOADLIBRARY('visa32.dll')

to get the address of the DLL that exports the important functions.

One of the functions is called 'viOpen'

So he then does

VIOPEN = GetProcAddress(DLLaddress,'viOpen')

He can then call that routine VIOPEN, knowing its address.

However this is challenging in Fortran. The functions LOADLIBRAY and GetProcaddress are indeed available to CVQ through USE DFWIN

However, GetProcAddress is a C function that expects DLLaddress to be passed by value.
I can find no way in Fortran to pass an argument by value in a call. I can receive an argument by value in a call from C program by writing

!DECL VALUE argument

However, the puzzle is, it seems to work in FORTAN, at least as far as gettng an address to 'viOpen'. It seems to find the address to all the functions. I can prove that by misspelling any one of them in the call to GetProcSAddress, and it retruns zero for that missing function, as it should. But then there is another problem. The addresses are way high and the code there doesn't make sense. I get an access violation if I try to go to those addresses. So that leads to another question as to whether the address returned by GetProcAddress is absolute or relative to something. When you Google Microsoft for that information, all those specifics are always missing!!
They even use the term "absolute offset" in one place.

The next problem is, there is no way in Fortran to call a function by its address.
Here is one method I tried:

I passed in the address of the routine in a variable 'SUBNAM' to another FORTRAN subroutine. Of course it was passed by the address of the variable SUBNAM, not by value. But inside the subroutine, I declared SUBNAM to be external, and then called it. It crashed, and I went into debugger to look at what was happening. I found this very strage fact: The value of SUBNAM received by the subroutine depends on whether the statement EXTERNAL SUBNAM was included in the subroutine or not.

So, I decided I needed an "indirect call" facility. So I wrote a little assembler program to take in the routine address found by GetProcAddress plus its argument addresses, push the arguments on the stack in the right order and by value or address as appropriate, then with the routime address in Eax I do "Call Eax" Also tried Call [Eax]. Crash Crash!

Am I right in thinking that passing by value should never have been allowed? If Microsoft had chosen to write everything in C, or C had never chosen passing by value, we woudnlt have such headaches, right?

When I am Emperor I will make a law: All arguments must be passed by address.

Not that Fortran is flawless: I think that if I have the address of a routine in a variable SUBNAM, I should be able to write CALL SUBNAM(.....)

 
Some remarks:

Argument passing rules in Fortran are called "by reference" (don't use "by address" funny term) AND "by value-result" (see also VALUE attribute).

So if you are Emperor we lose the Fortran programming language (or at least lose call statement and function calls;)...
 
Well, Paul,
I am loosing it a bit on what we are doing or at least trying to do. So I think we should get straight on some of the basics. For me these are
What do you have got ?
What do you want to achieve ?

What I understood was, that you have a couple of gauges, that you can plug in to your computer via an USB-plug and that you want to get the readings of the gauges into you PC.

If that is correct, then the question would be:
What software / driver have you got to handle this ?
I am very much in doubt that you can handle a USB-port without a proper driver. And I guess that USB is very much different from GPIB or HPIB, just by having a look at the number of pins that are present in the corresponding plugs. So you need some driver that 'speaks USB', that is able to have your USB-hub to forward your request to the proper instrument and return the proper instrument's response to the USB port and inform your PC that it is the data that you requested that is coming through now and confirm all this with a handshake. Of course you could do your own driver, but this seems to be very demanding, take a look at the articles on USB in WikiPedia to get an idea on what would be involved.

So in my undertstanding of the problem you should get yourdelf a driver. If this driver is in C, C++, VB or any such thing you can build your interface to this prog to be able to call the routines from your fortran program. And this part - only this part - I may be able to give you some support on.

I guess worming your way through what the internet has to say on such things will not get you any further, just as you encountered it, I believe that any new information gives rise to more new questions than it answers.

Okay, Paul, please understand, I do not want to be rude on you, but I do not feel adequate to answer all your detailed questions in your posts in a way that would be helping you forward.

Norbert


The optimist believes we live in the best of all possible worlds - the pessimist fears this might be true.
 
Have a look in the include directory in your IVF installation. There are files called kernel32.F90 and kernel32.mod. Kernel32 has the interface definitions for LoadLibrary and GetProcAddress.
 
ArkM: You can only place the "VALUE" attribute on an argument WITHIN the subroutine. So it can tell a subroutoine to ACCEPT an argument by value (e.g. from a call from a C program) but you cannot tell a Fortran program that calls a C-program to pass an argument OUT by value.

XWB: Thanks for the suggestion to look in Kernel32.F90. I didn't know that existed.

Norbert: I didn't expect the answer to my problem to be readily available, but I am receiving some useful suggestions from you all. I started this trhead because I saw other people were also asking similar quesions about how, in Fortram, you can handle other than standard I/O devices.

Each device indeed must have a driver that is registered in Windows, but unfortunately Fortran does not seem to provide the necessary hooks. Here is my "wishlist" for future implementations of Fortran in Windows to solve this:

-Upon invoking a Fortran IDE, it should scan Windows to discover ALL the installed device names and include those names in its "standard I/O devices" table along with COM1...4, LPT1...4 etc and also publish them in a "Help" file.

-Writing "CALL OPEN(IUNIT=13,FILE=devicename.....) should be translated to a link to a call through windows to the installed driver for the device. I believe that Windows performs a level of abstraction to the driver by making the driver conform to certain rules, such as accepting a Device Control Block (DCB) in a standard format

-When you then write WRITE(13,format)variable list, the results of processing the variable list through the format statement are a byte string. The location and length of the byte string are then filled in to the DCB and the DCB passed through Windows to the driver.


If the above existed, 90% of my problem would be solved. It would remain to know what byte strings each driver wanted. In the absence of documentation, this could be found by using a snooper program to look at the byte strings passed by other programs.

 
The problem with kernel32.f90 is that it takes ages to build. You can extract bits out of it and just go with that
Code:
! Extracted bits from kernel32.f90
module kernel32
use dfwinty
use kernel32_1
INTERFACE 
   FUNCTION LoadLibrary( &
        lpLibFileName)
   USE DFWINTY
      integer(HANDLE) :: LoadLibrary ! HMODULE
      !MS$ ATTRIBUTES  STDCALL,  ALIAS:'_LoadLibraryA@4' :: LoadLibrary
      !MS$ ATTRIBUTES REFERENCE  :: lpLibFileName
      character*(*) lpLibFileName ! LPCSTR lpLibFileName
   END FUNCTION
END INTERFACE

INTERFACE GetProcAddress
   FUNCTION GetProcAddress_G1( &
        hModule, &
        lpProcName)
   USE DFWINTY
      integer(LPVOID) :: GetProcAddress_G1 ! FARPROC
      !MS$ ATTRIBUTES  STDCALL,  ALIAS:'_GetProcAddress@8' :: GetProcAddress_G1
      integer(HANDLE) hModule ! HMODULE hModule
      !MS$ ATTRIBUTES REFERENCE  :: lpProcName
      character*(*) lpProcName ! LPCSTR lpProcName
   END FUNCTION

   FUNCTION GetProcAddress_G2( &
        hModule, &
        lpProcName)
   USE DFWINTY
      integer(LPVOID) :: GetProcAddress_G2 ! FARPROC
      !MS$ ATTRIBUTES  STDCALL,  ALIAS:'_GetProcAddress@8' :: GetProcAddress_G2
      integer(HANDLE) hModule ! HMODULE hModule
      integer(LPVOID) lpProcName ! LPCSTR lpProcName
   END FUNCTION
END INTERFACE

INTERFACE 
   FUNCTION FreeLibrary( &
        hLibModule)
   USE DFWINTY
      integer(BOOL) :: FreeLibrary ! BOOL
      !MS$ ATTRIBUTES  STDCALL,  ALIAS:'_FreeLibrary@4' :: FreeLibrary
      integer(HANDLE) hLibModule ! HMODULE hLibModule
   END FUNCTION
END INTERFACE

end module
!
! Calling a C function.  Fortran doesn't check that you've
! passed a INTEGER(LPVOID) in place of a subroutine
subroutine callc(extfunc, in_num)
   interface
       subroutine extfunc (in_x, out_2x)
          !DEC$ ATTRIBUTES C :: extfunc
          integer:: in_x, out_2x
       end subroutine
    end interface
    integer:: in_num
    integer:: val
    
    call extfunc(in_num, loc(val))
    print *, in_num, val
end subroutine

    program calldll
    use Kernel32
    ! Variables
    integer(HANDLE):: clib
    integer(BOOL):: ierr
    integer(LPVOID):: printstr
        
    ! Load the library - unix use dlopen
    clib = LoadLibrary ("cdll.dll"C)

    ! Get the address - unix use dlsym
    printstr = GetProcAddress (clib, "printstr"C)

    call callc(%val(printstr), 10)

    ! Free the library - unix use dlclose
    ierr = FreeLibrary (clib)
    end program calldll
 
Thanks XWB. I am browsing Kernel32.f90 now.


That callc and calldll inteface you found look like they will be useful for me.


There are lots of other things in the include folder to Compaq Visual Fortran that don't seem to be documented. Paul Dent
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top