Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1463

[VB6] How to embed console in a VB6 form

$
0
0
This is the cheapest implementation by using cExec redirection of input/output streams to emulate embedded console of cmd.exe into a black colored textbox on a VB6 form, much similar to how VS Code and other editors/IDEs have this in a panel.

Code:

'=========================================================================
'
' EmbedConsole (c) 2023 by wqweto@gmail.com
'
' Emulates embedded console in a VB6 form
'
'=========================================================================
Option Explicit
DefObj A-Z
Private Const MODULE_NAME As String = "Form1"

#Const ImpleUseMST = False

'=========================================================================
' API
'=========================================================================

Private Declare Function DefWindowProc Lib "user32" Alias "DefWindowProcW" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function NtQueryInformationProcess Lib "ntdll" (ByVal ProcessHandle As Long, ByVal InformationClass As Long, ByRef ProcessInformation As Any, ByVal ProcessInformationLength As Long, ByRef ReturnLength As Long) As Long
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Long, ByRef lpBuffer As Any, ByVal nSize As Long, ByRef lpNumberOfBytesWritten As Long) As Long

'=========================================================================
' Constants and member variables
'=========================================================================

Private WithEvents m_oText  As TextBox
Private m_oExec            As cExec
Private m_sInput            As String
Private m_sAutoComplete    As String
Private m_lPos              As Long
#If ImpleUseMST Then
    Private m_pTimer        As stdole.IUnknown
#End If

'=========================================================================
' Error handling
'=========================================================================

Private Sub PrintError(sFunction As String)
    #If USE_DEBUG_LOG <> 0 Then
        DebugLog MODULE_NAME, sFunction & "(" & Erl & ")", Err.Description & " &H" & Hex$(Err.Number), vbLogEventTypeError
    #Else
        Debug.Print "Critical error: " & Err.Description & " [" & MODULE_NAME & "." & sFunction & "]"
    #End If
End Sub

'=========================================================================
' Properties
'=========================================================================

#If ImpleUseMST Then
Private Property Get pvAddressOfTimerProc() As Form1
    Set pvAddressOfTimerProc = InitAddressOfMethod(Me, 0)
End Property
#End If

'=========================================================================
' Methods
'=========================================================================

Private Function pvInit(oText As TextBox, oExec As cExec, Optional Error As String) As Boolean
    Set m_oText = oText
    Set m_oExec = oExec
    If Not m_oExec.Run(Environ$("COMSPEC"), StartHidden:=True) Then
        Error = m_oExec.LastError
        GoTo QH
    End If
    #If ImpleUseMST Then
        Set m_pTimer = InitFireOnceTimerThunk(Me, pvAddressOfTimerProc.TimerProc)
    #Else
        Timer1.Enabled = True
    #End If
    '--- success
    pvInit = True
QH:
End Function

Private Sub pvAppendText(ByVal sText As String)
    Dim lPos            As Long
   
    If Left$(sText, 1) = Chr$(vbKeyClear) Then  '--- form feed
        m_oText.Text = vbNullString
        sText = Mid$(sText, 2)
    End If
    With m_oText
        HwndRedraw(.hWnd) = False
        .SelStart = m_lPos
        .SelLength = &H7FFF
        If .SelStart + Len(sText) > &H7FFF& Then
            sText = .Text & sText
            lPos = Len(sText) - &H7FFF& - 2
            If lPos < 1 Then
                lPos = 1
            End If
            .Text = Mid$(sText, InStr(lPos, sText, vbCrLf) + 2)
        Else
            .SelText = sText
        End If
        .SelStart = &H7FFF
        m_lPos = .SelStart
        .SelText = m_sInput
        HwndRedraw(.hWnd) = True
        .SelStart = &H7FFF
        .Refresh
    End With
End Sub

Private Sub pvAppendInput(ByVal sText As String, ByVal lIdx As Long)
    With m_oText
        HwndRedraw(.hWnd) = False
        .SelStart = m_lPos
        .SelLength = &H7FFF
        .SelText = sText
        HwndRedraw(.hWnd) = True
        .SelStart = m_lPos + lIdx
        .Refresh
    End With
End Sub

Private Sub pvReplaceSelection(lIdx As Long, ByVal lSize As Long, Optional sText As String)
    If lIdx < 0 Then
        lSize = lSize + lIdx
        lIdx = 0
    End If
    If lSize >= 0 Then
        m_sInput = Left$(m_sInput, lIdx) & sText & Mid$(m_sInput, lIdx + lSize + 1)
        lIdx = lIdx + Len(sText)
    End If
    m_sAutoComplete = vbNullString
