Hybrid GUI/Console App

315 views
Skip to first unread message

crjjrc

unread,
Apr 11, 2008, 11:28:22 AM4/11/08
to wx-u...@lists.wxwidgets.org

I think I've got a solution for making a hybrid application that can
either be run in GUI or console mode, depending on command-line
parameters. However, I've been unable to find an example of such a
program using wxWidgets. wx-users contains much discussion about
this, but I'm failing to find any code.

My solutions follows. If any command-line parameter is passed, no GUI
is used. (The check is a bit of a hack on MSW, I know.) Any thing
I'm doing wrong?

#include <iostream>
#include <wx/wx.h>

class GUITest : public wxApp {
bool OnInit();
};

bool GUITest::OnInit() {
wxFrame *frame = new wxFrame(NULL, -1, _T("Test"),
wxDefaultPosition,
wxDefaultSize);
frame->Show();
SetTopWindow(frame);
return true;
}

class GUIlessTest : public wxAppConsole {
bool OnInit();
};

bool GUIlessTest::OnInit() {
std::cout << "guiless" << std::endl;
return false;
}

#ifdef __WXMSW__
extern "C" int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
wxCmdLineArgType lpCmdLine,
int nCmdShow) {
int argc = 2 * lpCmdLine;
#else
int main(int argc, char **argv) {
#endif
if (argc == 2) {
GUIlessTest *app = new GUIlessTest;
} else {
GUITest *app = new GUITest;
}
#ifdef __WXMSW__
return wxEntry(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
#else
return wxEntry(argc, argv);
#endif
}

Vadim Zeitlin

unread,
Apr 11, 2008, 10:36:54 PM4/11/08
to wx-u...@lists.wxwidgets.org
On Fri, 11 Apr 2008 08:28:22 -0700 (PDT) crjjrc <crj...@gmail.com> wrote:

c> My solutions follows. If any command-line parameter is passed, no GUI
c> is used. (The check is a bit of a hack on MSW, I know.) Any thing
c> I'm doing wrong?

I don't think you do anything wrong but it would be nicer (e.g. if you
intend to put this on the wiki or maybe submit for inclusion in the manual
or as a new sample) if wx command line parsing could be used, is it too
late to do this in OnCmdLineParsed()?

Also, writing GUIlessTest seems rather pointless, usually people want to
have their "main()" called if they don't use GUI so you could just replace
this class creation with calls to wxInitialize (or use wxInitializer) and
call some console_main() function.

Regards,
VZ

--
TT-Solutions: wxWidgets consultancy and technical support
http://www.tt-solutions.com/

crjjrc

unread,
Apr 14, 2008, 10:04:30 AM4/14/08
to wx-u...@lists.wxwidgets.org

On Apr 11, 9:36 pm, Vadim Zeitlin <va...@wxwidgets.org> wrote:
> On Fri, 11 Apr 2008 08:28:22 -0700 (PDT) crjjrc <crj...@gmail.com> wrote:
>
> c> My solutions follows. If any command-line parameter is passed, no GUI
> c> is used. (The check is a bit of a hack on MSW, I know.) Any thing
> c> I'm doing wrong?
>
> I don't think you do anything wrong but it would be nicer (e.g. if you
> intend to put this on the wiki or maybe submit for inclusion in the manual
> or as a new sample) if wx command line parsing could be used, is it too
> late to do this in OnCmdLineParsed()?

Considering that OnCmdLineParsed() is a member of wxAppConsole, I
don't think this could be done since we are using the command-line
arguments to figure which application to create. Or am I missing
something?

Alternately I can just trigger the parsing myself by instancing a
wxCmdLineParser in main() or WinMain() and calling Parse(), right?
I'm not sure how to get the appropriate argc and argv for the parser's
constructor though. On Windows, I get one string in lpCmdLine, which
doesn't include the program name. Does wxCmdLineParser::SetCmdLine()
expect the program name to be first? It doesn't say in the docs.
Everywhere else, the incoming argv is char ** and the constructor
expects wxChar **. Is there an easy way to convert this? wxApp*
objects set their own argc and argv somewhere behind the scenes, and
I'm not sure how they do it.

- Chris

Lukasz Michalski

