WX as a plug in for Non-WX program Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
unknown1987
In need of some credit
In need of some credit
Posts: 6
Joined: Wed Aug 26, 2009 7:43 am
Location: Redmond, WA
Contact:

WX as a plug in for Non-WX program

Post by unknown1987 »

I'm writing a plug-in for an application that does not use WX and the application also runs the message pump. When the application loads the plug-in, I create a wxApp using IMPLEMENT_APP_NO_MAIN(MyApp) and wxEntryStart(...). In the OnInit() of the app I create a wxFrame. The application simply uses the windows functions GetMessage(...), TranslateMessage(...) and DispatchMessage(...); hence, my WX plugin does not invoke the normal WX MainLoop(). With the app running the message pump the wxFrame appears to respond to the input, but when the "X" of the frame is clicked WX does nothing. How can I fix this? What is the appropriate way to integrate WX into an application that wants to run the message pump?
unknown1987
In need of some credit
In need of some credit
Posts: 6
Joined: Wed Aug 26, 2009 7:43 am
Location: Redmond, WA
Contact:

Post by unknown1987 »

So it appears I can let all the windows messages filter through as I was and periodically let the wxMyApp run DeletePendingObjects() and that gets the behavior I want. I would still like to know if anyone knows of a better or more "proper" way to do it.
dsmtoday
In need of some credit
In need of some credit
Posts: 2
Joined: Sun Sep 13, 2009 3:49 am

I injected MDI children into a non-Widgets app

Post by dsmtoday »

I had a situation similar to yours where I could extend an application through a DLL, and the app did not use Widgets, but I wanted to. Doing MDI child injection is not easy - there are a lot of bases to cover. But if you aren't using MDI children, widgets almost works right out of the box.

As you found out, though, there a few things that don't quite work, including tooltips, window cleanup after closure, and some combobox operations. However, you can get almost everything to work with just a few painless hacks.

Widgets depends on the idle loop for getting idles, but since the application is already hogging the loop, we need to hack into it ourselves. You provide the hook process like this

Code: Select all

HHOOK hForegroundIdleHook = NULL;
DWORD CALLBACK ForegroundIdleProc(int nCode, DWORD wParam, LONG lParam)
{
	if (nCode >= 0)
	{
		// pump widget idle loops until done
		// undocumented call to the application's idle loop, which also handles top-level windows
		while (wxTheApp->ProcessIdle())
			;
	}

	return CallNextHookEx(hForegroundIdleHook, nCode, wParam, lParam);
}

// elsewhere...
hForegroundIdleHook = SetWindowsHookEx(WH_FOREGROUNDIDLE, (HOOKPROC)ForegroundIdleProc, NULL, GetCurrentThreadId());
Widgets also needs the message loop for a couple items. We can hack it like this.

Code: Select all

HHOOK hGetMsgHook = NULL;
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (nCode >= 0 && wParam == PM_REMOVE)
	{
		LPMSG lpMsg = (LPMSG)lParam;
		switch (lpMsg->message)
		{
		// undocumented call - need to relay these to widgets so tooltips work
		case WM_MOUSEMOVE:
			wxToolTip::RelayEvent((LPMSG)lParam);
			break;

		// widgets pushes in NULL messages on each pending event
		// using this undocumented call, we effectively forward these messages
		case WM_NULL:
			wxTheApp->ProcessPendingEvents();
			break;
		}
	}

	return CallNextHookEx(hGetMsgHook, nCode, wParam, lParam);
}

// elsewhere...
hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, NULL, GetCurrentThreadId());
There are many other things you have to do to make MDI children fully work, including adding to some of the above code. But this should make all non-MDI child widgets code work, including all the controls.

Good luck with your project!
NinjaNL
Moderator
Moderator
Posts: 899
Joined: Sun Oct 03, 2004 10:33 am
Location: Oosterwolde, Netherlands

Re: I injected MDI children into a non-Widgets app

Post by NinjaNL »

dsmtoday wrote:I had a situation similar to yours where I could extend an application through a DLL, and the app did not use Widgets, but I wanted to. Doing MDI child injection is not easy - there are a lot of bases to cover. But if you aren't using MDI children, widgets almost works right out of the box.

<snip>

There are many other things you have to do to make MDI children fully work, including adding to some of the above code. But this should make all non-MDI child widgets code work, including all the controls.

Good luck with your project!
I am sure that there are many users who would like to know how to do this with a MDI program too. Any chance you could write a tutorial to do this? Perhaps share your code in the code dump?
Follow the development of my screenplay authoring program at http://wxscreenplaywriter.blogspot.com/
skozlov
Knows some wx things
Knows some wx things
Posts: 26
Joined: Tue Dec 16, 2008 12:54 pm

