Threading module and embedded python

Eko palypse ekopalypse at gmail.com
Thu Apr 16 18:29:09 EDT 2020


Thank you for your help.
I made a minimum example which just creates the dialog window.
This runs, using the standard python37.exe without a problem.
But this also runs within the embedded interpreter slightly different
then my previous code.
In my previous code I used the hwnd from the C++ app

          DialogBoxIndirectParam(None,
                                (ctypes.c_ubyte *
len(self._array)).from_buffer_copy(self._array),
                                self.parent_hwnd,
                                DIALOGPROC(self._dlgproc),
                                0)

now I'm using None.
This still does NOT show the dialog when it is called,
BUT the C++ app keeps responsive and if I close it, then the dialog
appears. (???)

> Maybe in the embedded version you do not have an HWND that is usable, can
NULL be used?

The HWND I normally use should be correct as I have to use it in various
other calls
with SendMessageW and those methods works.

>  Is there a SHOW arg that you need to pass?

No.


Here the minimal code, just in case one is interested.

import ctypes
from ctypes import wintypes
import platform
from threading import Thread

user32 = ctypes.WinDLL('user32')

LRESULT = wintypes.LPARAM
DIALOGPROC = ctypes.WINFUNCTYPE(LRESULT,
                                wintypes.HWND,
                                wintypes.UINT,
                                wintypes.WPARAM,
                                wintypes.LPARAM)

INT_PTR = wintypes.INT if platform.architecture()[0] == '32bit' else
wintypes.LARGE_INTEGER

DialogBoxIndirectParam = user32.DialogBoxIndirectParamW
DialogBoxIndirectParam.restype = wintypes.HWND
DialogBoxIndirectParam.argtypes = [wintypes.HINSTANCE,
                                   ctypes.POINTER(ctypes.c_ubyte),
                                   wintypes.HWND,
                                   DIALOGPROC,
                                   wintypes.LPARAM]

GetDlgItem = user32.GetDlgItem
GetDlgItem.restype = wintypes.HWND
GetDlgItem.argtypes = [wintypes.HWND, wintypes.INT]

EndDialog = user32.EndDialog
EndDialog.restype = wintypes.BOOL
EndDialog.argtypes = [wintypes.HWND, INT_PTR]


def align_struct(tmp):
    ''' align control structure to dword size '''
    dword_size = ctypes.sizeof(wintypes.DWORD)
    align = dword_size - (len(tmp) % dword_size)
    if align < dword_size:
        tmp += bytearray(align)
    return tmp


class DlgWindow(Thread):
    ''' Implements a threaddialog template window '''

    def __init__(self):
        super().__init__()

        self._array = bytearray()  # DLGTEMPLATEEX structure buffer
        self._array += wintypes.WORD(1)  # dlgVer
        self._array += wintypes.WORD(0xFFFF)  # signature
        self._array += wintypes.DWORD(0)  # helpID
        self._array += wintypes.DWORD(0)  # exStyle
        #                              WS_POPUP | WS_BORDER | WS_SYSMENU |
WS_CAPTION | DS_MODALFRAME | DS_SETFONT | DS_CENTER
        self._array += wintypes.DWORD(0x80000000 | 0x00800000 | 0x00080000
| 0x00C00000 | 0x80 | 0x40 | 0x800)  # style
        self._array += wintypes.WORD(0)  # cDlgItems
        self._array += wintypes.SHORT(0)  # x
        self._array += wintypes.SHORT(0)  # y
        self._array += wintypes.SHORT(200)  # cx
        self._array += wintypes.SHORT(200)  # cy
        self._array += wintypes.WORD(0)  # menu
        self._array += wintypes.WORD(0)  # windowClass
        self._array += ctypes.create_unicode_buffer('Test Dialog')  # title
        self._array += wintypes.WORD(9)  # pointsize
        self._array += wintypes.WORD(400)  # weight
        self._array += wintypes.BYTE(0)  # italic
        self._array += wintypes.BYTE(0)  # charset
        self._array += ctypes.create_unicode_buffer('MS Shell Dlg')  #
