蟒蛇 |使用 ctypes 访问 dll

Posted

技术标签:

【中文标题】蟒蛇 |使用 ctypes 访问 dll【英文标题】:Python | accessing dll using ctypes 【发布时间】:2011-11-27 00:59:38 【问题描述】:

我正在尝试访问 Firefox 网络浏览器附带的 dll (nss3.dll) 中的一些功能。为了处理这个任务,我在 Python 中使用了 ctypes。问题是它在将 dll 加载到内存时的初始点失败。

这是我必须这样做的代码 sn-p。

>>> from ctypes import *
>>> windll.LoadLibrary("E:\\nss3.dll")

我得到的例外是

Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    windll.LoadLibrary("E:\\nss3.dll")
  File "C:\Python26\lib\ctypes\__init__.py", line 431, in LoadLibrary
    return self._dlltype(name)
  File "C:\Python26\lib\ctypes\__init__.py", line 353, in __init__
    self._handle = _dlopen(self._name, mode)
WindowsError: [Error 126] The specified module could not be found

假设可能存在依赖关系,我还尝试从 Firefox 安装路径加载它。

>>> windll.LoadLibrary("F:\\Softwares\\Mozilla Firefox\\nss3.dll")

但我遇到了与上述相同的异常。

谢谢。

【问题讨论】:

您确定它是 Windows DLL 而不是 C DLL?您是否尝试过 ctypes 库中的 cdll.LoadLibrary 【参考方案1】:

nss3.dll 链接到以下 DLL,它们都位于 Firefox 目录中:nssutil3.dll、plc4.dll、plds4.dll、nspr4.dll 和 mozcrt19.dll。系统库加载器在进程的DLL搜索路径中查找这些文件,包括应用程序目录、系统目录、当前目录以及PATH环境变量中列出的每个目录。

最简单的解决方案是将当前目录更改为 DLL Firefox 目录。但是,这不是线程安全的,所以我一般不会依赖它。另一种选择是将 Firefox 目录附加到 PATH 环境变量,这是我在此答案的原始版本中建议的。但是,这并不比修改当前目录好多少。

较新版本的 Windows(具有更新 KB2533623 的 NT 6.0+)允许通过 SetDefaultDllDirectoriesAddDllDirectoryRemoveDllDirectory 以线程安全的方式更新 DLL 搜索路径。但这种方法在这里过头了。

在这种情况下,为了简单起见并与旧版本的 Windows 兼容,只需调用带有标志 LOAD_WITH_ALTERED_SEARCH_PATHLoadLibraryEx 就足够了。您需要使用绝对路径加载 DLL,否则行为未定义。为方便起见,我们可以将ctypes.CDLLctypes.WinDLL 子类化为调用LoadLibraryEx 而不是LoadLibrary

import os
import ctypes

if os.name == 'nt':
    from ctypes import wintypes

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

    def check_bool(result, func, args):
        if not result:
            raise ctypes.WinError(ctypes.get_last_error())
        return args

    kernel32.LoadLibraryExW.errcheck = check_bool
    kernel32.LoadLibraryExW.restype = wintypes.HMODULE
    kernel32.LoadLibraryExW.argtypes = (wintypes.LPCWSTR,
                                        wintypes.HANDLE,
                                        wintypes.DWORD)

class CDLLEx(ctypes.CDLL):
    def __init__(self, name, mode=0, handle=None, 
                 use_errno=True, use_last_error=False):
        if os.name == 'nt' and handle is None:
            handle = kernel32.LoadLibraryExW(name, None, mode)
        super(CDLLEx, self).__init__(name, mode, handle,
                                     use_errno, use_last_error)

class WinDLLEx(ctypes.WinDLL):
    def __init__(self, name, mode=0, handle=None, 
                 use_errno=False, use_last_error=True):
        if os.name == 'nt' and handle is None:
            handle = kernel32.LoadLibraryExW(name, None, mode)
        super(WinDLLEx, self).__init__(name, mode, handle,
                                       use_errno, use_last_error)

这里是所有可用的LoadLibraryEx 标志:

DONT_RESOLVE_DLL_REFERENCES         = 0x00000001
LOAD_LIBRARY_AS_DATAFILE            = 0x00000002
LOAD_WITH_ALTERED_SEARCH_PATH       = 0x00000008
LOAD_IGNORE_CODE_AUTHZ_LEVEL        = 0x00000010  # NT 6.1
LOAD_LIBRARY_AS_IMAGE_RESOURCE      = 0x00000020  # NT 6.0
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE  = 0x00000040  # NT 6.0

