1

I am trying to get a spectrum analyzer program example working but it is having problems finding the module. Here is the error I'm getting

Traceback (most recent call last):
  File "C:\Users\user\Documents\Programs\Python_program_example.py", line 10, in <module>
    rsa300 = ctypes.WinDLL("C:\\Tektronix\\RSA306 API\\lib\\x64\\RSA300API.dll")
  File "C:\Python27\lib\ctypes\__init__.py", line 365, in __init__
    self._handle = _dlopen(self._name, mode)
WindowsError: [Error 126] The specified module could not be found

The DLL exists and os.path.exists() prints true with that path so I'm not sure what the problem is. Does anyone know what is wrong with this?

SirParselot
  • 2,640
  • 2
  • 20
  • 31

2 Answers2

2

The RSA300API.DLL might have dependencies in the folder so prior to using it, use os.chdir to set the working directory, for example:

import os

os.chdir(r'C:\Tektronix\RSA306 API\lib\x64')
rsa300 = ctypes.WinDLL(r"C:\Tektronix\RSA306 API\lib\x64\RSA300API.dll")

Checking one of their samples, this appears to be the recommended way to access it.

Alternatively, as @eryksub has mentioned, LoadLibraryEx can be used. win32api could be used to get the handle and pass it to WinDLL as follows:

import ctypes
import win32api
import win32con

dll_name = r'C:\Tektronix\RSA306 API\lib\x64\RSA300API.dll'
dll_handle = win32api.LoadLibraryEx(dll_name, 0, win32con.LOAD_WITH_ALTERED_SEARCH_PATH)
rsa300 = ctypes.WinDLL(dll_name, handle=dll_handle)
Martin Evans
  • 45,791
  • 17
  • 81
  • 97
  • It's a bit more thread friendly to either call [`SetDllDirectory`](https://msdn.microsoft.com/en-us/library/ms686203) or add the directory to the `PATH` environment variable, e.g. `os.environ['PATH'] += os.pathsep + dlldir`. Neither is really thread safe since they change global state, but they're less bad than changing the working directory. It's much better to call [`LoadLibraryEx`](https://msdn.microsoft.com/en-us/library/ms684179) with `LOAD_WITH_ALTERED_SEARCH_PATH` and manually wrap the DLL handle in an instance of `WinDLL`. Python uses this approach for loading extensions. – Eryk Sun Dec 10 '15 at 21:14
  • @eryksun Can you explain how you wrap the handle in an instance of `WinDLL`? I got the return value from `LoadLibraryEx` which is an int but `WinDLL` takes a string or a unicode object. – SirParselot Dec 11 '15 at 18:25
  • `LoadLibraryEx` returns a handle, this can be passed to `WinDLL` using `handle=xxxx` as one of the arguments. – Martin Evans Dec 11 '15 at 18:28
  • @SirParselot, make sure to set `LoadLibraryExW.restype = ctypes.c_void_p`, since the return value is the base address of the loaded module. – Eryk Sun Dec 11 '15 at 18:34
  • @eryksun `LoadLibraryEx` doesn't have `restype` as an attribute though. Do you mean `rsa300.restype`? – SirParselot Dec 11 '15 at 18:46
  • @SirParselot, it should be set up as `kernel32 = ctypes.WinDLL('kernel32', use_last_error=True);` `kernel32.LoadLibraryExW.restype = ctypes.c_void_p;` `kernel32.LoadLibraryExW.argtypes = [ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_uint]; LOAD_WITH_ALTERED_SEARCH_PATH = 8`. Then get the handle as `dll_handle = kernel32.LoadLibraryExW(dll_name, None, LOAD_WITH_ALTERED_SEARCH_PATH)` and wrap it in a `WinDLL` instance using `rsa300 = ctypes.WinDLL(dll_name, handle=dll_handle)`. – Eryk Sun Dec 11 '15 at 19:08
  • @SirParselot, if the `dll_handle` value is `None`, raise an exception based on the Windows last error value using `raise ctypes.WinError(ctypes.get_last_error())`. – Eryk Sun Dec 11 '15 at 19:16
  • @eryksun This is all new stuff to me so thanks for the help from both of you! – SirParselot Dec 11 '15 at 19:18
  • I should push a patch for ctypes to use `LoadLibraryEx` instead of `LoadLibrary`. Then we could use the `mode` parameter of `CDLL` and `WinDLL` to pass the flags that control how the loader behaves. – Eryk Sun Dec 11 '15 at 19:32
2

Tektronix application engineer here.

Martin is correct, our dll does have dependencies in the folder in which it resides. It looks like you're using the old version of our API, which has a whole truckload of dependencies in the C:\Tektronix\RSA306 API\lib\x64 folder. The newer version of the API purges and consolidates many of those dependencies and has renamed many of the functions to improve clarity and consistency. Our software engineers also decided that the old API wouldn't be overwritten when installing newer versions, so your old scripts will still work even if you install the newest API.

FWIW, I use the following code at the beginning of all my RSA control scripts:

"""
################################################################
C:\Tektronix\RSA306 API\lib\x64 needs to be added to the 
PATH system environment variable
################################################################
"""
os.chdir("C:\\Tektronix\\RSA_API\\lib\\x64")
rsa = cdll.LoadLibrary("RSA_API.dll")

Here is a link to download the latest version of the RSA API (as of 11/1/16): http://www.tek.com/model/rsa306-software

Here is a link to download the API documentation (as of 11/1/16). There is an Excel spreadsheet attached to this document that outlines the differences between old functions and new functions: http://www.tek.com/spectrum-analyzer/rsa306-manual-6