Writing to the DOS Command Window
We all know that PowerBuilder is great for developing client server applications. Often there is a need to perform batch processing on schedule, usually at night when nobody is around. These batch programs can easily be developed in PowerBuilder. All you have to do is create a non-visual custom class object and call functions in it from the application open event.
A batch program of mine writes status messages to a log file and can run a long time. The run time can be anywhere from 1 minute to over an hour depending on the input volume. PowerBuilder batch programs do not have the ability to write to the command window so the user monitoring the program has no way to tell how it is progressing without periodically opening the log file.
I searched the web looking for a solution that would allow my program to write to the command window. I came across the following article from the PowerBuilder Developer’s Journal:
Writing Console Applications with PowerBuilder 9
By XUE-SONG WU
http://pbdj.sys-con.com/node/42603
This article is a demo of PBNI, a feature introduced in version 9 that allows for two types of C/C++ integration. The first type allows C/C++ programs to call PowerBuilder code. The second type allows you to create PowerBuilder objects in C/C++. The article uses both types.
First is an executable that uses the PBNI function RunApplication to execute an application starting with the open event. Second is a .PBX file which is similar to a .DLL file. In PowerBuilder you right click a library in the system tree and select ‘Import PB Extension’ from the popup menu. Choose the .PBX file and an object is created that forms an interface to the .PBX that you can use just like it were a regular PowerScript object. The object contains functions that you use to interact with the command window.
I made a couple of improvements:
Upgraded the project from Visual C/C++ 6 to Visual Studio 2012.
Added an argument to specify the PowerBuilder VM filename so that it can be used with any version of PowerBuilder.
Added an optional argument that is passed to the application open event commandline argument.
Added a function to set a program return code.
Added a program icon to the executable and changed its name from ‘pb’ to ‘pbconsole’.
I put a zip file containing the Visual Studio project on my website. It includes PowerBuilder 9 and 10.5 examples. You can download it from here:
Hi Roland;
Very "kool" 😎 .. thanks for the update!
Regards ... Chris
I remember that article, I have it's link stored somewhere in my various online PB resources.
Hi Roland,
nice to see some someone that keeps publishing tools for PB 😘
We had developped a plain vanilla console object (nonvisual object + internal struct definitions + api calls) that let create a console when your application is visual, write some (colored) text, but cannot read from the console, though (we had no need for this).
I could post the code, if you wish, maybe that you could integrate the fuctionnalities?
There was an original posting by Bard Mettee posted on May 7, 2012 7:21 AM:
http://nntp-archive.sybase.com/nntp-archive/action/article/%3C4fa67f28.5a43.1681692777%40sybase.com%3E
I improved upon his code slightly to resolve the hanging prompt which did not push enter for the user and re-post the solution here:
/*
GF_Console_Output(String AS_STR)
// External Declarations worked on Windows 7 Enterprise 64 Bit + PowerBuilder 12.50 for Writing CommandLine arguments to command prompt window.
FUNCTION Int AttachConsole(long ProcID) library "Kernel32.dll"
FUNCTION Long GetStdHandle(long nStdHandle) library "Kernel32.dll"
FUNCTION Int FreeConsole() library "Kernel32.dll"
FUNCTION Ulong WriteConsole(long Handle, String OutPut, long NumCharsToWrite, ref long NumCharsWritten, long reserved) library "Kernel32.dll" Alias For "WriteConsoleW"
SUBROUTINE keybd_event( int bVk, int bScan, int dwFlags, int dwExtraInfo) LIBRARY "user32.dll"
SUBROUTINE ExitProcess(ulong uExitCode) LIBRARY "kernel32.dll"
*/
// PowerScript Code
// GF_Console_Output(string as_Str)
long console, hwnd
AttachConsole(-1)
console = GetStdHandle(-12)
hwnd = GetStdHandle(-12)
string s
long result
s = as_Str
s = "~r~n" + s + "~r~n"
WriteConsole(hwnd, s, len(s), result, 0)
WriteConsole(hwnd, CharA(13), len(CharA(13)), result,0)
// Send VK_RETURN 0x0D immediately to the console after last writeconsole is completed.
keybd_event( 13, 1, 0, 0 ) // this solves the hanging prompt issue.
FreeConsole() // de-attach from console
ExitProcess(1)
RETURN
// Full example:
// In your application open object you would have the following:
string ls_cmd
ls_cmd = Trim(CommandParm())
// Then you would analyze it and exit if the commandline was not set or set correctly etc
if isNull(ls_cmd) or ls_cmd = "" then
// cannot process no args.
GI_ERRORLEVEL = -1
gf_console_output("YourCoolProgram.exe v1.00 - Parameter not set")
HALT CLOSE
end if
You can get all kinds of ideas here-after how to use this.
No more hanging prompt
Thanks for posting this, it works great!
I went through all the functions to make sure the datatypes and argument names were correct.
Function boolean AttachConsole(ulong dwProcessId) Library "kernel32.dll"
Function boolean FreeConsole() Library "kernel32.dll"
Function long GetStdHandle(ulong nStdHandle) Library "kernel32.dll"
Function boolean WriteConsole(long hConsoleOutput, string lpBuffer, ulong nNumberOfCharsToWrite, ref ulong lpNumberOfCharsWritten, ulong lpReserved) Library "kernel32.dll" Alias For "WriteConsoleW"
Subroutine keybd_event(integer bVk, integer bScan, ulong dwFlags, ulong dwExtraInfo) Library "user32.dll"
Also, I used -11 (STD_OUTPUT_HANDLE) for the call to GetStdHandle.
So based on your recommended changes to the external declaration's I updated the sample GF_Console_Output() example code as follows.
fixed result variable changing from long result to ulong result
This example also now uses an array so you build your string array with a bunch of messages for the console then call the function with the array and it pops it all out.
// GF_Console_Output(string as_Str[], AB_FreeConsole) - PowerScript Code
long console, hwnd
AttachConsole(-1)
console = GetStdHandle(-11) // instead of -12
hwnd = GetStdHandle(-11) // instead of -12
/*
https://msdn.microsoft.com/en-us/library/windows/desktop/ms683231%28v=vs.85%29.aspx
STD_OUTPUT_HANDLE (DWORD)-11 The standard output device. Initially, this is the active console screen buffer, CONOUT$.
STD_ERROR_HANDLE (DWORD)-12 The standard error device. Initially, this is the active console screen buffer, CONOUT$.
*/
string s
ulong result // changed from long result to ulong result based on latest External Declaration format.
integer i
if upperbound(as_str[]) <= 0 then return -1
// process all args passed in
for i = 1 to upperbound(as_str[])
s = as_Str[i]
s = "~r~n" + s + "~r~n"
WriteConsole(hwnd, s, len(s), result, 0)
WriteConsole(hwnd, CharA(13), len(CharA(13)), result,0)
next
// Subroutine keybd_event(Byte bVk,Byte bScan,ulong dwFlags,ulong dwExtraInfo) LIBRARY "user32.dll"
// Send VK_RETURN 0x0D immediately to the console after last writeconsole is completed.
keybd_event( 13, 1, 0, 0 )
// FreeConsole() // de-attach from console
// ExitProcess(1)
IF AB_FreeConsole THEN
// Only de-attach from console if you dont plan on writing subsequent content in between other activities.
FreeConsole() // de-attach from console
ExitProcess(1)
END IF
RETURN 0
Everything works, except providing you dont do any processing after the initial console dump.
For example after the first console dump if you run some additional functions or code, calling the console dump again doesn't seem to work, probably handle changes?
After playing around realized needed to extend the function again a bit.
Added argument AB_FreeConsole to support writing multiple passes into console in between other function calls writes or other work.
IF AB_FreeConsole THEN
// Only de-attach from console if you dont plan on writing subsequent content in between other activities.
FreeConsole() // de-attach from console
ExitProcess(1)
END IF
So now I can run the program WITH the proper args, it outputs a "header' output , does some work, I can call the gf_Console_Output(AS_Msgs[], FALSE) and fire some console output in between stuff too now.
The final write I make to console i call gf_Console_Output(AS_Msgs[], TRUE) and thats it..
Attached is an NVO I created. It has 4 functions that you call:
of_attachconsole
of_freeconsole
of_writeconsole
of_writetoconsole
Call of_attachconsole once at program startup and of_freeconsole once at program end. You can call of_writeconsole as many times as you want. The function of_writetoconsole doesn't write a CRLF so can be used to append text to the current line.
$PBExportHeader$n_console.sru
forward
global type n_console from nonvisualobject
end type
end forward
global type n_console from nonvisualobject autoinstantiate
end type
type prototypes
Function boolean AttachConsole ( &
ulong dwProcessId &
) Library "kernel32.dll"
Function boolean FreeConsole ( &
) Library "kernel32.dll"
Function long GetStdHandle ( &
ulong nStdHandle &
) Library "kernel32.dll"
Function boolean WriteConsole ( &
long hConsoleOutput, &
string lpBuffer, &
ulong nNumberOfCharsToWrite, &
Ref ulong lpNumberOfCharsWritten, &
ulong lpReserved &
) Library "kernel32.dll" Alias For "WriteConsoleW"
Subroutine keybd_event ( &
integer bVk, &
integer bScan, &
ulong dwFlags, &
ulong dwExtraInfo &
) Library "user32.dll"
end prototypes
type variables
Constant String CRLF = "~r~n"
Boolean IsAttached = False
Long hWnd = 0
end variables
forward prototypes
public function boolean of_attachconsole ()
public subroutine of_freeconsole ()
public subroutine of_writeconsole (string as_msgtext)
public subroutine of_writetoconsole (string as_msgtext)
end prototypes
public function boolean of_attachconsole ();// attach the current process to the calling console window
Constant long INVALID_HANDLE_VALUE = -1
Constant ulong ATTACH_PARENT_PROCESS = -1
Constant ulong STD_OUTPUT_HANDLE = -11
ULong lul_Written
IsAttached = False
// attach to the console
If AttachConsole(ATTACH_PARENT_PROCESS) Then
// get a handle to standard output
hWnd = GetStdHandle(STD_OUTPUT_HANDLE)
If hWnd = INVALID_HANDLE_VALUE Then
FreeConsole()
Return False
End If
IsAttached = True
End If
Return True
end function
public subroutine of_freeconsole ();// free the attached console
If IsAttached Then
// send VK_RETURN 0x0D keystroke to give back keyboard control
keybd_event(13, 1, 0, 0)
// free the console
FreeConsole()
IsAttached = False
End If
end subroutine
public subroutine of_writeconsole (string as_msgtext);// write message to the attached console with
// CRLF so that it appears on the next line
ULong lul_Written
If IsAttached Then
// write CRLF to the console
WriteConsole(hWnd, CRLF, 2, lul_Written, 0)
// write message to the console
WriteConsole(hWnd, as_msgtext, Len(as_msgtext), lul_Written, 0)
End If
end subroutine
public subroutine of_writetoconsole (string as_msgtext);// write message to the attached console without
// CRLF so that it appears on the same line
ULong lul_Written
If IsAttached Then
// write message to the console
WriteConsole(hWnd, as_msgtext, Len(as_msgtext), lul_Written, 0)
End If
end subroutine
on n_console.create
call super::create
TriggerEvent( this, "constructor" )
end on
on n_console.destroy
TriggerEvent( this, "destructor" )
call super::destroy
end on