Problem with wx.ListCtrl with embedded Python+wxPython+wxWindows

Hi,

I'm embedding Python 2.2.3 and wxPython 2.4.1 into my C++ program
(which also uses wxWindows). I've run into a problem, though. If I
try to use a wx.ListCtrl, I get this assert:

src\common\event.cpp(797):assert "wxAssertFailure" failed:
wxPyListCtrl should [not] be a window but it is [not]

The line in question:

    if ( m_isWindow != IsKindOf(info) )
    {
        // assert is in here
    }

m_isWindow is 1.

The same code works fine when run from the DOS prompt (via
python.exe) as a Python script. In addition, the code works fine
embedded if I use a wx.ListBox instead of wx.ListCtrl.

In my program, the list control is part of a larger dialog involving
sizers. The assert is triggered during the line
"self.SetSizer(sizer)" in the constructor. (I'm assuming it's obvious
what this line is for :slight_smile:

When the assert happens, the top bit of the call stack is this:

<just more wxAssert-specific stuff from then up>
wxAssert(int 0, const char * 0x103e6ce0, int 797, const char *
0x103e6cd0, const char * 0x016eb7ec) line 520 + 21 bytes
wxEvtHandler::ProcessEvent(wxEvent & {...}) line 797 + 43 bytes
wxWindow::HandleMove(int 11, int 19) line 4017 + 26 bytes
wxWindow::MSWWindowProc(unsigned int 3, unsigned int 0, long 1245195)
line 2419 + 32 bytes
wxWndProc(HWND__ * 0x001c0784, unsigned int 3, unsigned int 0, long
1245195) line 2372 + 26 bytes

To make matters worse, I'm having some difficulty creating a simple
test program to demonstrate this problem. This program creates a
window, then Python code adds a listbox or listctrl as a child of
that window. With this, I get the wx.ListCtrl assert... but using
wx.ListBox prints a "XXX undetected error" on the console window and
seems to create no list box. (This program is about 120 lines C++ &
20 lines Python, and is appended to the end of this mail.)

I'm really quite lost here -- if anyone has even any vague
suggestions or hints they would be very useful. It is my (Possibly
forlorn) hope that this is a simple problem with an easy fix, though.

Regards,

--Tom

Test program, test1.py:

import wx

def TestFunc(p,x):
    print "TestFunc called like TestFunc(\""+x+"\")"
    #t=wx.ListBox(parent=p) # <-- this gives the assert
    wx.ListBox(parent=p) # <-- this gives "XXX..." on console
def OnExit(frame):#,event):
    print "python got OnExit"
def InitMenus(f):
    if f.GetMenuBar():
        print "already has menu bar"
    else:
        mb=wx.MenuBar()
        mfile=wx.Menu()
        mfile.Append(100,"Exit")
        wx.EVT_MENU(f,100,OnExit)
        mhelp=wx.Menu()
        mhelp.AppendSeparator()
        mb.Append(mfile,"File")
        mb.Append(mhelp,"Help")
        f.SetMenuBar(mb)
    
print "script loaded"

Test program, main.cpp, it's a win32 console program:

#include "pch.h"
#include <stdlib.h>

class TestFrame:
public wxFrame
{
public:
  enum {
    id_button=1,
  };
  TestFrame(wxWindow *parent);
  void OnButton(wxCommandEvent &);
  void OnClose(wxCloseEvent &);
  DECLARE_EVENT_TABLE();
};

TestFrame::TestFrame(wxWindow *parent):
wxFrame(parent,-1,"Test",wxDefaultPosition,wxDefaultSize,
    wxDEFAULT_FRAME_STYLE)
{
  wxButton *b=new wxButton(this,id_button,"Test");
}

void TestFrame::OnButton(wxCommandEvent &) {
  PyObject *test=0,*test_dict=0,*test_TestFunc=0,*test_InitMenus=0;
  
  wxPyBeginBlockThreads();
  test=PyImport_ImportModule("test1");//new ref
  wxASSERT(test);
  test_dict=PyModule_GetDict(test);//borrowed ref
  wxASSERT(test_dict);
  test_TestFunc=PyDict_GetItemString(test_dict,"TestFunc");//b ref
  test_InitMenus=PyDict_GetItemString(test_dict,"InitMenus");//b ref
  wxASSERT(test_TestFunc);
  PyObject *args_TestFunc=PyTuple_New(2);//new ref
  PyObject *args_InitMenus=PyTuple_New(1);//new ref
  PyTuple_SetItem(args_InitMenus,0,
    wxPyMake_wxObject(this));//steals reference
  PyTuple_SetItem(args_TestFunc,0,
    wxPyMake_wxObject(this));//steals reference
  PyTuple_SetItem(args_TestFunc,1,
    PyString_FromString("test string"));//steals reference
  PyObject *res_TestFunc=PyObject_CallObject(test_TestFunc,
    args_TestFunc);//n ref
  PyObject *res_InitMenus=PyObject_CallObject(test_InitMenus,
    args_InitMenus);//n ref
  Py_XDECREF(res_InitMenus);
  Py_XDECREF(res_TestFunc);
  Py_XDECREF(args_InitMenus);
  Py_XDECREF(args_TestFunc);
  Py_XDECREF(test);
  wxPyEndBlockThreads();
}

void TestFrame::OnClose(wxCloseEvent &) {
  printf("bye\n");
  this->Destroy();
}

BEGIN_EVENT_TABLE(TestFrame,wxFrame)
  EVT_BUTTON(id_button,TestFrame::OnButton)
  EVT_CLOSE(TestFrame::OnClose)
END_EVENT_TABLE()

class TestApp:
public wxApp
{
public:
  TestApp();
  ~TestApp();

  bool OnInit();
  int OnExit();
private:
  PyThreadState *pystate_;
};

TestApp::TestApp() {
}

bool TestApp::OnInit() {
  Py_IgnoreEnvironmentFlag=0;
  Py_Initialize();
  PyEval_InitThreads();
  wxPyCoreAPI_IMPORT();
  pystate_=wxPyBeginAllowThreads();
  
  printf("TestApp::OnInit\n");
  TestFrame *tf=new TestFrame(0);
  tf->SetSize(640,480);
  tf->Show(true);
  return true;
}

int TestApp::OnExit() {
  printf("TestApp::OnExit\n");
  return 0;
}

TestApp::~TestApp() {
  wxPyEndAllowThreads(pystate_);
  Py_Finalize();
}

IMPLEMENT_APP(TestApp)

int main(int argc,char *argv) {
  int r=WinMain(GetModuleHandle(0),0,"",SW_SHOW);
  int brk=0;
  return r;
}

wxpython@tomseddon.plus.com wrote:

Hi,

I'm embedding Python 2.2.3 and wxPython 2.4.1 into my C++ program (which also uses wxWindows). I've run into a problem, though. If I try to use a wx.ListCtrl, I get this assert:

src\common\event.cpp(797):assert "wxAssertFailure" failed: wxPyListCtrl should [not] be a window but it is [not]

[...]

I'm really quite lost here -- if anyone has even any vague suggestions or hints they would be very useful. It is my (Possibly forlorn) hope that this is a simple problem with an easy fix, though.

I havn't tried your sample yet but are you sure that the wxWindows DLL used by wxPython is also the one used by your C++ app? This is an absolute must. You can not use a different DLL or a static wxWin library and use wxPython with it's own wxWindows DLL in the same process.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Yes, same DLLs. I've only got one copy of each wxWIndows DLL on my
computer. But anyway, I managed to fix the problem, I think!

After a bit of study, it seems that wxPyListCtrl's wxClassInfo had
NULL pointers for 'm_baseInfo1' and 'm_baseInfo2'. After some more
searching, I came up with this solution: call
wxClassInfo::InitializeClasses() just after the first call to
wxPyCoreAPI_IMPORT().

It must be that wxPython is initializing its classes only once I've
called Py_Initialize() and wxPyCoreAPI_IMPORT(), by which point
wxWindows is already set up. Have I got some settings wrong, or is
this to be expected? If it's what's supposed to happen, I think this
line could be usefully added to the embedded sample.

Regards,

--Tom

···

On 30 Jul 2003 at 13:40, Robin Dunn wrote:

I havn't tried your sample yet but are you sure that the wxWindows DLL
used by wxPython is also the one used by your C++ app? This is an
absolute must. You can not use a different DLL or a static wxWin
library and use wxPython with it's own wxWindows DLL in the same process.

wxpython@tomseddon.plus.com wrote:

I havn't tried your sample yet but are you sure that the wxWindows DLL used by wxPython is also the one used by your C++ app? This is an absolute must. You can not use a different DLL or a static wxWin library and use wxPython with it's own wxWindows DLL in the same process.

Yes, same DLLs. I've only got one copy of each wxWIndows DLL on my computer. But anyway, I managed to fix the problem, I think!

After a bit of study, it seems that wxPyListCtrl's wxClassInfo had NULL pointers for 'm_baseInfo1' and 'm_baseInfo2'.

Yep, that is what I was expecting was the problem. It can also happen if there are more than one copy of wxWindows in the process.

After some more searching, I came up with this solution: call wxClassInfo::InitializeClasses() just after the first call to wxPyCoreAPI_IMPORT().

But this is the right fix in this case. You should also call wxClassInfo::CleanUpClasses() prior to calling InitalizeClasses otherwise you'll get duplicates for the classes that already exist in the system.

BTW, I think the need to do this will be going away in 2.5. Cross your fingers. :wink:

It must be that wxPython is initializing its classes only once I've called Py_Initialize() and wxPyCoreAPI_IMPORT(), by which point wxWindows is already set up. Have I got some settings wrong, or is this to be expected?

Yep.

If it's what's supposed to happen, I think this

line could be usefully added to the embedded sample.

I'll do that. Thanks for chasing it down.

···

On 30 Jul 2003 at 13:40, Robin Dunn wrote:

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!