unread,
Apr 14, 2008, 11:01:13 AM4/14/08
to wx-u...@lists.wxwidgets.org
crjjrc pisze:

>
> I think I've got a solution for making a hybrid application that can
> either be run in GUI or console mode, depending on command-line
> parameters. However, I've been unable to find an example of such a
> program using wxWidgets. wx-users contains much discussion about
> this, but I'm failing to find any code.
>
> My solutions follows. If any command-line parameter is passed, no GUI

Does it work from console under unix? (without x-server)

Regards,
Łukasz


signature.asc

Vadim Zeitlin

unread,
Apr 14, 2008, 3:53:42 PM4/14/08
to wx-u...@lists.wxwidgets.org
On Mon, 14 Apr 2008 07:04:30 -0700 (PDT) crjjrc <crj...@gmail.com> wrote:

c> Considering that OnCmdLineParsed() is a member of wxAppConsole, I
c> don't think this could be done since we are using the command-line
c> arguments to figure which application to create. Or am I missing
c> something?

I thought about deriving a wxGUIOrConsoleApp class from wxApp (i.e. the
GUI version) but overriding its virtual Initialize() to call wxAppConsole
version if argc != 1. I didn't test it but I think it should be possible to
make this work.

c> Alternately I can just trigger the parsing myself by instancing a
c> wxCmdLineParser in main() or WinMain() and calling Parse(), right?

With a "smart" wxApp class above you wouldn't need to manually deal with
WinMain() at all.

c> I'm not sure how to get the appropriate argc and argv for the parser's
c> constructor though. On Windows, I get one string in lpCmdLine

You can use wxCmdLineParser::ConvertStringToArgs() just as the code in
src/msw/main.cpp does. But, again, hopefully this is unnecessary anyhow.

w...@61131.com

unread,
Apr 14, 2008, 4:05:07 PM4/14/08
to wx-users
Moin Vadim,
moin crjjrc,

> c> Considering that OnCmdLineParsed() is a member of wxAppConsole, I
> c> don't think this could be done since we are using the command-line
> c> arguments to figure which application to create. Or am I missing
> c> something?
>
> I thought about deriving a wxGUIOrConsoleApp class from wxApp (i.e. the
> GUI version) but overriding its virtual Initialize() to call wxAppConsole
> version if argc != 1. I didn't test it but I think it should be possible to
> make this work.

Some (many?) GUI apps are capable to parse command lines, e.g. to detect which file to open etc. IMHO, just checking argc != 1 seems
too restrictive.

regards
Friedrich

Vadim Zeitlin

unread,
Apr 14, 2008, 4:07:27 PM4/14/08
to wx-u...@lists.wxwidgets.org

Yes, sure, it was just an example, we probably would want to use
wxCmdLineParser in some way if we wanted to do it properly anyhow.

crjjrc

unread,
Apr 15, 2008, 10:56:59 AM4/15/08
to wx-u...@lists.wxwidgets.org

On Apr 14, 2:53 pm, Vadim Zeitlin <va...@wxwidgets.org> wrote:

> c> Considering that OnCmdLineParsed() is a member of wxAppConsole, I
> c> don't think this could be done since we are using the command-line
> c> arguments to figure which application to create. Or am I missing
> c> something?
>
> I thought about deriving a wxGUIOrConsoleApp class from wxApp (i.e. the
> GUI version) but overriding its virtual Initialize() to call wxAppConsole
> version if argc != 1. I didn't test it but I think it should be possible to
> make this work.

I like this idea quite a bit. I found I had a bit more to do to get
it to work, and more things probably remain. I had to override
wxApp::CleanUp() since it too fiddled with GTK. And I had to make
sure the active log target was stderr instead of wxLogGui. It seemed
the most correct way to do this was derive a new wxAppTraits class and
override CreateLogTarget().

Here's the code, which now uses wxCmdLineParser:

------------
#include <iostream>
#include <wx/wx.h>
#include <wx/apptrait.h>
#include <wx/cmdline.h>

class HybridTraits : public wxGUIAppTraits {
public:
HybridTraits(bool gui_enabled)
: wxGUIAppTraits(),
gui_enabled(gui_enabled) {
}

wxLog *CreateLogTarget() {
if (gui_enabled) {
return wxGUIAppTraits::CreateLogTarget();
} else {
return new wxLogStderr;
}
}

private:
bool gui_enabled;
};