Post by skozlov »

Hello. I'm developing wx-plugin in non wx application too.
And thx to dsmtoday. Your code is works. But some things do not work properly.
For example wxProgressDialog wants event loop (check wxProgressDialog::Update()) for process gui events. So I created gui event loop for this situation in wxApp:

Code: Select all

bool Application::OnInit()
{
  ...

  // Note: do not forget to delete event loop in the desctructor
  wxEventLoopBase *loop = wxEventLoopBase::GetActive();
  wxEventLoopBase::SetActive(new wxGUIEventLoop());
  wxDELETE(loop);

  ...
}
Now wxProgressDialog works fine.

Also shortcuts do not work too (and something wrong with TAB events). And I extended GetMsgProc():

Code: Select all

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
  ...
      default:
        {

          // code from wxGUIEventLoop::PreProcessMessage()
          // (in src/msw/evtloop.cpp)
          // for processing accelerators
          HWND hwnd = lpMsg->hwnd;
          WXDLLIMPEXP_CORE wxWindow *wxGetWindowFromHWND(WXHWND hwnd);
          wxWindow *wndThis = wxGetWindowFromHWND((WXHWND)hwnd);
          wxWindow *wnd;

          // try translations first: the accelerators override everything
          for ( wnd = wndThis; wnd; wnd = wnd->GetParent() )
          {
            if ( wnd->MSWTranslateMessage((WXMSG *)lpMsg))
              break;

            // stop at first top level window, i.e. don't try to process the key
            // strokes originating in a dialog using the accelerators of the parent
            // frame - this doesn't make much sense
            if ( wnd->IsTopLevel() )
              break;
          }

          break;

        }

  ...
}
I'm using above code in the project and hope it is valid)
Does anyone knows tricks about how to process wx events outside wx event loop?
WinXp, wxWidgets 2.8.9, wxWidgets-svn, MSVC 9
dsmtoday
In need of some credit
In need of some credit
Posts: 2
Joined: Sun Sep 13, 2009 3:49 am

Post by dsmtoday »

Thanks for your suggestions about the progress meter and accelerators. I wasn't using either of those so I didn't see the problems. I had tabs working, but these were MDI child windows.

In the code snippets above, I tried to pull out all the hacks I did for the MDI child windows, because there were a lot, and because they were tangled in with hacks I needed to do to make it work with the EXE I am developing the plugin for. So I might have missed something. I did the work in this area of code a year or so ago, but I'm back into it and upgrading everything to 2.9.0. I will post additional items as I find them.

I would like to do a mini tutorial on the mdi child window plugin stuff, but you know how it is with time.

Here are some items I ripped out from the code snippets above. One thing I had to do was create a fake MDI parent frame that didn't really exist with a real window just so the widgets code would be happy. I made a function on it to idle all of its child frames, and I would call that function from the above idle loop hack. I had to keep my own list of child frames since I couldn't use any of the functions to attach them to the fake parent window or else there'd be problems with them being double added within Microsoft Windows (once by the EXE application I'm hacking, once by my fake frame). Anyway, that idle hack right there got rid of most of the problems with controls in my child windows not quite working (I think). But I only checked the controls I needed, I was not exhaustive.

Another problem with child window injection is the WM_MDIACTIVATE message (have to prevent widgets from using it while still allowing Microsoft to use it). Destruction and its affect on the vtable while WM_MDIACTIVATE is being processed is kinda hairy to get around as well.

I would really just like to release code that handles this stuff, unfortunately, it is bound up with additional code I needed to added to make it play well with the EXE I'm writing the plugin for, thus it will take some time to release something that is separated.
skozlov
Knows some wx things
Knows some wx things
Posts: 26
Joined: Tue Dec 16, 2008 12:54 pm

Post by skozlov »

Hey i noticed another problem. I'm using AUI library. And if you try to drag floating panel then nothing happens until you release mouse button.. How to process drag/move events??
WinXp, wxWidgets 2.8.9, wxWidgets-svn, MSVC 9
HarKoT
In need of some credit
In need of some credit
Posts: 7
Joined: Fri Oct 09, 2009 10:01 pm

Post by HarKoT »

Here is how I do to trigger the idle event :

Code: Select all

void AppWx::sendIdleEvent()
{
	SendIdleEvents(_mainFrame,wxIdleEvent());
}
Here is how I do to handle the "cross event"

Code: Select all

BEGIN_EVENT_TABLE(FrameWx, wxFrame)
EVT_CLOSE(FrameWx::EvtClose)
END_EVENT_TABLE()

void FrameWx::EvtClose(wxCloseEvent& event)
{
		PostQuitMessage(0);
}

Code: Select all

