LF95 can directly access functions in the Windows API, with some limitations. You
will need to have access to Windows API documentation and some knowledge of Windows
Programming in C or C++ to take full advantage of this functionality, since the
API is designed for C and C++.
Complete Windows applications can be written with Lahey Fortran 95 without resorting
to using another language for the user interface. This might not be the best approach
for many people, but examining how to do it can boost one's understanding of the
issues, and these issues can crop up even when creating Windows applications using
other approaches.
An example of this approach can be found in the Win32\Examples\Mix_Lang\WINAPI
subdirectory, in the files WINDEMO.F90, WINDEMO.RC, WINDOWS.F90,
and RUNWINDEMO.BAT
The first step is to compile the file WINDOWS.F90, found in
the Win32\SRC subdirectory. Then USE the module WINDOWS_H
in any procedure that will call the Windows API. WINDOWS.F90
is a Fortran translation of the standard windows header file WINDOWS.H,
which contains definitions for various Windows parameters.
Next declare the API function with the DLL_IMPORT attribute
in a type statement, for example, if you want to call the API function MessageBox:
INTEGER, DLL_IMPORT :: MessageBoxA
Names with the DLL_IMPORT declaration are case sensitive. Elsewhere in your Fortran
program the names of imported procedures are case insensitive.
Here are some more things to consider:
- Compile your code using the -ml winapi, -win, and
-nvsw options.
- When calling Windows API procedures from Fortran you will need to have
DLL_IMPORT statements with the names of all of the API procedures you will
use. These names are case sensitive and you will need to use the correct case in
the DLL_IMPORT statement. Elsewhere in your Fortran program
code the case for these procedure names does not matter, though it's a good idea
for clarity's sake to retain the case used in the Windows API. A good place for
these DLL_IMPORT statements is in the module you create for
your parameter declarations.
- If you have a resource file called MYRC.RC, compile it by adding MYRC.RC to the
LF95 command line. You need to include WINDOWS.H (supplied
with LF95 in the Win32/SRC subdirectory) in your resource
file. LF95's driver will call RC.EXE (the resource compiler
which ships with LF95 and with various other Windows compilers) to create
MYRC.RES This will then be linked with the other objects and libraries
you specified on the command line.
- Any new item you create with a #define in your resource file
needs to be declared as an INTEGER parameter in your Fortran source so that it is
accessible in the scoping unit in which it is referenced. It is cleanest to put
all of these parameter declarations in a module.
- Void API functions must be called as subroutines from Fortran and API functions
which return values must be called as functions from Fortran.
- Many of the API functions you call will need to have the letter 'A' appended to
the function name. This calls the ASCII (rather than the Unicode) version of the
function. If the linker gives you an unresolved external message on an API function
you think you've declared properly, try appending an 'A' to the name. It is a good
bet that API functions that deal with character strings will require the 'A'.
- API function arguments that do not map to Fortran intrinsic types need to be declared
in your Fortran program. Declare structure arguments as SEQUENCE derived types.
Declare pointers (to anything, including strings) as INTEGERs.
- Whenever you pass a numeric argument use CARG For example:
call PostQuitMessage(carg(0))
Whenever you pass a pointer argument use CARG(POINTER(argument))
instead of argument For example:
type (WNDCLASS):: wc
result=RegisterClassA(carg(pointer(wc))
Whenever you pass a pointer to CHARACTER, remember that C requires null-terminated
strings. CARG will make a copy of a string and null-terminate
it for you. However, because a copy is made, the original value cannot be changed
by the function you call. For example:
result = SendDlgItemMessageA(carg(hwnd), &
carg(IDC_LIST1, &
carg(LB_ADDSTRING), &
carg(0), &
carg(string))
To pass a string you want the function to change, null-terminate the string manually
and then use CARG of the POINTER Note
that you can use CHAR(0) to generate a null. For example:
character(len=81) :: mystr ! leave space for trailing null
mystr = trim(mystr(1:80)) // char(0)
call SomeAPIRoutineA(carg(pointer(mystr)))
Wherever on the right-hand side of a C assignment statement you would use the ampersand
character to get the address of something, you will need to use POINTER in your
Fortran program. For example:
wc%lpszClassName = pointer(szClassName)
is equivalent to the C:
wc.lpszClassName = &szClassName;
Callback procedures, where Windows will be calling a Fortran procedure, must not
be module procedures or internal procedures.
To set up a callback procedure, include an interface block defining the callback
procedure and declaring it to be ml_external. Then use the POINTER
of the procedure name. For example:
interface
integer function WndProc(hwndByValue, &
messageByValue, &
wParamByValue, &
lParamByValue)
ml_external WndProc
integer :: hwndbyValue, messageByValue, &
wParamByValue, lParamByValue
end function WndProc
end interface
type(WNDCLASS):: wc
wc%lpfnWndProc = offset(WndProc)
Arguments to a Fortran callback procedure are values (C passes by value). To make
these work in your callback procedure, assign the pointer of these values to local
variables. For example:
integer function WndProc(hwndByValue, &
messageByValue, &
wParamByValue, &
lParamByValue)
implicit none
ml_external WndProc
integer :: hwnd, message, wParam, lParam
integer :: hwndByValue, messageByValue
integer :: wParamByValue, lParamByValue
hwnd = pointer(hwndByValue)
message = pointer(messageByValue)
wParam = pointer(wParamByValue)
lParam = pointer(lParamByValue)
! do not reference the ByValue arguments from here on !
See windows.f90 in the SRC directory for examples of functions, types, and definitions
for use in Windows API programming.