class GUITest : public wxApp {
private:
bool OnInit();

bool Initialize(int& argc, wxChar **argv) {
static const wxCmdLineEntryDesc desc[] = {
{ wxCMD_LINE_SWITCH, "c", "console", "run in console
mode" },
{ wxCMD_LINE_NONE }
};

wxCmdLineParser parser(desc, argc, argv);
if (parser.Parse(true) != 0) {
exit(1);
}

gui_enabled = !parser.Found("c");
if (gui_enabled) {
return wxApp::Initialize(argc, argv);
} else {
return wxAppConsole::Initialize(argc, argv);
}
}

void CleanUp() {
if (gui_enabled) {
wxApp::CleanUp();
} else {
wxAppConsole::CleanUp();
}
}

HybridTraits *CreateTraits() {
return new HybridTraits(gui_enabled);
}

bool gui_enabled;
};

bool GUITest::OnInit() {

wxString msg;

if (gui_enabled) {
msg = "in gui mode";
} else {
msg = "in console mode";
}

wxLogMessage(msg);
return false;

}

IMPLEMENT_APP(GUITest);

------------

Vadim, if you think this would make a good sample, I'm happy to make
any changes to get it to a presentable state.

> You can use wxCmdLineParser::ConvertStringToArgs() just as the code in
> src/msw/main.cpp does. But, again, hopefully this is unnecessary anyhow.

Good to know. Thanks!

- Chris

Vadim Zeitlin

unread,
Apr 15, 2008, 1:39:35 PM4/15/08
to wx-u...@lists.wxwidgets.org
On Tue, 15 Apr 2008 07:56:59 -0700 (PDT) crjjrc <crj...@gmail.com> wrote:

c> I like this idea quite a bit. I found I had a bit more to do to get
c> it to work, and more things probably remain. I had to override
c> wxApp::CleanUp() since it too fiddled with GTK.

Sorry, what exactly was the problem? I think we should fix it in wxGTK.

c> And I had to make
c> sure the active log target was stderr instead of wxLogGui. It seemed
c> the most correct way to do this was derive a new wxAppTraits class and
c> override CreateLogTarget().

Indeed.

c> Here's the code, which now uses wxCmdLineParser:
c>
c> ------------
c> #include <iostream>
c> #include <wx/wx.h>
c> #include <wx/apptrait.h>
c> #include <wx/cmdline.h>
c>
c> class HybridTraits : public wxGUIAppTraits {
c> public:
c> HybridTraits(bool gui_enabled)
c> : wxGUIAppTraits(),
c> gui_enabled(gui_enabled) {
c> }
c>
c> wxLog *CreateLogTarget() {
c> if (gui_enabled) {
c> return wxGUIAppTraits::CreateLogTarget();
c> } else {
c> return new wxLogStderr;
c> }
c> }
c>
c> private:
c> bool gui_enabled;
c> };
c>
c> class GUITest : public wxApp {
c> private:
c> bool OnInit();
c>
c> bool Initialize(int& argc, wxChar **argv) {
c> static const wxCmdLineEntryDesc desc[] = {
c> { wxCMD_LINE_SWITCH, "c", "console", "run in console mode" },
c> { wxCMD_LINE_NONE }
c> };
c>
c> wxCmdLineParser parser(desc, argc, argv);
c> if (parser.Parse(true) != 0) {
c> exit(1);
c> }
c>
c> gui_enabled = !parser.Found("c");
c> if (gui_enabled) {
c> return wxApp::Initialize(argc, argv);
c> } else {
c> return wxAppConsole::Initialize(argc, argv);
c> }
c> }
c>
c> void CleanUp() {
c> if (gui_enabled) {
c> wxApp::CleanUp();
c> } else {
c> wxAppConsole::CleanUp();
c> }
c> }
c>
c> HybridTraits *CreateTraits() {
c> return new HybridTraits(gui_enabled);
c> }
c>
c> bool gui_enabled;
c> };
c>
c> bool GUITest::OnInit() {
c>
c> wxString msg;
c>
c> if (gui_enabled) {
c> msg = "in gui mode";
c> } else {
c> msg = "in console mode";
c> }
c>
c> wxLogMessage(msg);
c> return false;
c>
c> }
c>
c> IMPLEMENT_APP(GUITest);
c>
c> ------------
c>
c> Vadim, if you think this would make a good sample, I'm happy to make
c> any changes to get it to a presentable state.

Yes, I think this is a useful example and I'd be favourable to adding it
as a new sample. I don't have a good name for it though ("hybrid" is too
generic), do you?