while(!_stopApplication)
{
MSG	msg; // Windows Message Structure
	// Is There A Message Waiting?
	msg.hwnd;
	if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
	{
		_hWnd = msg.hwnd;
		// Have We Received A Quit Message?
		if (msg.message==WM_QUIT)
		{
			_stopApplication = true;
		}
		else
		{
			// Translate The Message
			TranslateMessage(&msg);
			// Dispatch The Message
			DispatchMessage(&msg);
		}
	}
	else// If There Are No Messages
	{
		clock_t now = clock();
		if(difftime(now, _lastFrame) < _delta)
		{
			_app->sendIdleEvent();
			Sleep(_lastFrame + _delta - now);
		}
		_lastFrame = now;
		_app->refreshRenderingTarget();
		//send refresh		
	}
}
Did you manage to make your DLL work when it's called from a window application though ? (Mine works only when it's called from a console application)
skozlov
Knows some wx things
Knows some wx things
Posts: 26
Joined: Tue Dec 16, 2008 12:54 pm

Post by skozlov »

My app is an extension for a host program. So it works from a window application...

I think the main difference between your solution and our is that we use hooks for process messages and pass them to the wx event loop and you use just loop with PeekMessage().

So for gui application you have to use hooks i think...

But anyway i can't figure out how to process AUI manager's events with hooks..
WinXp, wxWidgets 2.8.9, wxWidgets-svn, MSVC 9
HarKoT
In need of some credit
In need of some credit
Posts: 7
Joined: Fri Oct 09, 2009 10:01 pm

Post by HarKoT »

Does wxAUI work now in your application ?

Also how did you initialize wxWidgets so it works in a window application ?

At the moment I'm doing like this :

Code: Select all

_app = new AppWx();
	wxApp::SetInstance(_app);
	int		argc=1;
	char**  argv=0;
	CHECK_FAIL_MSG(wxEntryStart(argc,argv),"wx initialization failed");
	

	_app->CallOnInit();
But for some reason it works only for consoles.
skozlov
Knows some wx things
Knows some wx things
Posts: 26
Joined: Tue Dec 16, 2008 12:54 pm

Post by skozlov »

HarKoT wrote: Does wxAUI work now in your application ?
Well, it works, but it have some problems when you try to move/drag aui panel. Everything (or almost everything) freezes until you release mouse button... And i do not know what to do.
HarKoT wrote: Also how did you initialize wxWidgets so it works in a window application ?
Here is my way (i donot post all code):

Code: Select all

// header file
class Application: public wxApp
{
public:
  Application();
  ~Application();

  bool OnInit();
  int OnExit();

  void OnFatalException();

  static bool initWx();
  static bool uninitWx();

private:
  static HHOOK m_idleHook;
  static HHOOK m_msgHook;

  static DWORD CALLBACK IdleProc(int nCode, DWORD wParam, LONG lParam);
  static LRESULT CALLBACK MsgProc(int nCode, WPARAM wParam, LPARAM lParam);
}

...

// cpp file
extern HINSTANCE module_instance;

HHOOK Application::m_idleHook = NULL;
HHOOK Application::m_msgHook = NULL;

IMPLEMENT_APP_NO_MAIN(Application)

Application::Application()
{

  // call this to tell the library to call our OnFatalException()
  wxHandleFatalExceptions();

}
//--------------------------------------------------------------------------------

Application::~Application()
{
  
  wxEventLoopBase *e = wxEventLoopBase::GetActive();
  wxEventLoopBase::SetActive(NULL);
  wxDELETE(e);

}
//--------------------------------------------------------------------------------

bool Application::initWx()
{

  WXDLLIMPEXP_BASE void wxSetInstance(HINSTANCE hInst);
  wxSetInstance(module_instance);
  int argc = 0;
  wxChar **argv = NULL;
  wxEntryStart(argc, argv);

  if(!wxTheApp || !wxTheApp->CallOnInit())
    return false;

  m_idleHook = ::SetWindowsHookEx(
    WH_FOREGROUNDIDLE,
    (HOOKPROC)IdleProc,
    NULL,
    GetCurrentThreadId());

  m_msgHook = ::SetWindowsHookEx(
    WH_GETMESSAGE,
    MsgProc,
    NULL,
    GetCurrentThreadId());

  return true;

}
//--------------------------------------------------------------------------------

bool Application::uninitWx()
{

  if( !wxTheApp )
    return false;

  MainWindow *win = aGui().mainWindow();
  win->Close(true);

  ::UnhookWindowsHookEx(m_idleHook);
  ::UnhookWindowsHookEx(m_msgHook);

  if( wxTheApp )
    wxTheApp->OnExit();

  wxEntryCleanup();

  return true;

}
//--------------------------------------------------------------------------------

