2

I'm trying to figure out how to proceed and if it is feasible or not in general.

I working with external DLL to control my mechanical delay line.

This API has it internal procedure for message output in separate window. I have a strong desire to catch this message flow and present in my python (PyQT5) written application.

In API description there is a function:

int LS_SetProcessMessagesProc(void *pProc);

Function returns either 0 or 1, if there is no error or it is present, respectively.

According to dll description It enables the replacement of the internal message-dispatching procedure of the LStep API.

The LStep API processes during waiting for confirmation of the LStep in the main-thread messages. If you want to switch of the Message-Dispatching or replace with your onw Code, you can use SetProcessMessagesProc for using a callback-procedure.

pProc must be a pointer to a stdcall-procedure without a parameter:

void MyProcessMessages() {...}

Example: LS.SetProcessMessagesProc(&MyProcessMessages);

As example if we take python stdout, how I can send the message to it?

saldenisov
  • 215
  • 3
  • 10
  • If you load this dll into your own Python process, it must be feasible using ctypes module. Sorry no time to make an example right now. Typing from phone. – Vasily Ryabov May 31 '17 at 19:49
  • @VasilyRyabov, this is my problem, I do not get what I should path to this function LS_SetProcessMessagesProc as a parameter using ctypes. If you can write some kind of example or explain in other words, what should be done, I will be very grateful. – saldenisov Jun 01 '17 at 12:41

2 Answers2

1

Read about ctypes tutorial
Loading-dynamic-link-libraries
Accessing functions from loaded dlls
Calling functions

Linux example there, which uses the standard C library's qsort function:

  1. Load the libc.so.6 dll.

    from ctypes import *
    libc = CDLL("libc.so.6")
    
  2. Get a function pointer to qsort.

    qsort = libc.qsort
    qsort.restype = None
    
  3. Create the type for the callback function
    and implement the Python callback function.

    CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
    
    def py_cmp_func(a, b):
        return a[0] - b[0]
    
    cmp_func = CMPFUNC(py_cmp_func)
    
  4. Define a C-Type Integer Array with values
    and use qsort to sort the Array using cmp_func.

    IntArray5 = c_int * 5
    ia = IntArray5(5, 1, 7, 33, 99)
    
    qsort(ia, len(ia), sizeof(c_int), cmp_func)
    
    for i in ia:
        print(i)
    

Output

1 5 7 33 99
stovfl
  • 14,998
  • 7
  • 24
  • 51
1

I'm going to illustrate everything on the:

which is (a slightly more complicated example of) what you need: a function defined in an external .dll which needs to call another custom function (written by you in Python), via [Python]: ctypes module (on Win).

The code:

import ctypes
from ctypes import wintypes

try:
    from win32gui import GetWindowText
    pywin32_present = True
except ImportError:
    pywin32_present = False


def enum_windows_proc(hwnd, l_param):
    print("HWND: {}\n".format(hwnd))
    if pywin32_present:
        txt = GetWindowText(hwnd)
        if txt and "MSCTFIME UI" not in txt and "Default IME" not in txt:
            print("  Window text: {}\n".format(txt))
    return 1


def main():
    user32_dll = ctypes.windll.LoadLibrary("user32.dll")
    enum_windows = user32_dll.EnumWindows

    WND_ENUM_PROC_TYPE = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
    enum_windows.argtypes = (WND_ENUM_PROC_TYPE, wintypes.LPARAM)
    enum_windows.restype = wintypes.BOOL

    enum_windows(WND_ENUM_PROC_TYPE(enum_windows_proc), wintypes.LPARAM(0))


if __name__ == "__main__":
    main()

Notes:

  • imports:
    • wintypes is a ctypes sub-module that defines a bunch of Ms specific data (constants, structs, enums, ...)
    • [Python]: pywin32 is a Python wrapper over C Win functions, it's basically a more advanced (and pythonic) approach of what ctypes does. It doesn't come with Python by default, it must be manually installed; in our example, it's optional
  • def enum_windows_proc(hwnd, l_param)::
    • It's the Python form of BOOL CALLBACK EnumWindowsProc(_In_ HWND hwnd, _In_ LPARAM lParam);
    • Prints the hwnd (window handle) for each window (note that there will be lots of such windows, since most of them are "invisible" to the user)
    • If pywin32 module is installed, it will be used to extract each window's title(caption). Of course, that can also be done with ctypes but it's a little bit more complicated
    • The title filtering is to avoid printing useless text (for most of the windows). If you want more details, check: [SO]: Get the title of a window of another program using the process name
  • The main function:
    • First, the .dll that contains the function (in our case user32.dll) needs to be loaded. This is done using [MSDN]: LoadLibrary function (Ux: [man]: dlopen). Also, the internal structure (needed for next line to look so simple) of the returned object (user32_dll) is initialized
    • The function (or better: a pointer to it) is being retrieved (enum_windows) using [MSDN]: GetProcAddress function (Ux: [man]: dlsym)
    • The next 3 lines of code are used to let Python know the details about the loaded function pointer (return type and argument types). Note that (in some cases) there is a simpler way (codewise) to do all that, but for learning purposes, it's OK to go through the whole thing
    • Finally, call the external function, with our custom function as an argument

Since there will be lots of output (and will be mostly memory addresses), I won't paste it here.

Going to your problem, based on the function headers you pasted in the question, we can take the same approach (note that the code will not work copy/pasted OOTB):

import ctypes
from ctypes import wintypes

def my_process_messages():
    # Your code here (delete the next (`pass`) line)
    pass

dll_name = "your dll path (full or relative)"
dll_object = ctypes.windll.LoadLibrary(dll_name)
ls_set_process_messages_proc = dll_object.LS_SetProcessMessagesProc

PROCESS_MESSAGES_TYPE = ctypes.WINFUNCTYPE(None)
ls_set_process_messages_proc.argtypes = (PROCESS_MESSAGES_TYPE,)
ls_set_process_messages_proc.restype = ctypes.c_int

print("ls_set_process_messages_proc returned: {}\n".format(ls_set_process_messages_proc(PROCESS_MESSAGES_TYPE(my_process_messages))))

Note: The example is based on the fact that the external .dll:

  • Is Win style (uses stdcall calling convention). If that's not true (it uses cdecl), you need to change (for rigorousity's sake I'm going to say 2 things):

    • ctypes.windll to ctypes.cdll
    • ctypes.WINFUNCTYPE to ctypes.CFUNCTYPE
  • Exports C style functions (not C++ which mangles function names), which I'm almost 100% sure. But, if this is not the case then, sorry, nothing to do here. For more details on this topic, check: [SO]: Excel VBA, Can't Find DLL Entry Point from a DLL file.

CristiFati
  • 38,250
  • 9
  • 50
  • 87