End Sub

Private Function pvGetCurrentDir(ByVal hProcess As Long) As String
    Const ProcessBasicInformation          As Long = 0
    Const sizeof_PBI                        As Long = 6 * 4
    Const offsetof_ProcessParameters        As Long = &H10
    Const offsetof_CurrentDirectory        As Long = &H24
    Const sizeof_UNICODESTRING              As Long = 2 * 4
    Dim lPtr            As Long
    Dim aTemp(0 To 5)  As Long
    Dim sBuffer        As String
   
    If NtQueryInformationProcess(hProcess, ProcessBasicInformation, aTemp(0), sizeof_PBI, 0) < 0 Then
        GoTo QH
    End If
    If ReadProcessMemory(hProcess, aTemp(1) + offsetof_ProcessParameters, lPtr, 4, 0) = 0 Then
        GoTo QH
    End If
    If ReadProcessMemory(hProcess, lPtr + offsetof_CurrentDirectory, aTemp(0), sizeof_UNICODESTRING, 0) = 0 Then
        GoTo QH
    End If
    sBuffer = String$((aTemp(0) And &HFFFF&) \ 2, 0)
    If ReadProcessMemory(hProcess, aTemp(1), ByVal StrPtr(sBuffer), LenB(sBuffer), 0) = 0 Then
        GoTo QH
    End If
    pvGetCurrentDir = sBuffer
QH:
End Function

