Search code examples
pythondllctypeswindowserror

ctype: WindowsError: exception: access violation reading


My environment: Windows Vista 64, Python3.1, Visual Studio 2008 sp1

Error Description: I am trying to wrap a dll (eg. Lib.dll) using ctype, when proceeding to one of the function (eg. func()) in Lib.dll, an error occurred:

"WindowsError: exception: access violation reading"

I think the different between func() and previous functions of Lib.dll written in my '.py' file is that func() contains a DOUBLE type parameter, but I am sure that the parameter are passed correctly, since I have used c_double() to cast it. And it seems the error occurred when entering func(), since the first code (printf()) inside func() does not execute.

I also tried to run the same dll and its functions in C environment, it runs smoothly.

Further Information:

the lib.dll is compiled in MSVC (with extern "C"), and I am using CDLL for the calling type. The problem is actually happened on a function in another dll (lib2.dll). I use lib.dll only because the lib2.dll is written in C++ and I wrap the functions that I want in lib2. It looks like this:

///////////////////////////////////
// lib.cpp

lib2 l;

void func(char * c1, char * c2, double d) 
{
    printf("%s, %s, %f\n", c1, c2, d); // All the parameters are passed correctly
    l.func(c1, c2, d);                 // THIS IS where the error occurred
}
///////////////////////////////////
// lib2.cpp

void lib2::func(char * c1, char * c2, double d)
{
    printf();  // The error happened before this line being executed.
    ...
}
///////////////////////////////////
And python script looks like this:
// my.py

dll = cdll.LoadLibrary("lib.dll")

dll.some_func1(c_char_p('a'))
dll.some_func2(c_char_p('b'))

func(c_char_p('c1'), c_char_p('c2'), c_double(1.1))
////////////////////////////////////

it is also weird that lib2.dll cannot work when I use ctype to load it. It shows the function is not found. So I have to use lib.dll to call functions in lib2.dll.

Could anyone give me some hints? Thanks


Solution

  • ctypes is for C, but you can write a wrapper to expose a C++ class. Since you mentioned you use Python 3.1, I also noted you have c_char_p('c1') where 'c1' is a Unicode string. Since the example provided is not a complete example that can be used as is to reproduce the problem, it's difficult to tell what problem you are having.

    Below is a complete, working example. You can build it from a Visual Studio command prompt by running "nmake".

    lib1.cpp

    This wrapper "flattens" the C++ object into a C API.

    #include "lib2.h"
    extern "C" {
    __declspec(dllexport) lib2* lib2_new() { return new lib2; }
    __declspec(dllexport) void lib2_delete(lib2* p) { delete p; }
    __declspec(dllexport) void lib2_func(lib2* p, char* c1, char* c2, double d) {
        p->func(c1,c2,d);
    }
    }
    

    lib2.h

    #ifdef LIB2_EXPORTS
    #   define LIB2_API __declspec(dllexport)
    #else
    #   define LIB2_API __declspec(dllimport)
    #endif
    
    class LIB2_API lib2
    {
    public:
        void func(char * c1, char * c2, double d);
    };
    

    lib2.cpp

    #include <stdio.h>
    #include "lib2.h"
    
    void lib2::func(char * c1, char * c2, double d)
    {
        printf("%s %s %f\n",c1,c2,d);
    }
    

    makefile

    all: lib1.dll lib2.dll
    
    lib1.dll: lib1.cpp lib2.dll
        cl /nologo /LD /W4 lib1.cpp -link lib2.lib
    
    lib2.dll: lib2.cpp lib2.h
        cl /nologo /LD /W4 /D LIB2_EXPORTS lib2.cpp
    

    test.py

    #!python3
    from ctypes import *
    
    class lib2:
    
        lib1 = CDLL('lib1')
        # It's best to declare all arguments and types, so Python can typecheck.
        lib1.lib2_new.argtypes = []
        lib1.lib2_new.restype = c_void_p # Can use this for an opaque pointer.
        lib1.lib2_func.argtypes = [c_void_p,c_char_p,c_char_p,c_double]
        lib1.lib2_func.restype = None
        lib1.lib2_delete.argtypes = [c_void_p]
        lib1.lib2_delete.restype = None
    
        def __init__(self):
            self.obj = self.lib1.lib2_new()
    
        def __del__(self):
            self.lib1.lib2_delete(self.obj)
    
        def func(self,c1,c2,d):
            self.lib1.lib2_func(self.obj,c1,c2,d)
    
    o = lib2()
    o.func(b'abc',b'123',1.2) # Note byte strings
    

    Output

    C:\temp>nmake
    
    Microsoft (R) Program Maintenance Utility Version 11.00.50727.1
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
            cl /nologo /LD /W4 /D LIB2_EXPORTS lib2.cpp
    lib2.cpp
       Creating library lib2.lib and object lib2.exp
            cl /nologo /LD /W4 lib1.cpp -link lib2.lib
    lib1.cpp
       Creating library lib1.lib and object lib1.exp
    
    C:\temp>test.py
    abc 123 1.200000
    

    Alternatives

    Since writing wrappers can be tedious, it is better to use something like boost::Python, Cython, or SWIG. I'm most familiar with SWIG, so here's another example:

    makefile

    all: _lib2.pyd lib2.dll
    
    PYTHON_ROOT = c:\python33
    
    lib2_wrap.cxx: lib2.i
        @echo Generating wrapper...
        swig -c++ -python lib2.i
    
    _lib2.pyd: lib2_wrap.cxx lib2.dll
        cl /nologo /EHsc /MD /LD /W4 /I$(PYTHON_ROOT)\include lib2_wrap.cxx -link lib2.lib /LIBPATH:$(PYTHON_ROOT)\libs /OUT:_lib2.pyd
    
    lib2.dll: lib2.cpp lib2.h
        cl /nologo /LD /W4 /D LIB2_EXPORTS lib2.cpp
    

    lib2.i

    %module lib2
    
    %begin %{
    #pragma warning(disable:4127 4211 4706)
    %}
    
    %{
    #include "lib2.h"
    %}
    
    %include <windows.i>
    %include "lib2.h"
    

    test.py

    #!python3
    import lib2
    o = lib2.lib2()
    o.func('abc','123',1.2) #Note SWIG encodes Unicode strings by default
    

    Output

    C:\temp>nmake /las
    Generating wrapper...
    lib2.cpp
       Creating library lib2.lib and object lib2.exp
    lib2_wrap.cxx
       Creating library lib2_wrap.lib and object lib2_wrap.exp    
    
    C:\temp>test
    abc 123 1.200000