Setting up ZKTeco ZK8500 (ZK9500) fingerprint scanner in Python - python

I am working on a Python project that includes using Images taken by Fingerprint scanner and processing them. I am currently using ZKTeco ZK8500(ZK9500) that is provided with C SDK library. But i have very little knowledge in C/C++ and to be honest Python is not my strongest language. I am trying to use C SDK via ctypes library in Python but i have some difficulties with that. I am currently testing on Windows, installed SDK from official site. So far my code looks like:
import ctypes
from ctypes import *
import logging
logging.basicConfig(filename='app_fp.log',
filemode='a',
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%d-%m-%Y %H:%M:%S',
level=logging.INFO)
lib_3 = CDLL('libzkfp.dll')
###############################
lib_3.ZKFPM_Init.restype = ctypes.c_int
lib_3.ZKFPM_Terminate.restype = ctypes.c_int
lib_3.ZKFPM_GetDeviceCount.restype = ctypes.c_int
lib_3.ZKFPM_OpenDevice.restype = ctypes.c_void_p
lib_3.ZKFPM_OpenDevice.argtypes = [ctypes.c_int]
lib_3.ZKFPM_CloseDevice.restype = ctypes.c_int
lib_3.ZKFPM_CloseDevice.argtypes = [ctypes.c_void_p]
lib_3.ZKFPM_GetParameters.restype = ctypes.c_int
lib_3.ZKFPM_GetParameters.argtypes = [ctypes.c_void_p,
ctypes.c_int,
ctypes.POINTER(ctypes.c_ubyte),
ctypes.POINTER(ctypes.c_uint),
]
paramChar = c_ubyte()
paramInt = c_uint()
################################
res_init = lib_3.ZKFPM_Init()
print("Init:", res_init)
res_getDeviceCount = lib_3.ZKFPM_GetDeviceCount()
print("Dev count:", res_getDeviceCount, type(res_getDeviceCount))
dev0_handle = lib_3.ZKFPM_OpenDevice(0)
print("Dev HANDLE:", dev0_handle)
dev0_getParam = lib_3.ZKFPM_GetParameters(dev0_handle,
1102,
byref(paramChar),
byref(paramInt))
print("Get dev name:", dev0_getParam, paramChar, sizeof(paramChar))
logging.info("DeviceHandle:" + str(dev0_handle))
res_close = lib_3.ZKFPM_CloseDevice(dev0_handle)
print("Close dev:", dev0_handle, res_close)
#
res_terminate = lib_3.ZKFPM_Terminate()
print("Terminate:", res_terminate)
Stdout looks like this:
Init: 0
Dev count: 2 <class 'int'>
Dev HANDLE: 2682398313632
Get dev name: -3 c_ubyte(0) 1
Close dev: 2682398313632 0
Terminate: 0
Check sum data is true!sum=9286, buf[val]=9286
Check sum data is true!sum=-44, buf[val]=-44
opts->Stripe_Reference_Point_X=654, opts->Stripe_Reference_Point_Y=542
Check sum data is true!sum=908, buf[val]=908
CMOS Sensor->Exposure:256, RedGain:152, GreenGain1:200, GreenGain2:187,BlueGain:112.
Main1LED:200, Main2LED:200, Side1LED:175, Side2LED:175, Anti1LED:200, Anti2LED:200.
start to set the exposure parameters
Process finished with exit code 0
Description of GetParameters function from SDK is:
5.2.7 ZKFPM_GetParameters
[Function]
int APICALL ZKFPM_GetParameters(HANDLE hDevice, int nParamCode, unsigned
char* paramValue, unsigned int* cbParamValue);
[Purpose]
This function is used to acquire fingerprint reader parameters.
[Parameter Description]
hDevice
Device operation instance handle
nParamCode
Parameter code (For details, see the parameter code list.)
paramValue [out]
Returned parameter value
cbParamValue [in/out]
[in] Memory size allocated based on nParamCode
[out]Data size of the returned parameter value
[Return Value]
0 Succeeded
Others Failed (See the Appendixes.)
Questions:
I do not understand why i can OpenDevice, can CloseDevice but get Get dev name: -3 c_ubyte(0) 1 when i try to get parameter i get -3- No device connected. Maybe something in my Python declaration or variable types?
How i can get image to file from fingerprint scanner? Code snippets if anybody knows?

Related

Read Process Memory doesn't seem to give the right value

