Search code examples
pythondllpython-cffi

How to call a __cdel decorated function with cffi?


Hi @eryksun and Python experts:

I am trying to import a dll file into python with cffi with the following commands:

from cffi import FFI
ffi=FFI()
lib=ffi.dlopen('ATC3DG.DLL')

From a previous question about ctypes, I know that DLL are all cdecl (thanks to @eryksun's hints), and I was able to access its functions in the following fashion:

from ctypes import *
lib=windll.LoadLibrary('ATC3DG.DLL')
lib._InitializeBIRDSystem

However, I am not sure what would be the equivalent operations in cffi. lib._InitializeBIRDSystem works under ctypes but not cffi. Any suggestion?

Thanks for looking into this,

Erik


Solution

  • The symbols exported by atc3dg.dll use a leading underscore, which is unusual for cdecl. You'll have to add the underscore in the definitions used in ffi.cdef, just as you did with ctypes.

    import cffi
    
    ffi = cffi.FFI()
    ffi.cdef('''
    enum MESSAGE_TYPE
    {
        SIMPLE_MESSAGE,     // short string describing error code
        VERBOSE_MESSAGE,    // long string describing error code
    };
    
    int _InitializeBIRDSystem(void);
    
    int _GetErrorText(
        int                 errorCode,
        char                *pBuffer,
        int                 bufferSize,
        enum MESSAGE_TYPE   type
        );
    ''')
    
    lib = ffi.dlopen('atc3dg.dll')
    buf = ffi.new('char[100]')
    
    err = lib._InitializeBIRDSystem()
    lib._GetErrorText(err, buf, len(buf), lib.SIMPLE_MESSAGE)
    print(ffi.string(buf).decode('ascii'))
    
    # output:
    # System         : No BIRDs were found anywhere
    

    If you have a C compiler configured, you can use ffi.verify(). I'm not very experienced with cffi (not yet, but it looks promising), so take this with a grain of salt.

    I had to make a small change to the header. In the definition of COMMUNICATIONS_MEDIA_PARAMETERS, line 500 needs to add enum as follows:

    enum COMMUNICATIONS_MEDIA_TYPE mediaType;
    

    Also, just as an FYI, the compiler printed a few warnings, such as "conversion from 'unsigned __int64' to 'unsigned short', possible loss of data". I haven't followed up on this in the generated source for the extension module.

    import os
    import cffi
    import subprocess
    
    pth = os.path.abspath(os.path.dirname(__file__))
    os.environ['PATH'] += ';%s' % pth
    
    # preprocess: modify this for your compiler
    # Using Microsoft's cl.exe that comes with VC++.
    # cdef chokes on __declspec, so define DEF_FILE.
    cmd = 'cl.exe /DDEF_FILE /EP %s' % os.path.join(pth, 'atc3dg.h')
    hdr = subprocess.check_output(cmd, universal_newlines=True)
    
    ffi = cffi.FFI()
    ffi.cdef(hdr)
    
    # using __declspec(dllimport) links more efficiently,
    # but skipping it still works fine
    lib = ffi.verify(hdr, library_dirs=[pth], libraries=['atc3dg'])
    buf = ffi.new('char[100]')
    
    err = lib.InitializeBIRDSystem()
    lib.GetErrorText(err, buf, len(buf), lib.SIMPLE_MESSAGE)
    print(ffi.string(buf).decode('ascii'))
    # output:
    # System         : No BIRDs were found anywhere
    

    On the plus side, this approach avoids the leading underscore. It's an ABI detail handled by the linker when the extension module is compiled.