1
votes

I am trying to learn how to complie FORTRAN code into a DLL that I can call from Python using ctypes. Even a simple example is not working, can anyone help?

I have a single procedure in FORTRAN:

  subroutine ex(i)
  integer i
  i=i+1
  return
  end 

Then I try to run this from Python

I compile it with the MinGW complier as follows

gfortran -c test.f
gfortran -shared -mrtd -o test.dll test.o

Looking at the DLL created I see

Microsoft (R) COFF/PE Dumper Version 12.00.30723.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file test.dll

File Type: DLL

Section contains the following exports for test.dll

00000000 characteristics
       0 time date stamp Thu Jan 01 13:00:00 1970
    0.00 version
       1 ordinal base
       1 number of functions
       1 number of names

ordinal hint RVA      name

      1    0 00001280 ex_

Summary

    1000 .CRT
    1000 .bss
    1000 .data
    1000 .edata
    1000 .eh_fram
    1000 .idata
    1000 .rdata
    1000 .reloc
    1000 .text
    1000 .tls

Then I try to access this from Python

from ctypes import *

DLL = windll.test
print DLL

print getattr(DLL,'ex_')
print DLL[1]
print DLL.ex_

x = pointer( c_int(3) )
DLL.ex_( x )

The output is

<WinDLL 'test', handle 6bec0000 at 2143850>

<_FuncPtr object at 0x020E88A0>
<_FuncPtr object at 0x020E8918>
<_FuncPtr object at 0x020E88A0>
Traceback (most recent call last):
  File "C:\proj_py\test.py", line 20, in <module>
    DLL.ex_( x )
ValueError: Procedure probably called with too many arguments (4 bytes in excess) 

So although the function is there, I'm not calling it correctly. I'm stumped.

I am using python 2.7.10 (32 bit) on a 64-bit Windows-7 machine, I have a recent version of the MinGW compiler:

$ gfortran -v
Using built-in specs.
COLLECT_GCC=c:\mingw\bin\gfortran.exe
COLLECT_LTO_WRAPPER=c:/mingw/bin/../libexec/gcc/mingw32/4.8.1/lto-wrapper.exe
Target: mingw32
Configured with: ../gcc-4.8.1/configure --prefix=/mingw --host=mingw32 --build=mingw32 --without-pic --enable-shared --e
nable-static --with-gnu-ld --enable-lto --enable-libssp --disable-multilib --enable-languages=c,c++,fortran,objc,obj-c++
,ada --disable-sjlj-exceptions --with-dwarf2 --disable-win32-registry --enable-libstdcxx-debug --enable-version-specific
-runtime-libs --with-gmp=/usr/src/pkg/gmp-5.1.2-1-mingw32-src/bld --with-mpc=/usr/src/pkg/mpc-1.0.1-1-mingw32-src/bld --
with-mpfr= --with-system-zlib --with-gnu-as --enable-decimal-float=yes --enable-libgomp --enable-threads --with-libiconv
-prefix=/mingw32 --with-libintl-prefix=/mingw --disable-bootstrap LDFLAGS=-s CFLAGS=-D_USE_32BIT_TIME_T
Thread model: win32
gcc version 4.8.1 (GCC) 

Can anyone offer a solution?

Thanks

1
The Fortran subroutine in the DLL is probably really expecting a pointer. Try x = byref(3) in your Python instead of x = c_int(3). If that works, I'll post a longer explanation as an answer.Brick
I see that you edited your question since my first comment, and the error message changed. That's good, because one problem down. Now it looks like you're getting some incompatibility between pointers of different sizes. Hard to see from here what exactly is wrong, but it looks like you're passing a 64 bit pointer into a library expecting a 32 bit pointer. (That's a 4 byte difference - Exactly what your error message reports.) @BlairBrick
Your library is using cdecl (the caller cleans the stack), not stdcall (callee cleanup). Load it as dll = CDLL('test').Eryk Sun
It seems that @eryksun has the answer. Thanks :-) I thought the use of the option -mrtd when linking was invoking stdcall. So what is that option doing?Blair
Sorry, I missed that. -mrtd is a compile option, not a link option. Use gfortran -c -mrtd test.f if you want stdcall. In that case load the DLL using dll = WinDLL('test').Eryk Sun

1 Answers

3
votes

Fortran is generally pass-by-reference. When calling from other languages like C, that means passing a memory address into the Fortran subroutine. Apparently the Python implementation is similar. Before your edit, you had an error that said something like "Illegal access at 0x000003" which was suspiciously the same value "3" as you were trying to pass as the value. You entered the Fortran subroutine just fine, but when it tried to do the add, it looked for the integer at memory location 3 instead of using the value 3 in the addition itself.

After your edit, you are passing a pointer (based on my comment, I think). That gives a different error. It suggests that you passed an extra argument because it got 4 bytes more data than it expected. I think that's likely an incompatibility between some 32 bit libraries and some 64 bit libraries, with 4 bytes being a likely difference in length between pointers in the two architectures.