# These cannot be combined with LOAD_WITH_ALTERED_SEARCH_PATH.
# Install update KB2533623 for NT 6.0 & 6.1.
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR    = 0x00000100
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
LOAD_LIBRARY_SEARCH_USER_DIRS       = 0x00000400
LOAD_LIBRARY_SEARCH_SYSTEM32        = 0x00000800
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS    = 0x00001000

例如:

firefox_path = r'F:\Softwares\Mozilla Firefox'
nss3 = CDLLEx(os.path.join(firefox_path, 'nss3.dll'), 
              LOAD_WITH_ALTERED_SEARCH_PATH)

nss3.NSS_GetVersion.restype = c_char_p

>>> nss3.NSS_GetVersion()                 
'3.13.5.0 Basic ECC'

【讨论】:

【参考方案2】:

请注意,ctypes 模块适用于 C 扩展;如果你想用 C++ 编写代码,你可以这样做(C 代码相同):

您的 dll.c 源代码:(您可以毫无问题地使用扩展名为 .cpp 的 C++ 代码)

#include <math.h>

#ifdef __cplusplus
extern "C" 
#endif

__declspec(dllexport) double _sin(double x)

     return sin(x)


#ifdef __cplusplus

#endif

带有管理员身份验证的命令提示符:

使用 C 源代码:

C:\Windows\system32>cl /LD "your_source_path\dll.c" /I "c:\Python33 \include" "c:\Python33\libs\python33.lib" /link/out:dll.dll

使用 C++ 源代码:

C:\Windows\system32>cl /LD "your_source_path\dll.cpp" /I "c:\Python33 \include" "c:\Python33\libs\python33.lib" /link/out:dll.dll

编译器生成 DLL 文件:

Microsoft (R) C/C++ Optimizing Compiler Version 17.00.50727.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

dll.c // or dll.cpp 
Microsoft (R) Incremental Linker Version 11.00.50727.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:dll.dll
/dll
/implib:dll.lib
/out:dll.dll
dll.obj
c:\Python33\libs\python33.lib
   Creating library dll.lib and object dll.exp

您的 Python 模块:

import ctypes
dll = ctypes.CDLL('your_dll_path')
dll._sin.argtypes = [ctypes.c_double]
dll._sin.restype = ctypes.c_double
print(dll._sin(34))


# return 0.5290826861200238

【讨论】:

在 2 天的时间里,我已经在 C++ 和 python 之间运行了 12 个小时左右的基本通信。谢谢你的回答,因为它终于是我想要的。 @ZoranPavlovic,我希望你已经学会了当你的代码根本不使用它时不要链接 python33.lib。您的代码最终将被加载到 Python 进程中,不需要任何编译时链接到 Python DLL。这会很奇怪,而且几乎完全违背了创建 DLL 的意义。 试过了,还是不行。我使用您提供的确切代码使用 dev-C ++ 和 Visual Studio 创建了 dll 2 方式,但它不起作用。我收到错误:找不到指定的模块。错误发生在 python 的负载线上。 @Brana 给出编译后的 DLL 路径,它无需任何修改即可工作。 实际上它现在开始工作了,C++ 在我的电脑上不能正常工作,所以我编译了另一个。现在我安装了visual studio,它没有任何问题。【参考方案3】:

我刚刚遇到了与 ctypes.CDLL 类似的问题,我让它工作,将当前目录更改为库目录并仅按名称加载库(我想将目录放在系统路径中也可以) 所以,而不是

CDLL('C:/library/path/library.dll')

我做到了

os.chdir('C:/library/path')
CDLL('library')

【讨论】:

如果您处于单线程进程中或仅在程序启动时加载 DLL,则可以更改当前目录。通常,更改工作目录不是线程安全的。对于更新的 Windows Vista+(即安装了 KB2533623),您可以改为致电 AddDllDirectorySetDefaultDllDirectories。如果kernel32.AddDllDirectory 不存在,则退回到扩展PATH

以上是关于蟒蛇 |使用 ctypes 访问 dll的主要内容,如果未能解决你的问题,请参考以下文章

使用 ctypes 的 .dll 中的 python 中的 C++ 函数 - 未找到函数并且访问冲突

WindowsError:异常:使用从 C++ 到 Python 的 ctypes 创建 DLL 时出现访问冲突或 Windows 错误 193

使用 ctypes 包装 DLL 函数

使用 ctypes 时 Dll 函数的名称错误 [重复]

ctypes,python3.8:OSError:异常:访问冲突写入0x00000000

Python Ctypes OSError:异常:访问冲突读取0x00000000