【问题标题】:ctype: WindowsError: exception: access violation readingctype:WindowsError:异常:访问冲突读取
【发布时间】:2013-04-27 08:01:14
【问题描述】:

我的环境:Windows Vista 64、Python3.1、Visual Studio 2008 sp1

错误描述: 我正在尝试使用 ctype 包装一个 dll(例如 Lib.dll),当继续执行 Lib.dll 中的一个函数(例如 func())时,发生错误:

"WindowsError: exception: access violation reading"

我认为 func() 和以前写在我的 '.py' 文件中的 Lib.dll 函数之间的不同之处在于 func() 包含一个 DOUBLE 类型参数,但我确信该参数传递正确,因为我已使用c_double() 进行投射。并且似乎在输入func() 时出现错误,因为func() 中的第一个代码(printf()) 没有执行。

我也试过在C环境下运行同一个dll及其函数,运行流畅。

更多信息:

lib.dll 在 MSVC 中编译(使用 extern "C"),我使用 CDLL 作为调用类型。问题实际上发生在另一个 dll (lib2.dll) 中的函数上。我使用 lib.dll 只是因为 lib2.dll 是用 C++ 编写的,并且我在 lib2 中包装了我想要的函数。它看起来像这样:

///////////////////////////////////
// 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))
////////////////////////////////////

当我使用 ctype 加载 lib2.dll 时,它也无法工作,这也很奇怪。它显示未找到该功能。所以我必须使用 lib.dll 来调用 lib2.dll 中的函数。

谁能给我一些提示?谢谢

【问题讨论】:

  • 放一些代码。 from ctypes import * ...
  • 这是我对为什么不能直接调用 lib2.dll 的猜测:en.wikipedia.org/wiki/Name_mangling#Name_mangling_in_C.2B.2B
  • 谢谢回复。我知道在 MSVC 中编译时,如果没有 extern "C" 或 .def 文件,名称将被破坏,并且 func() 会变成类似 '_func@YZ' 的东西。但即使我使用那些(.def 等),或者使用 'func = getattr(lib2, '_func@YZ')',它仍然不起作用。
  • 顺便说一句,我可以在 MSVC 的另一个项目中加载没有 lib2.lib 的 lib2.dll,使用 loadlibrary(),它可以工作。
  • 在致电func 之前尝试dll.func.argtypes = [c_char_p,c_double]。此外,您的func 电话有两个c_char_p,但应该只有一个。

标签: python dll ctypes windowserror


【解决方案1】:

ctypes 用于 C,但您可以编写一个包装器来公开 C++ 类。由于您提到您使用 Python 3.1,我还注意到您有 c_char_p('c1') 其中 'c1' 是一个 Unicode 字符串。由于提供的示例不是一个完整的示例,无法按原样重现问题,因此很难判断您遇到了什么问题。

以下是一个完整的工作示例。您可以通过运行“nmake”从 Visual Studio 命令提示符构建它。

lib1.cpp

此包装器将 C++ 对象“扁平化”为 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);
}

制作文件

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

输出

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

替代方案

由于编写包装器可能很乏味,最好使用boost::PythonCythonSWIG 之类的东西。我最熟悉 SWIG,所以这里有另一个例子:

制作文件

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

输出

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

【讨论】:

  • 谢谢你,@Mark Tolonen,我认为你的“扁平化”C++ 对象的解决方案非常聪明!关于替代方案,我在使用 ctype 前几天尝试使用 SIP。如果 ctype 不能满足我的要求,也许我会切换到 SIP 或 SWIG。
  • 我尝试运行您的示例,但是当我对 makefile 使用“nmake”时它失败了。我进入了 msvs2008 “命令提示符”并设置了文件的路径(使用 cd /d c:\temp),但出现链接错误。我的msvs2008是不是有问题,还是需要添加libpaths等参数?
  • “未解决的外部符号 XXX”。我换了一台机器,它工作了。也许我应该重新安装我的 msvs2008...
  • 很高兴你知道了。如果答案对您有用,请考虑投票和/或接受答案。
  • 我重新安装了我的 MSVS2008,并通过 SIP 成功加载了我的 dll。我希望我可以投票给你的答案,但我现在没有足够的声誉。无论如何,谢谢你们所有的人!
【解决方案2】:

好的,我无法重现这个(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; 
}

VC++ 2008 解决方案中有两个项目,Lib2Dll(包含 Lib2Dll.h 和 Lib2Dll.cpp)和 Lib1Dll(包含 Lib1Dll.h 和 Lib1Dll.cpp)。 Lib1Dll 依赖于 Lib2Dll。两个库都设置为使用 cdecl 调用约定。

我编译了解决方案,它生成了 Lib1Dll.dll 和 Lib2Dll.dll。然后我尝试在 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

底部打印出来的数字1618402248是垃圾值。 ctypes 无法确定函数返回类型,因此假定它们都返回 int。在这种情况下,我们的函数是void,所以它不返回任何东西。看到的返回值是发生在ctypes 认为返回值可能所在的内存位置的任何事情。你可以通过设置l1.func.restype = None来告诉ctypes这个函数的返回类型是void

我不确定您如何直接调用 Lib2Dll.dll 中的 C++ 方法。我试过了,它几乎成功了,但我得到了一个错误,可能是因为我需要以某种方式传递一个类的实例但没有这样做:

>>> 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

【讨论】:

  • 非常感谢为我做这个测试。由于我正在度假,直到今天我才能访问我的代码。抱歉这么晚才回复。在 lib2.dll 中输入我的 func() 时出现的“WindowsError”问题是由于调用了我今天才发现的旧版本的 lib2.dll(dll hell) 引起的。根据您的程序,还可以确保问题不是由参数传递引起的。我还删除了 func() 中的所有参数以检查错误。现在我可以在lib2.dll中输入func(),但是我遇到另一个“WindowsError”...
  • 我会继续检查新问题。再次感谢您的帮助!
猜你喜欢
  • 2013-03-06
  • 2014-11-01
  • 1970-01-01
  • 2018-05-19
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 2019-06-19
  • 2023-03-14
相关资源
最近更新 更多