Question

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

Was it helpful?

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

OTHER TIPS

OK, I can't reproduce this (Python 2.7.3, Windows 7 x64, Visual C++ Express 2008).

Lib2Dll.h:

#ifdef LIB2DLL_EXPORTS
#define LIB2DLL_API __declspec(dllexport) 
#else
#define LIB2DLL_API __declspec(dllimport) 
#endif

#include "stdafx.h"

class lib2
{
public:
  void LIB2DLL_API func_internal(char *s1, char *s2, double d);
};

Lib2Dll.cpp:

#include "stdafx.h"
#include "Lib2Dll.h"
#include <iostream>

using namespace std;

void lib2::func_internal(char *s1, char *s2, double d)
{
    cout << "String 1: " << s1 << endl << "String 2: " << s2 << endl << "Double value: " << d << endl;
}

Lib1Dll.h:

#ifdef LIB1DLL_EXPORTS
#define LIB1DLL_API __declspec(dllexport) 
#else
#define LIB1DLL_API __declspec(dllimport) 
#endif

#include "stdafx.h"
#include "Lib2Dll.h"

extern "C" {
    void LIB1DLL_API func(char *s1, char *s2, double d);
}

Lib1Dll.cpp:

#include "stdafx.h"
#include "Lib1Dll.h"
#include <iostream>

using namespace std;

lib2 l;

void func(char *s1, char *s2, double d)
{
    cout << "Before" << endl; 
    l.func_internal(s1, s2, d);
    cout << "After" << endl; 
}

There are two projects in the VC++ 2008 solution, Lib2Dll (containing Lib2Dll.h and Lib2Dll.cpp) and Lib1Dll (containing Lib1Dll.h and Lib1Dll.cpp). Lib1Dll depends on Lib2Dll. Both libraries are set to use the cdecl calling convention.

I compiled the solution, and it generated Lib1Dll.dll and Lib2Dll.dll. I then tried to use these libraries in Python:

Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import CDLL, c_double
>>> l1 = CDLL("Lib1Dll")
>>> l1.func("ABC", "XYZ", c_double(3.2463))
Before
String 1: ABC
String 2: XYZ
Double value: 3.2463
After
1618402248

The number 1618402248 printed out at the bottom is a garbage value. ctypes can't determine function return types, so it assumes they all return int. In this case, our function is void, so it doesn't return anything. The return value seen is whatever happened to occupy the memory location where ctypes thought a return value might be. You can tell ctypes that this function's return type is void by setting l1.func.restype = None.

I'm not sure how you call the C++ method in Lib2Dll.dll directly. I tried, and it almost worked, but I got an error, probably because I needed to pass an instance of a class somehow but didn't do so:

>>> l2 = CDLL("Lib2Dll")
>>> func_int = getattr(l2, "?func_internal@lib2@@QAEXPAD0N@Z")
>>> func_int("ABC", "XYZ", c_double(4.2612))
String 1: ABC
String 2: XYZ
Double value: 4.2612
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Procedure called with not enough arguments (16 bytes missing) or wrong calling convention
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top