Подтвердить что ты не робот

Python | доступ к dll с использованием ctypes

Я пытаюсь получить доступ к некоторым функциям в dll (nss3.dll), который поставляется с веб-браузером Firefox. Чтобы справиться с этой задачей, я использовал ctypes в Python. Проблема заключается в том, что он не работает в начальной точке, которая возникает при загрузке 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")

Но у меня такое же исключение, как указано выше.

Спасибо.

4b9b3361

Ответ 1

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) путь поиска DLL можно обновлять поточно-безопасным способом через SetDefaultDllDirectories, AddDllDirectory и RemoveDllDirectory. Но этот подход был бы наверху здесь.

В этом случае для простоты и совместимости со старыми версиями Windows достаточно вызвать LoadLibraryEx с флагом LOAD_WITH_ALTERED_SEARCH_PATH. Вам нужно загрузить DLL, используя абсолютный путь, иначе поведение undefined. Для удобства мы можем подклассы ctypes.CDLL и ctypes.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 тот же):

Источник dll.c: (вы можете использовать код С++ с расширением .cpp без каких-либо проблем)

#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:\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

Ответ 3

У меня была аналогичная проблема с ctypes.CDLL, и я получил ее, чтобы сменить текущий каталог на каталог библиотеки и загрузить библиотеку только по имени (я думаю, что установка каталога в системный путь тоже будет работать) Итак, вместо

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

Я сделал

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