Python | 使用ctypes访问dll

19

我正在尝试访问Firefox Web浏览器附带的dll文件(nss3.dll)中的一些函数。为了处理此任务,我已经在Python中使用了ctypes。问题是它在将dll加载到内存时失败了。

以下是我用来加载dll的代码片段。

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

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

谢谢。


2
你确定它是Windows DLL而不是C DLL吗?你尝试过使用ctypes库中的cdll.LoadLibrary吗? - g.d.d.c
3个回答

22

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

最简单的解决方法是将当前目录更改为DLL Firefox目录。但是,这不是线程安全的,因此我不会通常依赖它。另一种选择是将Firefox目录附加到PATH环境变量中,这就是我在我的答案的原始版本中建议的做法。但是,这与修改当前目录没有太大区别。

新版本的Windows(NT 6.0+ KB2533623更新)允许通过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'

11
请注意,ctypes模块可以与C扩展一起使用;如果你想用C++编写代码,可以按照以下方式操作(C代码相同):
Your dll.c source:(你可以使用.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
我已经花费了大约12个小时的时间,在两天内努力让C++和Python之间的基本通信运行起来。非常感谢您提供的答案,因为这终于是我一直在寻找的。 - Zoran Pavlovic
1
@ZoranPavlovic,希望您已经学会了在代码根本不使用它时不要链接python33.lib。您的代码最终将在Python进程中加载,这不需要在编译时链接到Python DLL。那将是奇怪的,并且几乎完全打败了创建DLL的目的。 - Eryk Sun
尝试过了但不起作用。我用Dev-C++和Visual Studio两种方式创建了dll,使用您提供的完全相同的代码,但仍然不起作用。我收到错误消息:指定的模块找不到。这个错误发生在Python的load行上。 - Brana
1
@Brana 给出编译后的 DLL 路径,它可以直接使用,无需任何修改。 - Reza Ebrahimi
实际上现在它开始工作了,C++ 在我的电脑上无法正常工作,所以我在另一台电脑上编译了它。现在我安装了 Visual Studio,它可以毫无问题地运行。 - Brana
“__declspec(dllexport)”是我错过的,也是导致我所有问题的原因... 在Linux中你永远不需要这种垃圾... - SomethingSomething

5

我刚遇到了一个与ctypes.CDLL类似的问题,我通过将当前目录更改为库目录并仅按名称加载库来解决它(我猜将目录放在系统路径中也可以起作用)。

因此,不是:

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

我完成了

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

2
如果您在单线程进程中或仅在程序启动时加载DLL,则更改当前目录是可以的。一般来说,更改工作目录不是线程安全的。对于更新的Windows Vista+(即已安装KB2533623),您可以调用AddDllDirectorySetDefaultDllDirectories。如果kernel32.AddDllDirectory不存在,则回退到扩展PATH - Eryk Sun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接