typeface


    def _dlgproc(self, hwnd, msg, wparam, lparam):
        print(hwnd, msg, wparam, lparam)
        if msg == 16:  # WM_CLOSE
            EndDialog(hwnd, 0)
            return 1

        elif msg == 272:  # WM_INITDIALOG
            return 1

        return 0


    def run(self):
        ''' create the dialog window '''
        self._array = align_struct(self._array)
        DialogBoxIndirectParam(None,
                               (ctypes.c_ubyte *
len(self._array)).from_buffer_copy(self._array),
                               None,
                               DIALOGPROC(self._dlgproc),
                               0)


def test_window():
    dlg = DlgWindow()
    dlg.start()


if __name__ == '__main__':
    test_window()

Thanks
Eren

Am Do., 16. Apr. 2020 um 18:33 Uhr schrieb Barry Scott <
barry at barrys-emacs.org>:

>
>
> > On 16 Apr 2020, at 14:55, Eko palypse <ekopalypse at gmail.com> wrote:
> >
> > Barry, sorry for sending you a private message yesterday, was not
> intended.
> >
> > No, I only have rudimentary knowledge of C++,
> > but it has been developing since I started using Cython.
> > I haven't done any stack analysis yet but hey, there's always a first
> time.
> > I think my stubbornness could be of help here :-)
>
> Its a very useful when the simple debug stuff fails.
>
> >
> > Visual Studio reports that the last location of the thread is in
> _ctypes.pyd
> > and the call stack window shows that the last execution is
> > user32.dll!InternalDailogBox().
> >
> > Call Stack
> >
> >
> >
> *user32.dll!InternalDialogBox()user32.dll!DialogBoxIndirectParamAorW()user32.dll!DialogBoxIndirectParamW()_ctypes.pyd!000007fee7fc17e3()_ctypes.pyd!000007fee7fbfee3()_ctypes.pyd!000007fee7fbb4c5()_ctypes.pyd!000007fee7fbc019()_ctypes.pyd!000007fee7fb6dfa()python37.dll!_PyObject_FastCallKeywords(_object
> > * callable=0x0000000002fa8c78, _object * const *
> stack=0x0000000005261c78,
> > __int64 nargs=5, _object * kwnames=0x0000000000000000)*
>
> My guess is that you are missing an important parameter to the dialog that
> allows it be seen.
> Test the dialog code outside of the embedded python, with a command line
> python.
> I recall that you have to pass in the parent for a dialog. Maybe in the
> embedded version
> you do not have an HWND that is usable, can NULL be used?
> Is there a SHOW arg that you need to pass?
>
> I'd check the MSDN docs for the call you are making and check every param
> is as required.
>
> (Its been a along time since I did low level win32 in anger so forgive the
> lack of solutions)
>
>
> >
> >
> > The thread is neither suspended nor does it have any different status
> than
> > the main thread
> > which loops through its main event queue at this point.
>
> It is suspended inside the user32.dll.
>
> Barry
>
> >
> > Thank you
> > Eren
> >
> >
> > Am Mi., 15. Apr. 2020 um 22:57 Uhr schrieb Barry <barry at barrys-emacs.org
> >:
> >
> >>
> >>
> >>> On 15 Apr 2020, at 21:18, Eko palypse <ekopalypse at gmail.com> wrote:
> >>>
> >>> Thank you for your suggestion. I will give it a try.
> >>>
> >>>> What is the "stuck" thread doing? waiting for a lock?
> >>>
> >>> No, it should open a dialog created with DialogBoxIndirectParamW
> >>> <
> >>
> https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-dialogboxindirectparamw
> >>>
> >>
> >> I assume you are a C++ developer and can look at the stack of the
> thread.
> >> What is the thread doing? Is it in python code? Is it in windows code?
> >>
> >> Barry
> >>
> >>> .
> >>>
> >>> Eren
> >>>
> >>>> Am Mi., 15. Apr. 2020 um 20:12 Uhr schrieb Barry Scott <
> >>>> barry at barrys-emacs.org>:
> >>>>
> >>>>
> >>>>
> >>>>>> On 15 Apr 2020, at 13:30, Eko palypse <ekopalypse at gmail.com> wrote:
> >>>>>
> >>>>> Hi everyone,
> >>>>>
> >>>>> the following happens on Windows7 x64 and Python37 x64
> >>>>>
> >>>>> I have a plugin DLL for a C++ application in which Python37 is
> >> embedded.
> >>>>> The plugin itself works, except when I want to use the threading
> >> module.
> >>>>>
> >>>>> If I start a Python script in my plugin which uses the threading
> module
> >>>>> I can verify via ProcessExplorer that the thread is started,
> >>>>> but it doesn't do anything (??) and the c++ application doesn't
> really
> >>>> do anything anymore either.
> >>>>>
> >>>>> Only when I stop the C++ Applikation, the thread becomes active for a
> >>>> short time.
> >>>>> Verified with logging module over time print-outs.
> >>>>>
> >>>>> Apparently I did not understand everything about threads and embedded
> >>>> python.
> >>>>>
> >>>>> Any idea what I'm doing wrong?
> >>>>
> >>>> This is what I typically do.
> >>>>
> >>>> Make sure that you have installed the Python debug files.
> >>>> Now you can use the visual C++ debugger to attach to the process and
> >>>> look at what the threads are doing.
> >>>>
> >>>> I always have the python source code on hand to read as well.
> >>>>
> >>>> This should give you a clue.
> >>>>
> >>>> What is the "stuck" thread doing? waiting for a lock?
> >>>>
> >>>> Barry
> >>>>
> >>>>
> >>>>
> >>>>
> >>>>>
> >>>>>
> >>>>> The whole thing is initialized by the DllMain routine.
> >>>>>
> >>>>>
> >>>>> BOOL APIENTRY DllMain( HANDLE hModule,
> >>>>>                     DWORD  reasonForCall,
> >>>>>                     LPVOID /* lpReserved */ )
> >>>>> {
> >>>>>  switch ( reasonForCall )
> >>>>>  {
> >>>>>      case DLL_PROCESS_ATTACH:
> >>>>>          if (!Py_IsInitialized())
> >>>>>          {
> >>>>>              PyImport_AppendInittab("Npp", &PyInit_Npp);
> >>>>>              Py_InitializeEx(0);
> >>>>>              PyEval_InitThreads();  //<- this shouldn't be needed as
> I
> >>>> understand that it is called by Py_InitializeEx anyway
> >>>>>          }
> >>>>>          PyImport_ImportModule("Npp");
> >>>>>          break;
> >>>>>      case DLL_PROCESS_DETACH:
> >>>>>          Py_Finalize();
> >>>>>          break;
> >>>>>
> >>>>>      case DLL_THREAD_ATTACH:
> >>>>>          break;
> >>>>>
> >>>>>      case DLL_THREAD_DETACH:
> >>>>>          break;
> >>>>>  }
> >>>>>
> >>>>>  return TRUE;
> >>>>> }
> >>>>>
> >>>>> and the code in the plugin which executes the python scripts is this
> >>>>>
> >>>>> cdef void run_code():
> >>>>>  try:
> >>>>>      global_dict = globals()
> >>>>>      if '__name__' not in global_dict or global_dict['__name__'] !=
> >>>> '__main__':
> >>>>>          global_dict.update({"__name__": "__main__",})
> >>>>>      exec(compile(editor.getText(), '<string>', 'exec'), global_dict)
> >>>>>
> >>>>>  except Exception:
> >>>>>      MessageBoxW(nppData._nppHandle,
> >>>>>                  traceback.format_exc(),
> >>>>>                  'RUN CODE EXCEPTION',
> >>>>>                  0)
> >>>>>
> >>>>> I don't know if this is important, but the DLL is generated by
> Cython.
> >>>>>
> >>>>> Thank you for reading and stay healthy
> >>>>>
> >>>>> Eren
> >>>>> --
> >>>>> https://mail.python.org/mailman/listinfo/python-list
> >>>>>
> >>>>
> >>>>
> >>> --
> >>> https://mail.python.org/mailman/listinfo/python-list
> >>>
> >>
> >>
> > --
> > https://mail.python.org/mailman/listinfo/python-list
>
>


More information about the Python-list mailing list