Skip to content

Commit

Permalink
Use a standard search path for DLL loading in windows (#235)
Browse files Browse the repository at this point in the history
Since Python 3.8, ctypes uses an altered search path when loading DLLs
in Windows which only includes the application directory, the system32
directory, and directories manually specified with add_dll_directory.
None of those suit us.  The standard search path can be specified with
`winmode=0`.

This commit adds a new function to loads libraries with `winmode=0`
and makes all modules that load shared libraries use it.
  • Loading branch information
David Miguel Susano Pinto committed Aug 22, 2023
1 parent 6a4ee44 commit 03e6b5c
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 18 deletions.
5 changes: 5 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ Version 0.7.0 (upcoming)

* Microscope is now dependent on Python 3.7 or later.

* Python 3.8 changed the default DLL search path in Windows which
caused all cameras, deformable mirrors, and Linkam stage to fail to
load. This version restores the use of Windows standard search
path.


Version 0.6.0 (2021/01/14)
--------------------------
Expand Down
33 changes: 33 additions & 0 deletions microscope/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
## You should have received a copy of the GNU General Public License
## along with Microscope. If not, see <http://www.gnu.org/licenses/>.

import ctypes
import os
import sys
import threading
import typing

Expand All @@ -43,6 +46,36 @@
)


def library_loader(
libname: str, dlltype: typing.Type[ctypes.CDLL] = ctypes.CDLL, **kwargs
) -> ctypes.CDLL:
"""Load shared library.
This exists mainly to search for DLL in Windows using a standard
search path, i.e, search for dlls in ``PATH``.
Args:
libname: file name or path of the library to be loaded as
required by `dlltype`
dlltype: the class of shared library to load. Typically,
``CDLL`` but sometimes ``WinDLL`` in windows.
kwargs: other arguments passed on to ``dlltype``.
"""
# Python 3.8 in Windows uses an altered search path. Their
# reasons is security and I guess it would make sense if we
# installed the DLLs we need ourselves but we don't. `winmode=0`
# restores the use of the standard search path. See issue #235.
if (
os.name == "nt"
and sys.version_info >= (3.8)
and "winmode" not in kwargs
):
winmode_kwargs = {"winmode": 0}
else:
winmode_kwargs = {}
return dlltype(libname, **winmode_kwargs, **kwargs)


