且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

ctypes中的find_library()

更新时间:2023-02-03 12:33:16

在Windows上, find_library 搜索 PATH 环境变量中的目录,该目录不是真正的 Windows加载程序所使用的桌面应用程序搜索顺序。值得注意的是, find_library 不包括应用程序目录和当前目录。



调用Windows SearchPath 会更近一些,但在DLL 激活上下文和其他API,例如 SetDllDirectory 或更新的API SetDefaultDllDirectories AddDllDirectory



鉴于没有简单的方法来复制Windows加载程序使用的搜索,只需使用 CDLL $ c按名称加载DLL $ c>(cdecl)或 WinDLL (stdcall):

  nidaq_cdecl = ctypes.CDLL('NIDAQmx')
nidaq_stdcall = ctypes.WinDLL('NIDAQmx')

您可以在运行时动态地将DLL目录添加到 PATH (与Linux加载程序的缓存 LD_LIBRARY_PATH 在启动时)。例如,假设您的DLL依赖项位于程序包的 dlls子目录中。您可以在此目录之前添加以下内容:

  import os 

basepath = os.path.dirname(os.path.abspath(__ file__))
dllspath = os.path.join(basepath,'dlls')
os.environ ['PATH'] = dllspath + os .pathsep + os.environ ['PATH']






或者,您可以使用上下文管理器,该上下文管理器调用 GetDllDirectory SetDllDirectory 临时修改当前工作目录通常占用的搜索位置。请记住,就像修改 PATH 一样,这会修改全局进程数据,因此在使用多个线程时应格外小心。这种方法的优点是它不会修改 CreateProcess 用于查找可执行文件的搜索路径。

  import os 
import ctypes
from ctypes import wintypes
from contextlib import contextmanager

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

def check_dword(result,func,args):
if result == 0:
last_error = ctypes.get_last_error( )
如果last_error!= 0:
提高ctypes.WinError(last_error)
返回args

def check_bool(result,func,args):
如果没有结果:
last_error = ctypes.get_last_error()
如果last_error!= 0:
抛出ctypes.WinError(last_error)
否则:
引发OSError
return args