I am trying to read memory from a process (gameboy advance emulator) in Python using ReadProcessMemory. There is a memory viewer and I am supposed to get 81 at 0xD273 (see picture). I am new to this, I tried to do everything correctly by adding reference in the ReadProcessMemory, but there might be some things that are wrong. I am pretty sure I have the right process id since it matches the one in the task manager.
When I run my code, I get random byte values that are different everytime. 15, 255, 11, 195, but I think I should be getting 81.
I need to use python 32-bit to run the script otherwise I get error 299 (ERROR_PARTIAL_COPY).
Is there something that I'm doing wrong? I don’t specify the base address but I assumed it’s handled by the processHandle.
Here is my code and an example of the output:
result: 1, err code: 0, bytesRead: 1
data: 0000000000000015h
21
import ctypes as c
from ctypes import wintypes as w
import psutil
# Must use py -3-32 vba_script.py
vba_process_id = [p.pid for p in psutil.process_iter() if "visualboyadvance" in p.name()][0]
pid = vba_process_id # I assume you have this from somewhere.
k32 = c.WinDLL('kernel32', use_last_error=True)
OpenProcess = k32.OpenProcess
ReadProcessMemory = k32.ReadProcessMemory
CloseHandle = k32.CloseHandle
processHandle = OpenProcess(0x10, False, pid)
addr = c.c_void_p(0xD273)
dataLen = 8
data = c.c_byte()
bytesRead = c.c_byte()
result = ReadProcessMemory(processHandle, c.byref(addr), c.byref(data), c.sizeof(data), c.byref(bytesRead))
e = c.get_last_error()
print('result: {}, err code: {}, bytesRead: {}'.format(result,e,bytesRead.value))
print('data: {:016X}h'.format(data.value))
print(data.value)
CloseHandle(processHandle)
After reading #jasonharper answer, I found a way to get the actual address in the memory.
To get them, I used Cheat Engine, here was my procedure:
In Cheat Engine, search for 87949181 (hex for BRUH, which is trainer's name). Any data that you know will not change is also fine.
Find the address that really corresponds to it. Should have 0x00000050, 0x01000000, 0x0000FF99, 0x99000000, 0x00001600, 0x212D0316, DC00002D after (see picture 1). This address is a pointer. In my case, it's 0x07D2F370 (picture 2).
Double click on the address and do a pointer scan. In my case, the pointer address 0x07D2F218. This is a dynamic address that will change everytime, so you need to find the static address.
You can find that "visualboyadvance-m.exe"+02224064 -> 07D2F218. The base address is therefore 0x07D2F218 - 0x02224064 = 0x9a0000. The static address offset for the data I'm searching for is 0x02224064. The offset for the trainer's name data is 0x158.
After opening the process, to search in the memory, you can have a code like this:
base_addr = 0x9a0000 # "visualboyadvance-m.exe"
static_addr_offset = 0x02224064
address = base_addr + static_addr_offset + 0x158
k32 = c.WinDLL('kernel32', use_last_error=True)
buffer = c.create_string_buffer(buffer_size)
buffer_size=32
bytes_read = c.c_ulong(0)
if k32.ReadProcessMemory(processHandle, address, buffer, buffer_size, c.byref(bytes_read)):
data = c.c_uint32.from_buffer(buffer)
print(f"data: {data .value:X}")
This returns the right data that I'm looking for: data: 87949181.
Here are the pictures:
================================================================
================================================================
Bonus:
The base address will change if you close the game and you will need to find it back everytime. There is some way of doing way it by getting the module with the name of the process pname. You can get it easily with psutil.
import win32process
import psutils
vba_process = [p for p in psutil.process_iter() if "visualboyadvance" in p.name()][0]
pid = vba_process.pid
pname = vba_process.name
k32 = c.WinDLL('kernel32', use_last_error=True)
processHandle = k32.OpenProcess(0x10, False, pid)
modules = win32process.EnumProcessModules(processHandle)
for module in modules:
moduleFileName = win32process.GetModuleFileNameEx(processHandle, module)
if pname in moduleFileName:
base_address = module
print("Success: Got Base Address:", hex(base_address))
Success: Got Base Address: 0x9a0000
Edit: Found how to get base address of process automatically

How to add Python script to registry using Ctypes and kernel32.dll

I'm trying to add my program to registry and this is my code...
def regc():
reg = windll.kernel32
print(reg)
hkey = 'HKEY_CURRENT_USER'
lsubkey = 'Software\Microsoft\Windows\CurrentVersion\Run'
reserved = 0
flag = 'REG_OPTION_BACKUP_RESTORE'
samdesired = 'KEY_ALL_ACCESS'
ipsec = None
handle = reg.RegCreateKeyExA(hkey, lsubkey, reserved, flag, samdesired, ipsec, None)
Its not giving me any errors but it still isn't creating a new key in registry. What am I doing wrong?
To use ctypes correctly, define .argtypes and .restype to do error checking of your parameters. Many of the types used are wrong. hkey, flag, and samdesired are not strings, for example. The return value is not a handle, but a status. The return value is an output parameter (pkhResult in the docs). You must read the documentation and examine the header files of all the variable definitions carefully.
Also, in Python 3 strings are Unicode, so use the W form of Windows APIs to accept Unicode strings. Use raw strings (r'...') for the subkey since it contains backslashes that could be interpreted as escape codes.
Here's a working example:
from ctypes import *
from ctypes import wintypes as w
# Values found from reading RegCreateKeyExW documentation,
# using Go To Definition on the types in Visual Studio,
# and printing constants in a C program, e.g. printf("%lx\n",KEY_ALL_ACCESS);
HKEY = c_void_p
PHKEY = POINTER(HKEY)
REGSAM = w.DWORD
LPSECURITY_ATTRIBUTES = c_void_p
LSTATUS = w.LONG
# Disposition values
REG_CREATED_NEW_KEY = 0x00000001
REG_OPENED_EXISTING_KEY = 0x00000002
ERROR_SUCCESS = 0
HKEY_CURRENT_USER = c_void_p(0x80000001)
REG_OPTION_NON_VOLATILE = 0
KEY_ALL_ACCESS = 0x000F003F
dll = WinDLL('kernel32')
dll.RegCreateKeyExW.argtypes = HKEY,w.LPCWSTR,w.DWORD,w.LPWSTR,w.DWORD,REGSAM,LPSECURITY_ATTRIBUTES,PHKEY,w.LPDWORD
dll.RegCreateKeyExW.restype = LSTATUS
hkey = HKEY_CURRENT_USER
lsubkey = r'Software\Microsoft\Windows\CurrentVersion\Run'
options = REG_OPTION_NON_VOLATILE
samdesired = KEY_ALL_ACCESS
# Storage for output parameters...pass by reference.
handle = HKEY()
disp = w.DWORD()
status = dll.RegCreateKeyExW(HKEY_CURRENT_USER, lsubkey, 0, None, options, samdesired, None, byref(handle), byref(disp))
if status == ERROR_SUCCESS:
print(f'{disp=} {handle=}')
Output:
disp=c_ulong(2) handle=c_void_p(3460)
The disposition value of 2 indicates the key already exists (REG_OPENED_EXISTING_KEY).
You could also install pywin32 and use win32api.RegCreateKey or win32api.RegCreateKeyEx where all the work is already done for you.

How to produce a Python ctypes instance

I am attempting to use the pyueye library to run ML cameras, and am running into problems with ctypes. One function requires an argument of type 'ctypes instance,' and despite trying every possible variation I cannot figure out how to produce this with the ctypes library. There is no documentation for python with this library, but the C documentation of the function I am trying to use is:
Syntax
INT is_SetAutoParameter (HIDS hCam, INT param, double* pval1, double* pval2)
Example 1
//Enable auto gain control:
double dEnable = 1;
int ret = is_SetAutoParameter (hCam, IS_SET_ENABLE_AUTO_GAIN, &dEnable, 0);
The code and subsequent error I am recieving in python is:
nRet = ueye.is_SetAutoParameter(hCam, ueye.IS_SET_ENABLE_AUTO_GAIN, ctypes.byref(ctypes.c_long(1)), ctypes.byref(ctypes.c_long(0)))
Error:
ret = _is_SetAutoParameter(_hCam, _param, ctypes.byref(pval1), ctypes.byref(pval2))
TypeError: byref() argument must be a ctypes instance, not 'CArgObject'
Any advice on ctypes instances? Thanks
EDIT: Minimal reproducible example
from pyueye import ueye
import ctypes
class Turfcam:
def main(self):
turfcam.take_photo()
def take_photo(self):
hCam = ueye.HIDS(0)
pval1 = ctypes.c_double(1)
pval2 = ctypes.c_double(0)
nRet = ueye.is_SetAutoParameter(hCam, ueye.IS_SET_ENABLE_AUTO_GAIN, ctypes.byref(pval1), ctypes.byref(pval2))
# Camera Init
nRet = ueye.is_InitCamera(hCam, None)
if __name__ == "__main__":
turfcam = Turfcam()
turfcam.main()
The pyueye library has some minimal documentation:
_is_SetAutoParameter = _bind("is_SetAutoParameter", [ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double)], ctypes.c_int)
def is_SetAutoParameter(hCam, param, pval1, pval2):
"""
:param hCam: c_uint (aka c-type: HIDS)
:param param: c_int (aka c-type: INT)
:param pval1: c_double (aka c-type: double \*)
:param pval2: c_double (aka c-type: double \*)
:returns: success, or no success, that is the answer
:raises NotImplementedError: if function could not be loaded
"""
if _is_SetAutoParameter is None:
raise NotImplementedError()
_hCam = _value_cast(hCam, ctypes.c_uint)
_param = _value_cast(param, ctypes.c_int)
ret = _is_SetAutoParameter(_hCam, _param, ctypes.byref(pval1), ctypes.byref(pval2))
return ret
The wrapper is doing the byref, so only a ctypes object needs to be passed. The uEye docs say the parameters can be input or output parameters, so always create c_double objects and initialize them as needed for the function passed. Note that for an output parameter you must assign the object a name so it exists after the call to be queried. Input parameters can get away with passing ctypes.c_double(value) directly to the function, but for consistency I'd always assign names to the objects:
Example:
pval1 = ctypes.c_double()
pval2 = ctypes.c_double() # not used for output on IS_GET_ENABLE_AUTO_GAIN
nRet = ueye.is_SetAutoParameter(hCam, ueye.IS_GET_ENABLE_AUTO_GAIN, pval1, pval2)
print(pval1.value) # query the returned value
To complete the answer given by Mark Tolonen: the following code works OK (but probably you also want to check the error code nRet):
def take_photo(self):
hCam = ueye.HIDS(0)
# Camera Init (you must do this before using hCam !!)
nRet = ueye.is_InitCamera(hCam, None)
pval1 = ctypes.c_double(1)
pval2 = ctypes.c_double(0)
nRet = ueye.is_SetAutoParameter(hCam, ueye.IS_SET_ENABLE_AUTO_GAIN, pval1, pval2)