DWORD CALLBACK Application::IdleProc(int nCode, DWORD wParam, LONG lParam)
{

  if (nCode >= 0) {
    
    // pump widget idle loops until done
    // undocumented call to the application's idle loop,
    // which also handles top-level windows
    while (wxTheApp->ProcessIdle());

  }

  return CallNextHookEx(m_idleHook, nCode, wParam, lParam);

}
//--------------------------------------------------------------------------------

LRESULT CALLBACK Application::MsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{

  if(nCode >= 0 && wParam == PM_REMOVE){

    LPMSG lpMsg = (LPMSG)lParam;

    switch (lpMsg->message){

      // undocumented call - need to relay these to widgets so tooltips work
      case WM_MOUSEMOVE:
        wxToolTip::RelayEvent((LPMSG)lParam);
        break;

      // widgets pushes in NULL messages on each pending event
      // using this undocumented call, we effectively forward these messages
      case WM_NULL:
        wxTheApp->ProcessPendingEvents();
        break;

      default:
        {

          // code from wxGUIEventLoop::PreProcessMessage()
          // (in src/msw/evtloop.cpp)
          // for processing accelerators
          HWND hwnd = lpMsg->hwnd;
          WXDLLIMPEXP_CORE wxWindow *wxGetWindowFromHWND(WXHWND hwnd);
          wxWindow *wndThis = wxGetWindowFromHWND((WXHWND)hwnd);
          wxWindow *wnd;

          // try translations first: the accelerators override everything
          for(wnd = wndThis; wnd; wnd = wnd->GetParent()){

            if(wnd->MSWTranslateMessage((WXMSG *)lpMsg))
              break;

            // stop at first top level window, i.e. don't try to process the key
            // strokes originating in a dialog using the accelerators of the parent
            // frame - this doesn't make much sense
            if( wnd->IsTopLevel() )
              break;

          }

          break;

        }

    } // switch

  } // if

  return CallNextHookEx(m_msgHook, nCode, wParam, lParam);

}
//--------------------------------------------------------------------------------

bool Application::OnInit()
{
  
  if( !wxApp::OnInit() )
    return false;

  wxInitAllImageHandlers();

  SetExitOnFrameDelete(true);

  ...

  // Here we set gui event loop for stuff
  // like pregress dialog Update() method.

  // just to be sure..
  wxEventLoopBase *loop = wxEventLoopBase::GetActive();
  wxEventLoopBase::SetActive(new wxGUIEventLoop());
  wxDELETE(loop);

  return true;

}
//--------------------------------------------------------------------------------
I don't know if everything above is correct, but it works.. except some parts of the AUI..
WinXp, wxWidgets 2.8.9, wxWidgets-svn, MSVC 9
skozlov
Knows some wx things
Knows some wx things
Posts: 26
Joined: Tue Dec 16, 2008 12:54 pm

Post by skozlov »

I found the problem with AUI.

AUI panel asking to process idle events while you moving it.

Chunk of the code from src\aui\floatpane.cpp:

Code: Select all

void wxAuiFloatingFrame::OnIdle(wxIdleEvent& event)
{
    if (m_moving)
    {
        if (!isMouseDown())
        {
            m_moving = false;
            OnMoveFinished();
        }
        else
        {
            event.RequestMore();
        }
    }
}
event.RequestMore() - ask more processing.

And we process idle evens like:

Code: Select all

while (wxTheApp->ProcessIdle());
And above loop will executes until you release mouse button...

So we have to do something in this loop to process some other events, i think.

Code: Select all

while(wxTheApp->ProcessIdle()){
  // do something.
}
But i do not know what to do in the loop..
Another solution is to remove while loop and do one call of the wxTheApp->ProcessIdle():

Code: Select all

DWORD CALLBACK Application::IdleProc(int nCode, DWORD wParam, LONG lParam)
{

  if (nCode >= 0) {
    
    //while (wxTheApp->ProcessIdle());
    wxTheApp->ProcessIdle();

  }

  return CallNextHookEx(m_idleHook, nCode, wParam, lParam);

}
And this is works... Now we can move panel and everything seems fine...

Also i have another idea:

Code: Select all

DWORD CALLBACK Application::IdleProc(int nCode, DWORD wParam, LONG lParam)
{

  static int count = 0;
  static const int max_count = 4;

  if (nCode >= 0) {
    
    while(wxTheApp->ProcessIdle() && ++count < max_count);
    count = 0;

  }

  return CallNextHookEx(m_idleHook, nCode, wParam, lParam);

}
Here we will process only a few idle events at once (4 in this example).

Am I right? Can anybody tell me... ](*,)

Oh.. f*cking events.
WinXp, wxWidgets 2.8.9, wxWidgets-svn, MSVC 9
Post Reply