Thanks,

Fabian Cenedese

unread,
Apr 16, 2008, 5:16:31 AM4/16/08
to wx-u...@lists.wxwidgets.org

>c> Vadim, if you think this would make a good sample, I'm happy to make
>c> any changes to get it to a presentable state.
>
> Yes, I think this is a useful example and I'd be favourable to adding it
>as a new sample.

I haven't tried this code, but when I once tried to create such an app
I was told that it's not possible on windows because the linker decides
the type of application with /SUBSYSTEM:WINDOWS or CONSOLE.

So how would this hybrid app now be created? Would it still have a
console attached even when using a GUI?

Thanks

bye Fabi


crjjrc

unread,
Apr 16, 2008, 11:32:40 AM4/16/08
to wx-u...@lists.wxwidgets.org

On Apr 15, 12:39 pm, Vadim Zeitlin <va...@wxwidgets.org> wrote:

> c> I like this idea quite a bit. I found I had a bit more to do to get
> c> it to work, and more things probably remain. I had to override
> c> wxApp::CleanUp() since it too fiddled with GTK.
>
> Sorry, what exactly was the problem? I think we should fix it in wxGTK.

Well, the assertion failure occurs in wxApp::CleanUp in src/gtk/
app.cpp. At last checkout, this was line 529.

void wxApp::CleanUp()
{
if (m_idleSourceId != 0)
g_source_remove(m_idleSourceId);

// release reference acquired by Initialize()
g_type_class_unref(g_type_class_peek(GTK_TYPE_WIDGET)); // line
529

However, I don't think we can say it constitutes a bug. If I override
Initialize(), in which the GTK libraries are dynamically loaded and
which fails if they're not, I should probably expect that I need to
override any other wxApp functions that assume GTK is available.

> Yes, I think this is a useful example and I'd be favourable to adding it

> as a new sample. I don't have a good name for it though ("hybrid" is too
> generic), do you?

guioptional? guinogui?

I'm not sure how to address any Windows issues, since I don't have a
Windows machine to build on. It was my understanding that wApps do
not show the console window at all, which would be the case here.
wxAppConsoles, on the other, optionally show the console window.

- Chris

Vadim Zeitlin

unread,
Apr 17, 2008, 7:48:36 AM4/17/08
to wx-u...@lists.wxwidgets.org
On Wed, 16 Apr 2008 11:16:31 +0200 Fabian Cenedese <Cene...@indel.ch> wrote:

FC> I haven't tried this code, but when I once tried to create such an app
FC> I was told that it's not possible on windows because the linker decides
FC> the type of application with /SUBSYSTEM:WINDOWS or CONSOLE.
FC>
FC> So how would this hybrid app now be created? Would it still have a
FC> console attached even when using a GUI?

Good question, I forgot about this issue to be honest. There is indeed a
problem with this under MSW because console applications always have a
console and while it can be explicitly destroyed with FreeConsole() it is
still visible on startup. And while Windows applications can be run from
the console, they don't have any console of their own. I thought that doing
::AttachConsole(ATTACH_PARENT_PROCESS) could help here but this function
fails for me (Windows 2003) with "invalid handle" error and, anyhow, it's
XP and later only. And in fact even allocating a new console doesn't work
(the console does appear but printf() still does nothing).

So it unfortunately seems that the usefulness of this method under Windows
is indeed limited to console applications which sometimes need to show GUI.

Regards,

Vadim Zeitlin

unread,
Apr 17, 2008, 8:06:51 AM4/17/08
to wx-u...@lists.wxwidgets.org
On Thu, 17 Apr 2008 13:48:36 +0200 I wrote:

VZ> I thought that doing ::AttachConsole(ATTACH_PARENT_PROCESS) could help
VZ> here but this function fails for me (Windows 2003) with "invalid
VZ> handle" error and, anyhow, it's XP and later only. And in fact even
VZ> allocating a new console doesn't work (the console does appear but
VZ> printf() still does nothing).

