[Tutor] reg: How to import dll in python

eryksun eryksun at gmail.com
Thu Mar 6 03:52:14 CET 2014


On Wed, Mar 5, 2014 at 7:49 AM, Shweta Kaushik <k-shweta at hcl.com> wrote:
>
> Please find code used to create dll:

I hardly ever use C++, so C++ gurus feel free to correct any errors
here. I just wanted a basic C API to work with ctypes -- since that's
the Python aspect of this question. I've also only targeted
Microsoft's compiler since the question is using a Windows DLL.

First and foremost, in a C++ project you need to use `extern "C"` to
get C linkage without C++ name mangling of exported symbols.

My example uses the default __cdecl calling convention. You can switch
to __stdcall if you wish. ctypes automatically handles stdcall name
decoration.

A C++ method uses the __thiscall convention that implicitly passes the
`this` pointer (e.g. for x86 MSVC passes it in the ECX register). I
handle this in the __cdecl C API by using stub functions and a pointer
to an opaque structure.

FYI, native x64 code only has one calling convention (for now). For an
x64 target, MSVC ignores calling convention keywords such as __stdcall
and __thiscall.

Creating the stub functions manually is tedious, and prone to
introducing bugs. Big projects automate this with SWIG, SIP, etc. You
could also use Boost.Python or Cython. But probably all of the latter
are off topic for python-tutor. If we're sticking with CPython's
standard library, then ctypes is the only game in town.

OK, enough blabber.

mathematics.h:

    #ifndef MATHEMATICS_H
    #define MATHEMATICS_H

    #ifdef BUILD_MATHEMATICS
    #define DLLAPI __declspec(dllexport)
    #else
    #define DLLAPI __declspec(dllimport)
    #endif

    #ifdef __cplusplus
    class Mathematics {
        int x, y;
        public:
            void input();
            int add();
    };
    #else /* !__cpluplus */
    typedef struct _Mathematics Mathematics;
    #endif

    #ifdef __cplusplus
    extern "C" {
    #endif
    DLLAPI Mathematics *math_create();
    DLLAPI void math_free(Mathematics *);
    DLLAPI void math_input(Mathematics *);
    DLLAPI int math_add(Mathematics *);
    #ifdef __cplusplus
    }
    #endif

    #endif /* !MATHEMATICS_H */


mathematics.cpp:

    #include <new>
    #include <iostream>
    #include "mathematics.h"

    using namespace std;

    void Mathematics::input()
    {
        cout << "Input two integers" << endl;
        cin >> x >> y;
    }

    int Mathematics::add()
    {
        return x + y;
    }

    Mathematics *math_create()
    {
        Mathematics *self = nullptr;
        try
        {
            self = new Mathematics;
        }
        catch(bad_alloc &ba)
        {
            cerr << "bad_alloc: " << ba.what() << endl;
        }
        return self;
    }

    void math_free(Mathematics *self)
    {
        delete self;
    }

    void math_input(Mathematics *self)
    {
        self->input();
    }

    int math_add(Mathematics *self)
    {
        return self->add();
    }


mathematics.dll, mathematics.lib:

    cl mathematics.cpp /EHsc /MD /LD /DBUILD_MATHEMATICS


mathtest.c:

    #include <stdlib.h>
    #include <stdio.h>
    #include "mathematics.h"

    #pragma comment(lib, "mathematics.lib")

    int main() {
        Mathematics *m = math_create();
        if (m == NULL)
            return EXIT_FAILURE;
        math_input(m);
        printf("Result = %d\n", math_add(m));
        math_free(m);
        return EXIT_SUCCESS;
    }


Test run:

    C:\>mathtest.exe
    Input two integers
    -10 15
    Result = 5


mathematics.py:

    import os
    import ctypes

    libpath = os.path.join(os.path.dirname(__file__),
                           'mathematics.dll')
    lib = ctypes.CDLL(libpath)

    class _CMathematics(ctypes.Structure): pass
    CMathematics = ctypes.POINTER(_CMathematics)

    def check_alloc(result, func, args):
        if not result:
            raise MemoryError
        return args

    lib.math_create.restype = CMathematics
    lib.math_create.errcheck = check_alloc

    lib.math_free.restype = None
    lib.math_free.argtypes = [CMathematics]

    lib.math_input.restype = None
    lib.math_input.argtypes = [CMathematics]

    lib.math_add.restype = ctypes.c_int
    lib.math_add.argtypes = [CMathematics]

    class Mathematics(object):
        _lib = lib     # for __del__
        _handle = None
        def __init__(self):
            self._handle = lib.math_create()

        def __del__(self):
            if self._handle:
                self._lib.math_free(self._handle)

        def input(self):
            lib.math_input(self._handle)

        def add(self):
            return lib.math_add(self._handle)

    del os, ctypes

    if __name__ == '__main__':
        m = Mathematics()
        m.input()
        print("Result = %d" % m.add())


I cached lib in the class to enable Mathematics.__del__ to work during
shutdown. Without that, the __del__ method might try to use lib after
Python has already set it to None. This way avoids spurious errors.
Generally avoid using global names in __del__.

CDLL calls the platform loader, such as Windows LoadLibrary or POSIX
dlopen. If the shared library is already mapped in the process, both
LoadLibrary and dlopen will increment the reference count and return a
handle to the already loaded library. Generally you don't have to
worry about calling Windows FreeLibrary or POSIX dlclose to decrement
the reference count. Just leave the library loaded for the life of the
process.

CDLL uses the cdecl calling convention. For stdcall, use the WinDLL
subclass. There's also OleDLL for use with COM, but if you need COM
you'll probably find it easier to use the comtypes package that's
built on top of ctypes.

If you're accessing a DLL that's in the system search path, you can
also use cdll and windll, e.g. cdll.msvcrt or windll.kernel32. Windows
LoadLibrary automatically appends the .dll extension. Note that
cdll.LoadLibrary is pretty much pointless. It just returns a CDLL
instance.

Getting an attribute of a library will automatically create and cache
a function pointer for the export with the given name. If a DLL
exports a symbol by ordinal only, use subscription instead of
attribute access, e.g. use `lib[10]` to get ordinal 10.

The default result type (restype) is c_int, which AFAIK is 32-bit on
all platforms supported by CPython. c_int is also the default when
converting a Python integer as an argument. Keep that in mind when
working with 64-bit pointers. You'll need to make sure argtypes and
restype are set to use a pointer type.


More information about the Tutor mailing list