Detect media insertion on Windows in Python

I need a program that detects media insertion and also tells me the drive letter so that I can build on it and add other functions to be run when the device inserted event is fired.
I think it can be done using WMI using the Win32_VolumeChangeEvent class (I found some implementations in Powershell and C# but I want to do it with Python). I know there is also the wmi module for python eventually and I found this snippet of code from a Python mailing list but it seems it doesn't work.
Then I also found this Python script that could do what I need. It seems it was written for python 2 and I adjusted the parenthesis for the print() function in order to make it work on python 3, besides I noticed there were a couple of unnecessary ; in the code. (maybe it was ported from C and the developer left them there by mistake. This python script uses ctypes).
I show you the code I got:
import win32api, win32con, win32gui
from ctypes import *
#
# Device change events (WM_DEVICECHANGE wParam)
#
DBT_DEVICEARRIVAL = 0x8000
DBT_DEVICEQUERYREMOVE = 0x8001
DBT_DEVICEQUERYREMOVEFAILED = 0x8002
DBT_DEVICEMOVEPENDING = 0x8003
DBT_DEVICEREMOVECOMPLETE = 0x8004
DBT_DEVICETYPESSPECIFIC = 0x8005
DBT_CONFIGCHANGED = 0x0018
#
# type of device in DEV_BROADCAST_HDR
#
DBT_DEVTYP_OEM = 0x00000000
DBT_DEVTYP_DEVNODE = 0x00000001
DBT_DEVTYP_VOLUME = 0x00000002
DBT_DEVTYPE_PORT = 0x00000003
DBT_DEVTYPE_NET = 0x00000004
#
# media types in DBT_DEVTYP_VOLUME
#
DBTF_MEDIA = 0x0001
DBTF_NET = 0x0002
WORD = c_ushort
DWORD = c_ulong
class DEV_BROADCAST_HDR(Structure):
_fields_ = [
("dbch_size", DWORD),
("dbch_devicetype", DWORD),
("dbch_reserved", DWORD)
]
class DEV_BROADCAST_VOLUME(Structure):
_fields_ = [
("dbcv_size", DWORD),
("dbcv_devicetype", DWORD),
("dbcv_reserved", DWORD),
("dbcv_unitmask", DWORD),
("dbcv_flags", WORD)
]
def drive_from_mask(mask):
n_drive = 0
while 1:
if (mask & (2 ** n_drive)):
return n_drive
else:
n_drive += 1
class Notification:
def __init__(self):
message_map = {
win32con.WM_DEVICECHANGE: self.onDeviceChange
}
wc = win32gui.WNDCLASS()
hinst = wc.hInstance = win32api.GetModuleHandle(None)
wc.lpszClassName = "DeviceChangeDemo"
wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
wc.hbrBackground = win32con.COLOR_WINDOW
wc.lpfnWndProc = message_map
classAtom = win32gui.RegisterClass(wc)
style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
self.hwnd = win32gui.CreateWindow(
classAtom,
"Device Change Demo",
style,
0, 0,
win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
0, 0,
hinst, None
)
def onDeviceChange(self, hwnd, msg, wparam, lparam):
#
# WM_DEVICECHANGE:
# wParam - type of change: arrival, removal etc.
# lParam - what's changed?
# if it's a volume then...
# lParam - what's changed more exactly
#
dev_broadcast_hdr = DEV_BROADCAST_HDR.from_address(lparam)
if wparam == DBT_DEVICEARRIVAL:
print("Something's arrived")
if dev_broadcast_hdr.dbch_devicetype == DBT_DEVTYP_VOLUME:
print("It's a volume!")
dev_broadcast_volume = DEV_BROADCAST_VOLUME.from_address(lparam)
if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
print("with some media")
drive_letter = drive_from_mask(dev_broadcast_volume.dbcv_unitmask)
print("in drive", chr(ord("A") + drive_letter))
return 1
if __name__ == '__main__':
w = Notification()
win32gui.PumpMessages()
Windows sends all top-level windows a set of default WM_DEVICECHANGE messages when new devices or media (such as a CD or DVD) are added and become available, and when existing devices or media are removed.
Each WM_DEVICECHANGE message has an associated event that describes the change, and a structure that provides detailed information about the change. The structure consists of an event-independent header, DEV_BROADCAST_HDR, followed by event-dependent members. The event-dependent members describe the device to which the event applies. To use this structure, applications must first determine the event type and the device type. Then, they can use the correct structure to take appropriate action.
When the user inserts a new CD or DVD into a drive, applications receive a WM_DEVICECHANGE message with a DBT_DEVICEARRIVAL event. The application must check the event to ensure that the type of device arriving is a volume (the dbch_devicetype member is DBT_DEVTYP_VOLUME) and that the change affects the media (the dbcv_flags member is DBTF_MEDIA).
Here you can find an implementation in C++ directly from Microsoft MSDN.
PROBLEMS:
The code compiles without errors and if I insert a USB drive I get the message "Something's arrived" and "It's a volume!" correctly but the message "with some media" and the drive letter are never displayed so it's like this part of the code doesn't work:
dev_broadcast_volume = DEV_BROADCAST_VOLUME.from_address(lparam)
if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
print("with some media")
drive_letter = drive_from_mask(dev_broadcast_volume.dbcv_unitmask)
print("in drive", chr(ord("A") + drive_letter))
I need to fix the program in order to know also the drive letter of the new media inserted.
UPDATE:
I tried to print the value of dev_broadcast_volume.dbcv_flags and it's 0.
Then I tried to print the value of DBTF_MEDIA and it's 1.
I see that in the code there is an if statement with a bitwise operation:
if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
If both dev_broadcast_volume.dbcv_flags and DBTF_MEDIA were == 1, the bitwise operation would return 1, so the if statement would be True and the code inside would be executed but dev_broadcast_volume.dbcv_flags == 0 so the bitwise operation would return 0 and the if statement is False and the code won't get executed, right?
I tried to remove the if statement entirely and although the check doesn't exist anymore (is it necessary?), now the drive letter is printed correctly.
This is the output of the program I get now:
Something's arrived
It's a volume!
in drive K

Python Wrapper to a C Callback

Trying to create a python callback which needs to be invoked while calling the C callback
from a dll in Windows environment. Please review the code below to understand the issue.
from ctypes import *
#---------qsort Callback-------------#
IntArray5 = c_int * 5
ia = IntArray5(5,1,7,33,99)
libc = cdll.msvcrt
qsort = libc.qsort
qsort.restype = None
CMPFUNC = CFUNCTYPE(c_int,POINTER(c_int),POINTER(c_int) )
test = 0
def py_cmp_func(a,b):
#print 'py_cmp_func:',a[0],b[0]
global test
test = 10000
return a[0]-b[0]
cmp_func = CMPFUNC(py_cmp_func)
qsort(ia, len(ia), sizeof(c_int), cmp_func)
print "global test=",test
for item in ia : print item
#----------Load DLL & Connect ------------#
gobiDLL = WinDLL("C:\LMS\QCWWAN2k.dll")
print 'Output of connect : ',gobiDLL.QCWWANConnect()
#----------SetByteTotalsCallback----------#
tx = POINTER(c_ulonglong)
rx = POINTER(c_ulonglong)
proto_callback = WINFUNCTYPE(c_void_p,tx,rx)
gtx = grx = 0 # Used to copy the response in the py_callback
def py_callback(t,r):
sleep(10)
print 'python callback ...'
print "tx=",t,"rx=",r
global gtx,grx
gtx = 5000 # gtx = t
grx = 2000 # grx = r
#return 0
callback = proto_callback(py_callback)
gobiDLL.SetByteTotalsCallback.restype = c_ulong
gobiDLL.SetByteTotalsCallback.argtypes = [proto_callback,c_byte]
print "SetByteTotalsCallback = ",gobiDLL.SetByteTotalsCallback(callback, c_byte(256))
print "gtx = ",gtx
print "grx = ",grx
The DLL Documents the Prototype and the callback for the SetByteTotalsCallback() method as shown below.
Prototype :
ULONG QCWWANAPI2K SetSessionStateCallback( tFNSessionState pCallback );
Callback :
void ByteTotalsCallback( ULONGLONG txTotalBytes, ULONGLONG rxTotalBytes );
OUTPUT :
>>>
global test= 10000
1
5
7
33
99
Output of connect : 0
SetByteTotalsCallback = 0
gtx = 0
grx = 0
>>>>
The current problem is that the whole program gets called properly,
but the python callback does not get called at all. The program exits with 0 status from
gobiDLL.SetByteTotalsCallback(callback, c_byte(256)) method, but the callback() method written
in python does not called during the call.
Could you please point out what could help enter the python callback ?
The other sample qsort() method passes the pointer to the python function pointer wonderfully.
At a loss to get the root cause of the issue here.
TIA,
Anthony
You can't. C/C++ functions can't access Python functions directly - that function prototype is probably expecting a pointer to C. Python will be passing it a pointer to its internal data structure for that particular function.
This is the time to build a C extension to python to wrap that DLL and expose it to Python. What you'd do is essentially have the C callback call the Python callback, since that can be done. To be clearer, what you want to achieve is:
| This side is C land i.e. "real" addresses
|
Python objects --> C extension -- register callback with --> DLL
| |
in the python | Calls callback
| |
interpreter <-------------- Callback in C extension <-------
|
The following is a very quick explanation for building a calling a python function from C. You'll need to build this code with the MSVC (or alternative tool) that was used to build your Python distribution; use depends.exe to find out which msvcXX.dll it is linked against.
Global state is generally considered bad, but for simplicity that's what I used:
static PyObject* pyfunc_event_handler = NULL;
static PyObject* pyfunc_event_args = NULL;
I then added a set handler function to make the process of setting the callback easier. However, you don't need to do that, you just need to
static PyObject* set_event_handler(PyObject *self, PyObject *args)
{
PyObject *result = NULL;
PyObject *temp;
The next line is the important one - I allow passing of two python objects (the O arguments to PyArg_ParseTuple. One object contains the function, the other its parameters.
if (PyArg_ParseTuple(args, "OO", &temp, &pyfunc_event_args)) {
if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be a function");
return NULL;
}
Sort out references. Python needs you to do this.
Py_XINCREF(temp); /* Add a reference to new func */
Py_XDECREF(pyfunc_event_handler); /* Dispose of previous callback */
pyfunc_event_handler = temp; /* Remember new callback */
/* Boilerplate to return "None" */
Py_INCREF(Py_None);
result = Py_None;
}
return result;
}
You can then call this elsewhere with:
PyObject* arglist = Py_BuildValue("(O)", pyfunc_event_args);
pyobjresult = PyObject_CallObject(pyfunc_event_handler, arglist);
Py_DECREF(arglist);
Don't forget the DECREF, you need Python to gc the arglist.
From python, using this is as simple as:
set_event_handler(func, some_tuple)
Where func has matching parameters like so:
def func(obj):
/* handle obj */
Things you probably want to read up on:
LoadLibrary (load DLL from C).
GetProcAddress (find a function to call).
Extending Python with C or C++ from the Python docs.

Categories

Resources