Of course, it was enough to send this message to find the solution: you
need to call freopen("CONOUT$", "wt", stdout) after allocating the console
to make console output work.

So on the systems where AttachConsole() is available we have a working
solution. For the older ones we have to fall back to AllocConsole() even if
this means creating a new console when running from an existing one.

Of course, AttachConsole() is still not perfect (problems with redirection
(although maybe this could be worked around by looking at GetStartupInfo()?,
the console is shared with the parent process, input apparently doesn't
work, ...) but then Windows console subsystem is so broken that it hardly
seems fair to expect too much from it.

Vadim Zeitlin

unread,
Apr 17, 2008, 7:52:34 AM4/17/08
to wx-u...@lists.wxwidgets.org
On Wed, 16 Apr 2008 08:32:40 -0700 (PDT) crjjrc <crj...@gmail.com> wrote:

c> Well, the assertion failure occurs in wxApp::CleanUp in src/gtk/
c> app.cpp. At last checkout, this was line 529.
c>
c> void wxApp::CleanUp()
c> {
c> if (m_idleSourceId != 0)
c> g_source_remove(m_idleSourceId);
c>
c> // release reference acquired by Initialize()
c> g_type_class_unref(g_type_class_peek(GTK_TYPE_WIDGET)); // line
c> 529
c>
c> However, I don't think we can say it constitutes a bug. If I override
c> Initialize(), in which the GTK libraries are dynamically loaded and
c> which fails if they're not, I should probably expect that I need to
c> override any other wxApp functions that assume GTK is available.

Yes, thinking more about this you're right, CleanUp() is only supposed to
be called if Initialize() succeeded.

c> > Yes, I think this is a useful example and I'd be favourable to adding it
c> > as a new sample. I don't have a good name for it though ("hybrid" is too
c> > generic), do you?
c>
c> guioptional? guinogui?
c>
c> I'm not sure how to address any Windows issues, since I don't have a
c> Windows machine to build on. It was my understanding that wApps do
c> not show the console window at all, which would be the case here.
c> wxAppConsoles, on the other, optionally show the console window.

It doesn't depend on the class used in the code but on the linker option
specified. But while you can use /subsystem:console with GUI applications
and vice versa there is unfortunately still a problem, see my other post in
this thread.

Thibault Genessay

unread,
Apr 18, 2008, 3:47:39 AM4/18/08
to wx-u...@lists.wxwidgets.org
Hi Guys

> ::AttachConsole(ATTACH_PARENT_PROCESS) could help here but this function
> fails for me (Windows 2003) with "invalid handle" error and, anyhow, it's
> XP and later only. And in fact even allocating a new console doesn't work
> (the console does appear but printf() still does nothing).
>
> So it unfortunately seems that the usefulness of this method under Windows
> is indeed limited to console applications which sometimes need to show GUI.

Attached is a bunch of code that creates a console for a process that
was linked with the /SUBSYSTEM:WINDOWS. I use it on Win XP and it
works as expected. Not so broken, after all.

Regards

Thibault


void allocateConsole()
{
int hConHandle;

HANDLE stdHandle;

CONSOLE_SCREEN_BUFFER_INFO coninfo;

FILE *fp;

// allocate a console for this app
AllocConsole();

// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
&coninfo);
coninfo.dwSize.Y = 300;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
coninfo.dwSize);


// redirect unbuffered STDOUT to the console
stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
hConHandle = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "w" );
*stdout = *fp;
setvbuf( stdout, NULL, _IONBF, 0 );


// redirect unbuffered STDIN to the console
stdHandle = GetStdHandle(STD_INPUT_HANDLE);
hConHandle = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "r" );
*stdin = *fp;
setvbuf( stdin, NULL, _IONBF, 0 );

// redirect unbuffered STDERR to the console
stdHandle = GetStdHandle(STD_ERROR_HANDLE);
hConHandle = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "w" );
*stderr = *fp;
setvbuf( stderr, NULL, _IONBF, 0 );

// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
// point to console as well

std::ios::sync_with_stdio();
}

Reply all
Reply to author
Forward
0 new messages