Jump to content
omnibrain

Hosting a console in a Delphi Application

Recommended Posts

I want to host a console in my Delphi application. Editors/IDEs like VS Code and IntelliJ IDEA have an embedded console. I want essentially achieve the same. For the moment it doesn't matter if it is the "old" console oder the new "windows terminal". I want to have a tab or panel in my application with a console that runs cmd or powershell.

 

Trouble is, I don't know where to start. When I search I only find how to write Delphi console applications. Or I find information on how to read the console output of other programs (like with TurboPack DOSCommand). 

 

Share this post


Link to post
5 hours ago, omnibrain said:

I want to have a tab or panel in my application with a console that runs cmd or powershell.

You have to launch the corresponding command interpreter executable. If you don't want to have it in a separate floating window, you must redirect input and output to your own application using pipes. You'll easily find numerous Delphi libraries for the purpose. Search "Delphi Redirect input output" or "interprocess communication using pipes" (remove double quotes) with your favorite search engine.

Share this post


Link to post
11 hours ago, omnibrain said:

I want to host a console in my Delphi application. Editors/IDEs like VS Code and IntelliJ IDEA have an embedded console. I want essentially achieve the same. For the moment it doesn't matter if it is the "old" console oder the new "windows terminal". I want to have a tab or panel in my application with a console that runs cmd or powershell.

 

Trouble is, I don't know where to start. When I search I only find how to write Delphi console applications. Or I find information on how to read the console output of other programs (like with TurboPack DOSCommand). 

 

Start by reading the docs for the AllocConsole API function.

Share this post


Link to post
52 minutes ago, PeterBelow said:

Start by reading the docs for the AllocConsole API function. 

No need to call AllocConsole: it is enough to check "make console application" in the project options AFTER having created a GUI application. Delphi RTL will create a console Window.

Not that it is not enough to run cmd and powershell! See my previous message.

Share this post


Link to post
14 hours ago, FPiette said:

You have to launch the corresponding command interpreter executable. If you don't want to have it in a separate floating window, you must redirect input and output to your own application using pipes. You'll easily find numerous Delphi libraries for the purpose. Search "Delphi Redirect input output" or "interprocess communication using pipes" (remove double quotes) with your favorite search engine.

Yes, but redirecting input and output is not what I want to do. That's possible with the TurboPack DOSCommand I mentioned. But this way I lose everything that makes a (modern) console magic. Colours, clickable links. So I want to "host" the real deal.

I suppose I can start a Console, pass a handle to a panel or something as the parent and have it draw within my program. I don't need the output. I could send input via Windows Messages if necessary.

Share this post


Link to post
7 minutes ago, omnibrain said:

Yes, but redirecting input and output is not what I want to do. That's possible with the TurboPack DOSCommand I mentioned. But this way I lose everything that makes a (modern) console magic. Colours, clickable links. So I want to "host" the real deal.

I suppose I can start a Console, pass a handle to a panel or something as the parent and have it draw within my program. I don't need the output. I could send input via Windows Messages if necessary.

Read the code of an open source project that does this and work out what they do

Share this post


Link to post

In one of my applications I also need just to show a console window in a tab or panel, and I also have no need to capture its output. It's a rarely used option, not a central part of the application.

 

You can get the command window handle by calling GetProcessWindow(pi.dwProcessID), where pi is the PROCESS_INFOMATION available after calling Create Process; and then calling SetParent to set the parent of the command window as your target component. You will also need to call SetWindowPos with the command window handle to position and size the command window over the target component.

 

It's not very pretty, and there may well be more elegant ways to do this, but it is simple, and has been sufficient for me over several Delphi releases up to and including 11.

Share this post


Link to post
10 hours ago, timfrost said:

You can get the command window handle by calling GetProcessWindow(pi.dwProcessID), where pi is the PROCESS_INFOMATION available after calling Create Process;

There is no GetProcessWindow() function in the Win32 API, since windows are not tied to processes, only to threads.  So I'm assuming that function is implemented in your own code, yes?  Probably a combination of EnumWindows() and GetWindowThreadProcessId(), though using EnumThreadWindows() with the pi.dwThreadID instead might make more sense.

10 hours ago, timfrost said:

and then calling SetParent to set the parent of the command window as your target component.

That can be very dangerous if you are not careful.

Is it legal to have a cross-process parent/child or owner/owned window relationship?

Share this post


Link to post

Yes, you are right. This code is so old that I had forgotten that GetProcessWindow is in a library function I borrowed from somewhere else, and which as you say uses EnumWindows.  And thanks for the Raymond Chen link; luckily no problem has ever surfaced and this small program is anyway about to be dropped because it is used to configure a service which has reached EOL.

Share this post


Link to post
2 hours ago, A.M. Hoornweg said:

 

Isn't this what modern internet browser do, having separate child processes for tabs contained in a common host process ?

 

 

They control the code for all processes involved. That's the crucial difference.

  • Like 1

Share this post


Link to post
9 hours ago, A.M. Hoornweg said:

Isn't this what modern internet browser do, having separate child processes for tabs contained in a common host process ?

Yes, they do, but those child processes are specifically designed to interoperate and sync with their host process.  That is not the case when you simply host someone else's UI inside of your own.  That is when bad things can happen, because the child process doesn't know it's being hosted and so the parent and child don't sync their states together.

Edited by Remy Lebeau

Share this post


Link to post

Minimum code I used is:

procedure TForm1.FormCreate(Sender: TObject);
Var
 windowstyle: Integer;
 appthreadid: Cardinal;
 cmdhandle: THandle;
Begin
 cmdhandle := FindWindow('ConsoleWindowClass', 'Command Prompt');

 // Hide title bars and borders of launched application
 windowstyle := GetWindowLong(cmdhandle, GWL_STYLE);
 windowstyle := windowstyle - WS_CAPTION - WS_BORDER - WS_OVERLAPPED - WS_THICKFRAME;
 SetWindowLong(cmdhandle,GWL_STYLE,windowstyle);

 // Attach the container applications input thread to the launched ones, so that we receive user input
 appthreadid := GetWindowThreadProcessId(cmdhandle, nil);
 AttachThreadInput(GetCurrentThreadId, appthreadid, True);

 // Docking. Change the parent of the launched application
 WinApi.Windows.SetParent(cmdhandle, Self.Handle);
 SendMessage(Self.Handle, WM_UPDATEUISTATE, UIS_INITIALIZE, 0);
 UpdateWindow(cmdhandle);

 // Make the docked window fill all the client area of the container
 SetWindowPos(cmdhandle, 0, 0, 0, Self.ClientWidth, Self.ClientHeight, SWP_NOZORDER);

 // This prevents the parent control to redraw on the area of its child windows (the launched application)
 SetWindowLong(Self.Handle, GWL_STYLE, GetWindowLong(Self.Handle, GWL_STYLE) Or WS_CLIPCHILDREN);

// SetForegroundWindow(WindowHandle);
// SetFocus(WindowHandle);
End;

This does not take care of resizing the docked window if the form resizes and you also have to keep an eye on if / when your docked application closes. Also it includes no error checking / handling.

The result is as expected:

 

image.thumb.png.545c0e051c6aeed1e88584f4ff7d77ac.png

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×