kernel32.GetDllDirectoryW.errcheck = check_dword
kernel32.GetDllDirectoryW.argtypes =(wintypes.DWORD,#_In _ _ nBufferLength
wintypes。 b
@contextmanager
def use_dll_dir(dll_dir):
size = newsize = 0
而newsize> =大小:
size = newsize
prev = (ctypes.c_wchar * size)()
newsize = kernel32.GetDllDirectoryW(size,prev)
kernel32.SetDllDirectoryW(os.path.abspath(dll_dir))
试试:
最后产生

kernel32.SetDllDirectoryW(prev)

例如:

 如果__name__ =='__main__':
basepath = os.path.dirname( os.path.abspath(__ file__))
dllspath = os.path.join(basepath,'dlls')
with use_dll_dir(dllspath):
nidaq = ctypes.CDLL('NIDAQmx' )

当然,如果您只想在启动时设置一次DLL目录,则问题会简单得多。只需直接调用 SetDllDirectoryW






另一种方法是调用 LoadLibraryEx ,带有标志 LOAD_WITH_ALTERED_SEARCH_PATH ,该标志可将加载的DLL目录临时添加到搜索路径中。您需要使用绝对路径加载DLL,否则行为是不确定的。为了方便起见,我们可以将 ctypes.CDLL ctypes.WinDLL 子类化,以调用 LoadLibraryEx 而不是 LoadLibrary

 从ctypes中导入ctypes 
import wintypes

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

def check_bool(结果,函数,args ):
如果没有结果:
提高ctypes.WinError(ctypes.get_last_error())
返回args

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

类CDLLEx (ctypes.CDLL):
def __init __((自身,名称,模式= 0,句柄=无,
use_errno = True,use_last_error = False):
如果句柄为无:
手柄= kerne l32.LoadLibraryExW(名称,无,模式)
super(CDLLEx,self).__ init __(名称,模式,句柄,
use_errno,use_last_error)

类WinDLLEx(ctypes。 WinDLL):
def __init __(自身,名称,模式= 0,句柄=无,
use_errno = False,use_last_error = True):
如果handle为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_EL_V_10 = 6.1 LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x0000002 0#NT 6.0
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040#NT 6.0

#这些不能与LOAD_WITH_ALTERED_SEARCH_PATH结合使用。
#为NT 6.0和更高版本安装更新KB2533623。 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
$ RS $ p>

例如:

 如果__name__ =='__main__':
basepath = os.path.dirname(os.path.abspath(__ file__))
dllpath = os.path.join(basepath,'dlls','NIDAQmx.dll' )
nidaq = CDLLEx(dllpath,LOAD_WITH_ALTERED_SEARCH_PATH)


I am trying to use the command find_library() from ctypes but I'm getting an error that I don't understand its reason. I am working on Windows

This is the code:

import ctypes
from ctypes.util import find_library
import numpy
from string import atoi
from time import sleep

# Class constants
#nidaq = ctypes.windll.nicaiu
nidaq  = ctypes.cdll.LoadLibrary(find_library('NIDAQmx'))

And this is the error I'm getting:

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    nidaq  = ctypes.cdll.LoadLibrary(find_library('NIDAQmx'))
  File "C:\Python27\lib\ctypes\__init__.py", line 443, in LoadLibrary
    return self._dlltype(name)
  File "C:\Python27\lib\ctypes\__init__.py", line 365, in __init__
    self._handle = _dlopen(self._name, mode)
TypeError: expected string or Unicode object, NoneType found

Should I place NIDAQmx in a specific place for example so that it can be found? Or this is unrelated?

Thanks!

On Windows, find_library searches the directories in the PATH environment variable, which isn't the real search order for desktop applications that's used by the Windows loader. Notably find_library doesn't include the application directory and the current directory.

Calling Windows SearchPath would be closer, but not much closer given DLL activation contexts and other APIs such as SetDllDirectory or the newer APIs SetDefaultDllDirectories and AddDllDirectory.

Given there's no simple way to replicate the search that's used by the Windows loader, just load the DLL by name using either CDLL (cdecl) or WinDLL (stdcall):

nidaq_cdecl   = ctypes.CDLL('NIDAQmx')
nidaq_stdcall = ctypes.WinDLL('NIDAQmx')

You can add the DLL directory to PATH dynamically at runtime (in contrast to the Linux loader's caching of LD_LIBRARY_PATH at startup). For example, say your DLL dependencies are in the "dlls" subdirectory of your package. You can prepend this directory as follows:

import os

basepath = os.path.dirname(os.path.abspath(__file__))
dllspath = os.path.join(basepath, 'dlls')
os.environ['PATH'] = dllspath + os.pathsep + os.environ['PATH']


Alternatively, you can use a context manager that calls GetDllDirectory and SetDllDirectory to temporarily modify the search slot that's normally occupied by the current working directory. Bear in mind that, just like modifying PATH, this modifies global process data, so care should be taken when using multiple threads. An advantage to this approach is that it doesn't modify the search path that CreateProcess uses to find executables.

import os
import ctypes
from ctypes import wintypes
from contextlib import contextmanager

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

def check_dword(result, func, args):
    if result == 0:
        last_error = ctypes.get_last_error()
        if last_error != 0:
            raise ctypes.WinError(last_error)
    return args

def check_bool(result, func, args):
    if not result:
        last_error = ctypes.get_last_error()
        if last_error != 0:
            raise ctypes.WinError(last_error)
        else:
            raise OSError
    return args

kernel32.GetDllDirectoryW.errcheck = check_dword
kernel32.GetDllDirectoryW.argtypes = (wintypes.DWORD,  # _In_  nBufferLength
                                      wintypes.LPWSTR) # _Out_ lpBuffer

kernel32.SetDllDirectoryW.errcheck = check_bool
kernel32.SetDllDirectoryW.argtypes = (wintypes.LPCWSTR,) # _In_opt_ lpPathName

@contextmanager
def use_dll_dir(dll_dir):
    size = newsize = 0
    while newsize >= size:
        size = newsize
        prev = (ctypes.c_wchar * size)()
        newsize = kernel32.GetDllDirectoryW(size, prev)
    kernel32.SetDllDirectoryW(os.path.abspath(dll_dir))
    try:
        yield
    finally:
        kernel32.SetDllDirectoryW(prev)

For example:

if __name__ == '__main__':
    basepath = os.path.dirname(os.path.abspath(__file__))
    dllspath = os.path.join(basepath, 'dlls')
    with use_dll_dir(dllspath):
        nidaq = ctypes.CDLL('NIDAQmx')

Of course, if you're only interested in setting the DLL directory once at startup the problem is much simpler. Just call SetDllDirectoryW directly.


Another approach is to call LoadLibraryEx with the flag LOAD_WITH_ALTERED_SEARCH_PATH, which temporarily adds the loaded DLL directory to the search path. You need to load the DLL using an absolute path, else the behavior is undefined. For convenience we can subclass ctypes.CDLL and ctypes.WinDLL to call LoadLibraryEx instead of LoadLibrary.

import ctypes
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 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 handle is None:
            handle = kernel32.LoadLibraryExW(name, None, mode)
        super(WinDLLEx, self).__init__(name, mode, handle,
                                       use_errno, use_last_error)

Here are all of the available LoadLibraryEx flags:

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

For example:

if __name__ == '__main__':
    basepath = os.path.dirname(os.path.abspath(__file__))
    dllpath = os.path.join(basepath, 'dlls', 'NIDAQmx.dll')
    nidaq = CDLLEx(dllpath, LOAD_WITH_ALTERED_SEARCH_PATH)