class OnlyTriggersOnceOnSoftwareMixin(microscope.abc.TriggerTargetMixin):
"""Utility mixin for devices that only trigger "once" with software.
Expand Down
6 changes: 4 additions & 2 deletions microscope/_wrappers/BMC.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import os
from ctypes import c_char, c_char_p, c_double, c_int, c_uint, c_uint32

import microscope._utils


if os.name == "nt": # is windows
# Not actually tested yet
SDK = ctypes.WinDLL("BMC2")
SDK = microscope._utils.library_loader("BMC2", ctypes.WinDLL)
else:
SDK = ctypes.CDLL("libBMC.so.3")
SDK = microscope._utils.library_loader("libBMC.so.3")


# Definitions from BMCDefs.h
Expand Down
5 changes: 3 additions & 2 deletions microscope/_wrappers/asdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
import os
from ctypes import c_char_p, c_double, c_int, c_size_t, c_uint32

import microscope._utils


if os.name == "nt": # is windows
_libname = "ASDK"
else:
_libname = "libasdk.so" # Not actually tested yet

SDK = ctypes.CDLL(_libname)
SDK = microscope._utils.library_loader(_libname)


class DM(ctypes.Structure):
Expand Down
6 changes: 4 additions & 2 deletions microscope/_wrappers/dcamapi4.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
import enum
import os

import microscope._utils


if os.name == "nt":
_LIB = ctypes.WinDLL("dcamapi")
_LIB = microscope._utils.library_loader("dcamapi", ctypes.WinDLL)
else:
_LIB = ctypes.CDLL("libdcamapi.so")
_LIB = microscope._utils.library_loader("libdcamapi.so", ctypes.CDLL)


if os.name == "nt":
Expand Down
4 changes: 3 additions & 1 deletion microscope/_wrappers/mirao52e.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@

import ctypes

import microscope._utils


# Vendor only supports Windows
SDK = ctypes.WinDLL("mirao52e")
SDK = microscope._utils.library_loader("mirao52e", ctypes.WinDLL)


TRUE = 1 # TRUE MroBoolean value
Expand Down
18 changes: 14 additions & 4 deletions microscope/cameras/_SDK3.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import os
from ctypes import POINTER, c_double, c_int, c_uint, c_void_p

import microscope._utils


#### typedefs
AT_H = ctypes.c_int
Expand All @@ -34,12 +36,20 @@
_stdcall_libraries = {}

if os.name == "nt": # is windows
_stdcall_libraries["ATCORE"] = ctypes.WinDLL("atcore")
_stdcall_libraries["ATUTIL"] = ctypes.WinDLL("atutility")
_stdcall_libraries["ATCORE"] = microscope._utils.library_loader(
"atcore", ctypes.WinDLL
)
_stdcall_libraries["ATUTIL"] = microscope._utils.library_loader(
"atutility", ctypes.WinDLL
)
CALLBACKTYPE = ctypes.WINFUNCTYPE(c_int, AT_H, POINTER(AT_WC), c_void_p)
else:
_stdcall_libraries["ATCORE"] = ctypes.CDLL("atcore.so")
_stdcall_libraries["ATUTIL"] = ctypes.CDLL("atutility.so")
_stdcall_libraries["ATCORE"] = microscope._utils.library_loader(
"atcore.so"
)
_stdcall_libraries["ATUTIL"] = microscope._utils.library_loader(
"atutility.so"
)
CALLBACKTYPE = ctypes.CFUNCTYPE(c_int, AT_H, POINTER(AT_WC), c_void_p)

#### Defines
Expand Down
5 changes: 3 additions & 2 deletions microscope/cameras/atmcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from numpy.ctypeslib import ndpointer

import microscope
import microscope._utils
import microscope.abc


Expand All @@ -88,9 +89,9 @@
else:
_dllName = "atmcd64d"
if os.name == "nt": # is windows
_dll = ctypes.WinDLL(_dllName)
_dll = microscope._utils.library_loader(_dllName, ctypes.WinDLL)
else:
_dll = ctypes.CDLL(_dllName + ".so")
_dll = microscope._utils.library_loader(_dllName + ".so")

# Andor's types
at_32 = c_long
Expand Down
8 changes: 5 additions & 3 deletions microscope/cameras/pvcam.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
import Pyro4

import microscope
import microscope._utils
import microscope.abc


Expand Down Expand Up @@ -676,11 +677,12 @@ class md_frame(ctypes.Structure):

if os.name == "nt": # is windows
if platform.architecture()[0] == "32bit":
_lib = ctypes.WinDLL("pvcam32")
_lib = microscope._utils.library_loader("pvcam32", ctypes.WinDLL)
else:
_lib = ctypes.WinDLL("pvcam64")
_lib = microscope._utils.library_loader("pvcam64", ctypes.WinDLL)
else:
_lib = ctypes.CDLL("pvcam.so")
_lib = microscope._utils.library_loader("pvcam.so")


### Functions ###
STRING = ctypes.c_char_p
Expand Down
6 changes: 4 additions & 2 deletions microscope/stages/linkam.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from enum import Enum, IntEnum

import microscope
import microscope._utils
import microscope.abc


Expand Down Expand Up @@ -975,9 +976,10 @@ def get_sdk_version():
def init_sdk():
"""Initialise the SDK and set up event callbacks"""
if os.name == "nt": # is windows
__class__._lib = ctypes.CDLL("LinkamSDK.dll")
_libname = "LinkamSDK.dll"
else: # assuming Linux. Not tested.
__class__._lib = ctypes.CDLL("libLinkamSDK.so")
_libname = "libLinkamSDK.so"
__class__._lib = microscope._utils.library_loader(_libname)
_lib = __class__._lib
"""Initialise the SDK, and create and set the callbacks."""
# Omit conditional pending a fix for ctypes issues when optimisations in use.
Expand Down

0 comments on commit 03e6b5c

Please sign in to comment.