Private Function pvGetAutoComplete(ByVal sText As String, ByVal lIdx As Long) As String
    Dim lPos            As Long
    Dim sPath          As String
   
    If LenB(m_sAutoComplete) = 0 Then
        sText = Left$(sText, lIdx)
        lPos = InStrRev(sText, " """) + 2
        If lPos = 2 Then
            lPos = InStrRev(sText, " ") + 1
        End If
        sPath = Mid$(sText, lPos)
        If Mid$(sPath, 2, 1) <> ":" And Left$(sPath, 1) <> "\" Then
            sPath = PathCombine(pvGetCurrentDir(m_oExec.hProcess), sPath)
        End If
        m_sAutoComplete = Dir$(sPath & "*", vbDirectory Or vbArchive)
        Do While m_sAutoComplete = "." Or m_sAutoComplete = ".."
            m_sAutoComplete = Dir$
        Loop
        If LenB(m_sAutoComplete) <> 0 Then
            If InStrRev(sText, "\") > lPos Then
                lPos = InStrRev(sText, "\") + 1
            End If
            sText = Left$(sText, lPos - 1)
        End If
    Else
        lPos = Len(m_sAutoComplete)
        m_sAutoComplete = Dir$
        If LenB(m_sAutoComplete) <> 0 Then
            sText = Left$(sText, Len(sText) - lPos)
        End If
    End If
    pvGetAutoComplete = sText & m_sAutoComplete
End Function

Public Function TimerProc() As Long
    Const FUNC_NAME    As String = "TimerProc"
    Dim lPos            As Long
    Dim sEcho          As String
    Dim sText          As String
   
    On Error GoTo EH
    lPos = InStr(m_sInput, vbCrLf)
    Do While lPos > 0
        m_oExec.WriteInput Left$(m_sInput, lPos + 1)
        sEcho = m_oExec.ReadOutput(lPos + 1, TimeoutMs:=100)  '--- flush echoed input
        If sEcho <> Left$(m_sInput, lPos + 1) Then
            sText = sText & sEcho
        End If
        m_lPos = m_lPos + lPos + 2
        m_sInput = Mid$(m_sInput, lPos + 2)
        lPos = InStr(m_sInput, vbCrLf)
    Loop
    sText = sText & m_oExec.ReadPendingError & m_oExec.ReadPendingOutput
    If LenB(sText) <> 0 Then
        pvAppendText sText
    ElseIf m_oExec.AtEndOfOutput() Then
        Unload Me
    End If
    #If ImpleUseMST Then
        Set m_pTimer = InitFireOnceTimerThunk(Me, pvAddressOfTimerProc.TimerProc)
    #End If
    Exit Function
EH:
    PrintError FUNC_NAME
    Resume Next
End Function

'= shared ================================================================

Property Let HwndRedraw(ByVal hWnd As Long, ByVal bValue As Boolean)
    Const WM_SETREDRAW                  As Long = &HB
    If hWnd <> 0 Then
        Call DefWindowProc(hWnd, WM_SETREDRAW, -bValue, ByVal 0)
    End If
End Property

Private Function PathCombine(sPath As String, sFile As String) As String
    PathCombine = sPath & IIf(LenB(sPath) <> 0 And Right$(sPath, 1) <> "\" And LenB(sFile) <> 0, "\", vbNullString) & sFile
End Function

'=========================================================================
' Control events
'=========================================================================

Private Sub m_oText_KeyDown(KeyCode As Integer, Shift As Integer)
    Const FUNC_NAME    As String = "m_oText_KeyDown"
    Dim lIdx            As Long
    Dim lSize          As Long
   
    On Error GoTo EH
    lIdx = m_oText.SelStart - m_lPos
    lSize = m_oText.SelLength
    Select Case KeyCode + Shift * &H10000
    Case vbKeyC + vbCtrlMask * &H10000
        If lSize > 0 Then
            Clipboard.SetText m_oText.SelText
        End If
    Case vbKeyV + vbCtrlMask * &H10000, vbKeyInsert + vbShiftMask * &H10000
        If lIdx + lSize < 0 Then
            lIdx = Len(m_sInput)
        End If
        pvReplaceSelection lIdx, lSize, Clipboard.GetText
        pvAppendInput m_sInput, lIdx
    Case vbKeyDelete
        If lSize > 0 Then
            pvReplaceSelection lIdx, lSize
        Else
            If lIdx < 0 Then
                lIdx = 0
            End If
            pvReplaceSelection lIdx, 1
        End If
        pvAppendInput m_sInput, lIdx
    End Select
    Exit Sub
EH:
    PrintError FUNC_NAME
End Sub

Private Sub m_oText_KeyPress(KeyAscii As Integer)
    Const FUNC_NAME    As String = "m_oText_KeyPress"
    Dim lIdx            As Long
    Dim lSize          As Long
   
    On Error GoTo EH
    lIdx = m_oText.SelStart - m_lPos
    lSize = m_oText.SelLength
    If KeyAscii = vbKeyEscape Then
        m_sInput = vbNullString
        m_sAutoComplete = vbNullString
        lIdx = 0
    ElseIf KeyAscii = vbKeyReturn Then
        m_sInput = m_sInput & vbCrLf
        m_sAutoComplete = vbNullString
    ElseIf KeyAscii = vbKeyBack Then
        If lIdx + lSize < 0 Then
            lIdx = Len(m_sInput)
        ElseIf lSize > 0 Then
            pvReplaceSelection lIdx, lSize
        Else
            lIdx = lIdx - 1
            pvReplaceSelection lIdx, 1
        End If
    ElseIf KeyAscii = vbKeyTab Then
        If lIdx + lSize < 0 Then
            lIdx = Len(m_sInput)
        Else
            lIdx = lIdx + lSize
        End If
        m_sInput = pvGetAutoComplete(m_sInput, lIdx)
        lIdx = Len(m_sInput)
    ElseIf KeyAscii < 32 Or KeyAscii = 255 Then
        Exit Sub
    Else
        If lIdx + lSize < 0 Then
            lIdx = Len(m_sInput)
        End If
        If KeyAscii < 256 Then
            pvReplaceSelection lIdx, lSize, Chr$(KeyAscii)
        Else
            pvReplaceSelection lIdx, lSize, ChrW$(KeyAscii)
        End If
    End If
    pvAppendInput m_sInput, lIdx
    Exit Sub
EH:
    PrintError FUNC_NAME
End Sub

Private Sub Form_Load()
    Const FUNC_NAME    As String = "Form_Load"
    Dim sError          As String
   
    On Error GoTo EH
    If Not pvInit(Text1, New cExec, sError) Then
        MsgBox sError, vbCritical
        Unload Me
    End If
    Exit Sub
EH:
    PrintError FUNC_NAME
End Sub

Private Sub Form_Resize()
    Const FUNC_NAME    As String = "Form_Resize"
   
    On Error GoTo EH
    If WindowState <> vbMinimized Then
        m_oText.Move 0, 0, ScaleWidth, ScaleHeight
    End If
    Exit Sub
EH:
    PrintError FUNC_NAME
End Sub

Private Sub Timer1_Timer()
    TimerProc
End Sub

Hint: type exit to close console, cls to clear screen, use Tab key to auto-complete, Esc to clear input.



Here is the complete project zipped: EmbedConsole6.zip

cheers,
</wqw>

Viewing all articles
Browse latest Browse all 1463

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>