There is a filter wheel SDK, named "libEFWFilter.so", provided by ZWO company. The downloaded link is https://dl.zwoastro.com/software?app=DeveloperEfwSdk&platform=windows86®ion=China
.
I want to use ctypes or CFFI module of python to call this dynamic library on Ubuntu20.04 or CentOS7. The following is my python program:test.py.
#!/usr/bin/python3
import ctypes
from ctypes.util import find_library
import os
print(find_library("udev"))
print(find_library("EFWFilter"))
print(os.getcwd() + "/libEFWFilter.so.1.7")
libefw = ctypes.cdll.LoadLibrary(os.getcwd() + "/libEFWFilter.so.1.7")
When I run this test.py, the output error messages are as follows.
libudev.so.1
libEFWFilter.so.1.7
/home/ls/libEFW/EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7
Traceback (most recent call last):
File "test.py", line 11, in <module>
libefw = ctypes.cdll.LoadLibrary(os.getcwd() + "/libEFWFilter.so.1.7")
File "/usr/lib/python3.8/ctypes/__init__.py", line 451, in LoadLibrary
return self._dlltype(name)
File "/usr/lib/python3.8/ctypes/__init__.py", line 373, in __init__
self._handle = _dlopen(self._name, mode)
OSError: /home/ls/libEFW/EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7: undefined symbol: udev_enumerate_new
But I have installed the libudev library.
ls@ubuntu64:~/libEFW/EFW_linux_mac_SDK_V1.7/lib/x64$ whereis libudev
libudev: /usr/lib/x86_64-linux-gnu/libudev.so /usr/include/libudev.h /usr/share/man/man3/libudev.3.gz
The demo provided by this company can be compiled correctly and the executable file runs OK. The demo contains "main.cpp"、"EFW_filter.h"、"Makefile". Just as follows.
main.cpp
#include "stdio.h"
#include "EFW_filter.h"
#ifdef _WINDOWS
#include <windows.h>
#else
#include <sys/time.h>
#include <unistd.h>
#define Sleep(a) usleep((a)*1000)
#endif
int main()
{
int EFW_count = EFWGetNum();
if(EFW_count <= 0)
{
printf("no filter wheel connected, press any key to exit\n");
getchar();
return -1;
}
else
printf("attached filter wheel:\n");
EFW_INFO EFWInfo;
for(int i = 0; i < EFW_count; i++)
{
EFWGetID(i, &EFWInfo.ID);
EFWGetProperty(EFWInfo.ID, &EFWInfo);
printf("index %d: %s\n",i, EFWInfo.Name);
}
printf("\nselect one \n");
int EFWIndex, iSelectedID;
scanf("%d", & EFWIndex);
EFWGetID(EFWIndex, &iSelectedID);
if(EFWOpen(iSelectedID) != EFW_SUCCESS)
{
printf("open error,are you root?,press any key to exit\n");
getchar();
return -1;
}
if(iSelectedID < 0)
{
printf("open error,are you root?,press any key to exit\n");
getchar();
return -1;
}
else
{
EFW_ERROR_CODE err;
while(1){
err = EFWGetProperty(iSelectedID, & EFWInfo);
if(err != EFW_ERROR_MOVING )
break;
Sleep(500);
}
printf("%d slots: ", EFWInfo.slotNum);
for(int i = 0; i < EFWInfo.slotNum; i++)
printf("%d ", i + 1);
int currentSlot;
while(1)
{
err = EFWGetPosition(iSelectedID, ¤tSlot);
if(err != EFW_SUCCESS || currentSlot != -1 )
break;
Sleep(500);
}
printf("\ncurrent position: %d\n", currentSlot + 1);
int targetSlot;
char c;
printf("\nPlease input target position, type \'q\' to quit:\n");
while(1)
{
c = getchar();
if (EOF == c)
continue;
if(c == 'q')
break;
if(c >= '0' && c <= '9')
targetSlot = c - '0';
else
continue;
targetSlot--;
if(targetSlot < 0 || targetSlot >= EFWInfo.slotNum)
continue;
err = EFWSetPosition(iSelectedID, targetSlot);
if(err == EFW_SUCCESS)
printf("\nMoving...\n");
while(1)
{
err = EFWGetPosition(iSelectedID, ¤tSlot);
if(err != EFW_SUCCESS ||currentSlot != -1 )
break;
Sleep(500);
}
printf("\nPlease input target position, type \'q\' to quit:\n");
}
EFWClose(iSelectedID);
printf("main function over\n");
return 1;
}
}
EFW_filter.h
/**************************************************
this is the ZWO filter wheel EFW SDK
any question feel free contact us:yang.zhou@zwoptical.com
here is the suggested procedure.
--> EFWGetNum
--> EFWGetID for each filter wheel
--> EFWGetProperty
--> EFWOpen
--> EFWGetPosition
--> EFWSetPosition
...
--> EFWClose
***************************************************/
#ifndef EFW_FILTER_H
#define EFW_FILTER_H
#ifdef _WINDOWS
#define EFW_API __declspec(dllexport)
#else
#define EFW_API
#endif
#define EFW_ID_MAX 128
typedef struct _EFW_INFO
{
int ID;
char Name[64];
int slotNum;
} EFW_INFO;
typedef enum _EFW_ERROR_CODE{
EFW_SUCCESS = 0,
EFW_ERROR_INVALID_INDEX,
EFW_ERROR_INVALID_ID,
EFW_ERROR_INVALID_VALUE,
EFW_ERROR_REMOVED, //failed to find the filter wheel, maybe the filter wheel has been removed
EFW_ERROR_MOVING,//filter wheel is moving
EFW_ERROR_ERROR_STATE,//filter wheel is in error state
EFW_ERROR_GENERAL_ERROR,//other error
EFW_ERROR_NOT_SUPPORTED,
EFW_ERROR_CLOSED,
EFW_ERROR_END = -1
}EFW_ERROR_CODE;
typedef struct _EFW_ID{
unsigned char id[8];
}EFW_ID;
typedef EFW_ID EFW_SN;
#ifdef __cplusplus
extern "C" {
#endif
/***************************************************************************
Descriptions:
this should be the first API to be called
get number of connected EFW filter wheel, call this API to refresh device list if EFW is connected
or disconnected
Return: number of connected EFW filter wheel. 1 means 1 filter wheel is connected.
***************************************************************************/
EFW_API int EFWGetNum();
/***************************************************************************
Descriptions:
get the product ID of each wheel, at first set pPIDs as 0 and get length and then malloc a buffer to load the PIDs
Paras:
int* pPIDs: pointer to array of PIDs
Return: length of the array.
***************************************************************************/
EFW_API int EFWGetProductIDs(int* pPIDs);
/***************************************************************************
Descriptions:
get ID of filter wheel
Paras:
int index: the index of filter wheel, from 0 to N - 1, N is returned by GetNum()
int* ID: pointer to ID. the ID is a unique integer, between 0 to EFW_ID_MAX - 1, after opened,
all the operation is base on this ID, the ID will not change.
Return:
EFW_ERROR_INVALID_INDEX: index value is invalid
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWGetID(int index, int* ID);
/***************************************************************************
Descriptions:
open filter wheel
Paras:
int ID: the ID of filter wheel
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_GENERAL_ERROR: number of opened filter wheel reaches the maximum value.
EFW_ERROR_REMOVED: the filter wheel is removed.
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWOpen(int ID);
/***************************************************************************
Descriptions:
get property of filter wheel. SlotNum is 0 if not opened.
Paras:
int ID: the ID of filter wheel
EFW_INFO *pInfo: pointer to structure containing the property of EFW
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_MOVING: slot number detection is in progress, generally this error will happen soon after filter wheel is connected.
EFW_SUCCESS: operation succeeds
EFW_ERROR_REMOVED: filter wheel is removed
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWGetProperty(int ID, EFW_INFO *pInfo);
/***************************************************************************
Descriptions:
get position of slot
Paras:
int ID: the ID of filter wheel
int *pPosition: pointer to slot position, this value is between 0 to M - 1, M is slot number
this value is -1 if filter wheel is moving
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_SUCCESS: operation succeeds
EFW_ERROR_ERROR_STATE: filter wheel is in error state
EFW_ERROR_REMOVED: filter wheel is removed
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWGetPosition(int ID, int *pPosition);
/***************************************************************************
Descriptions:
set position of slot
Paras:
int ID: the ID of filter wheel
int Position: slot position, this value is between 0 to M - 1, M is slot number
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_SUCCESS: operation succeeds
EFW_ERROR_INVALID_VALUE: Position value is invalid
EFW_ERROR_MOVING: filter wheel is moving, should wait until idle
EFW_ERROR_ERROR_STATE: filter wheel is in error state
EFW_ERROR_REMOVED: filter wheel is removed
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWSetPosition(int ID, int Position);
/***************************************************************************
Descriptions:
set unidirection of filter wheel
Paras:
int ID: the ID of filter wheel
bool bUnidirectional: if set as true, the filter wheel will rotate along one direction
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWSetDirection(int ID, bool bUnidirectional);
/***************************************************************************
Descriptions:
get unidirection of filter wheel
Paras:
int ID: the ID of filter wheel
bool *bUnidirectional: pointer to unidirection value .
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWGetDirection(int ID, bool *bUnidirectional);
/***************************************************************************
Descriptions:
calibrate filter wheel
Paras:
int ID: the ID of filter wheel
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_SUCCESS: operation succeeds
EFW_ERROR_MOVING: filter wheel is moving, should wait until idle
EFW_ERROR_ERROR_STATE: filter wheel is in error state
EFW_ERROR_REMOVED: filter wheel is removed
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWCalibrate(int ID);
/***************************************************************************
Descriptions:
close filter wheel
Paras:
int ID: the ID of filter wheel
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWClose(int ID);
/***************************************************************************
Descriptions:
get version string, like "0, 4, 0824"
***************************************************************************/
EFW_API char* EFWGetSDKVersion();
/***************************************************************************
Descriptions:
get hardware error code of filter wheel
Paras:
int ID: the ID of filter wheel
bool *pErrCode: pointer to error code .
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWGetHWErrorCode(int ID, int *pErrCode);
/***************************************************************************
Descriptions:
Get firmware version of filter wheel
Paras:
int ID: the ID of filter wheel
int *major, int *minor, int *build: pointer to value.
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWGetFirmwareVersion(int ID, unsigned char *major, unsigned char *minor, unsigned char *build);
/***************************************************************************
Descriptions:
Get the serial number from a EFW
Paras:
int ID: the ID of focuser
EFW_SN* pSN: pointer to SN
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_ERROR_NOT_SUPPORTED: the firmware does not support serial number
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWGetSerialNumber(int ID, EFW_SN* pSN);
/***************************************************************************
Descriptions:
Set the alias to a EFW
Paras:
int ID: the ID of filter
EFW_ID alias: the struct which contains the alias
Return:
EFW_ERROR_INVALID_ID: invalid ID value
EFW_ERROR_CLOSED: not opened
EFW_ERROR_NOT_SUPPORTED: the firmware does not support setting alias
EFW_SUCCESS: operation succeeds
***************************************************************************/
EFW_API EFW_ERROR_CODE EFWSetID(int ID, EFW_ID alias);
#ifdef __cplusplus
}
#endif
#endif
Makefile
platform = x64
CC = g++
ifeq ($(platform), mac32)
CFLAGS += -m32 -framework IOKit -framework CoreFoundation
endif
ifeq ($(platform), mac64)
CFLAGS += -framework IOKit -framework CoreFoundation
endif
ifeq ($(platform), mac)
CFLAGS += -arch i386 -arch x86_64 -framework IOKit -framework CoreFoundation
endif
ifeq ($(platform), x86)
CFLAGS += -ludev
endif
ifeq ($(platform), x64)
CFLAGS += -ludev
endif
ifeq ($(platform), armv5)
CFLAGS += -ludev
endif
ifeq ($(platform), armv6)
CFLAGS += -ludev
endif
ifeq ($(platform), armv8)
CFLAGS += -ludev
endif
all:test_console
test_console: main.cpp
$(CC) main.cpp -o test_console $(CFLAGS) ../../lib/$(platform)/libEFWFilter.a -I../../include $(CFLAGS) -lpthread
cp test_console bin/$(platform)/
clean:
rm -f test_console
When I build demo, It's OK and no error.
The things that I have tried:
But the above methods didn't solve this "undefined symbol" problem. I noticed that some solutions to solve this similar problem is add "extern C" to cpp function. But I am not able to do this, because I don't know the source code of libEFWFilter.so library. So what should I do to solve this problem by python? Thanks for your help very much!
updated on 2024/2/20
The follows is output information When I type "ctypes.CDLL("libudev.so").udev_enumerate_new" in Python console and type "dpkg -s libudev1:amd64" and "ldd ./libEFWFilter.so.1.7" in linux terminal.
ls@ubuntu64:~/libEFW/EFW_linux_mac_SDK_V1.7/lib/x64$ python3
Python 3.8.10 (default, Nov 22 2023, 10:22:35)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> print(ctypes.CDLL("libudev.so").udev_enumerate_new)
<_FuncPtr object at 0x7f70be96bb80>
>>> from ctypes.util import find_library
>>> print(find_library("udev"))
libudev.so.1
>>>
ls@ubuntu64:~/libEFW/EFW_linux_mac_SDK_V1.7/lib/x64$ dpkg -s libudev1:amd64
Package: libudev1
Status: install ok installed
Priority: required
Section: libs
Installed-Size: 340
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: amd64
Multi-Arch: same
Source: systemd
Version: 245.4-4ubuntu3.23
Depends: libc6 (>= 2.30)
Description: libudev shared library
This library provides access to udev device information.
Homepage: https://www.freedesktop.org/wiki/Software/systemd
Original-Maintainer: Debian systemd Maintainers <pkg-systemd-maintainers@lists.alioth.debian.org>
ls@ubuntu64:~/libEFW/EFW_linux_mac_SDK_V1.7/lib/x64$ ldd ./libEFWFilter.so.1.7
linux-vdso.so.1 (0x00007ffd491f3000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f6866b1d000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f68669ce000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f68669b3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f68667c1000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6866f6e000)
From the above message, it seems no problem. But there is no "libudev.so" in the output of "ldd ./libEFWFilter.so.1.7", it may be a error? how to solve this problem? Thanks again.
The example works, but it's not the same scenario, as it links to the static library variant (../../lib/$(platform)/libEFWFilter.a). Also, it links to LibUDev (CFLAGS += -ludev
), which is the 1st clue.
As a side note, the Makefile is kind of poorly written and also the archives structures can be much improved.
Back to our library:
(py_pc064_03.08_test0_lancer) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q078021783]> . ~/sopr.sh ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [064bit prompt]> [064bit prompt]> ldd EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7 linux-vdso.so.1 (0x00007fff72300000) libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5ac3600000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5ac3919000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5ac38f9000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5ac3200000) /lib64/ld-linux-x86-64.so.2 (0x00007f5ac3c7f000) [064bit prompt]> [064bit prompt]> nm -S EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7 | grep udev U udev_device_get_devnode U udev_device_get_parent_with_subsystem_devtype U udev_device_get_sysattr_value U udev_device_new_from_devnum U udev_device_new_from_syspath U udev_device_unref U udev_enumerate_add_match_subsystem U udev_enumerate_get_list_entry U udev_enumerate_new U udev_enumerate_scan_devices U udev_enumerate_unref U udev_list_entry_get_name U udev_list_entry_get_next U udev_new U udev_unref
As already mentioned, the library doesn't depend on (link to) LibUDev (libudev.so*), but it uses some of its functions (udev_*).
It's a common behavior on Nix that libraries depend on symbols that will be present in the loading executable. Now, the symbols source might be the executable itself or other libraries already loaded. This is the case for Python extension modules (e.g.: _ctypes*.so), which depend on symbols from PYTHONCORE (like Py_Initialize) that are defined inside python executable. But, if it's a shared build, the symbols will be defined in libpython*.so (that the executable links to).
It's the same thing here. All we have to do is (prerequisites):
Loading libudev.so*
Make its symbols (udev_enumerate_new) visible to libEFWFilter.so* (when it will be loaded)
Example:
[064bit prompt]> python Python 3.8.18 (default, Aug 25 2023, 13:20:30) [GCC 11.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> import ctypes as cts >>> >>> lib = "./EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7" >>> >>> # --- Attempt loading our library --- >>> cts.CDLL(lib) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.8/ctypes/__init__.py", line 373, in __init__ self._handle = _dlopen(self._name, mode) OSError: ./EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7: undefined symbol: udev_enumerate_new >>> >>> # --- Load LibUDev into the Python process --- >>> cts.CDLL("libudev.so") <CDLL 'libudev.so', handle 28056b0 at 0x7f6cfeda3910> >>> >>> # --- Attempt loading our library --- >>> cts.CDLL(lib) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.8/ctypes/__init__.py", line 373, in __init__ self._handle = _dlopen(self._name, mode) OSError: ./EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7: undefined symbol: udev_enumerate_new >>> >>> # --- Load LibUDev into the Python process (actually, it's already loaded by the previous call) --- >>> # --- AND make its symbols visible --- >>> cts.CDLL("libudev.so", mode=cts.RTLD_GLOBAL) # !!! <CDLL 'libudev.so', handle 28056b0 at 0x7f6cfedc5d90> >>> >>> # --- Attempt loading our library --- >>> cts.CDLL(lib) <CDLL './EFW_linux_mac_SDK_V1.7/lib/x64/libEFWFilter.so.1.7', handle 2807da0 at 0x7f6cff10e400>
Might want to also check:
[Python.Docs]: ctypes - A foreign function library for Python
[SO]: Resolving circular shared-object dependencies with ctypes/cffi (@CristiFati's answer)
[SO]: How does ctypes.cdll.LoadLibrary(None) work? (@CristiFati's answer)
[SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) - for a common pitfall when working with CTypes (calling functions). This will be useful in the next step
[SO]: Can't import dll module in Python (@CristiFati's answer) - kind of similar, but on Win