Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 

0. Введение.

   Как-то раз мне понадобилось написать на Visual Basic программу, взаимодействующую с процесcом DOS (т.е. с окном DOS'а в Windows) - которая могла бы читать из этого окна и писать в него. Естественная лень заставила меня в первую очередь обратится к Интернет ресурсам на эту тему. Однако максимум найденного как в наших, так и в англоязычных ресурсах - чтение вывода этого окна. С другой стороны, на форумах по программированию на VB этот вопрос поднимается достаточно часто, и обычно остается без ответа.

   В MSDN пример, конечно, есть, но он написан на C и перевод его на VB не банален. И учитывая некоторые национальные особенности у владельца VB не всегда есть MSDN ;), да и английский не все знают.

   Поэтому я решил попробовать побороть лень и написать такую программу сам. И в том числе наконец ответить на вопросы по этому поводу. Первая редакция этой статьи, точнее, то, что её прочитало более 700 человек, подтвердила мою правоту.

1. Каналы (Pipes)

   Основновной принцип реализации такого взаимодействия - переназначение стандартных потоков ввода и вывода (stdin и stdout) процесса DOS из его окна туда, где эти потоки будут доступны нашему приложению. Сделать это можно используя так называемые каналы. Канал (Pipe) - это обьект Windows, служащий для передачи информации между процессами. Для приложения канал характеризуется входом и выходом. Информация, одним процессом записанная во вход канала может быть затем считана другим с его выхода.

   Каналы разделяются на два типа: именованные (named) и анонимные.
   Имя для именованных каналов задаётся создающим приложением. Поэтому это имя всегда определено и известно и может использоваться для соединения приложений и обмена их информацией в сети. Данные в именованнных каналах хранятся в виде блоков (пакетов).
   Анонимные каналы соответствуют случаю, когда имя для канала задаётся Windows. Часть функций Windows API (далее просто API), рассчитанных на работу с именованными каналами работают и с анонимными как с их частным случаем. Однако работа по сети с анонимными каналами невозможна. Фактически, анонимные каналы являются просто общей для нескольких процессов областью памяти.

   В нашем случае мы будем использовать анонимные каналы - с ними намного проще работать - соединяя их одним концом с потоками ввода/вывода процесса DOS, а с другим взаимодействуя из нашего приложения.

2. Работа с каналами

Для работы с каналами в данном случае нам понадобятся несколько функции API, сперва
Declare Function CreatePipe Lib "kernel32" ( _
phReadPipe As Long, _  ' дескриптор выходного конца канала
phWritePipe As Long, _ ' дескриптор входного конца канала
lpPipeAttributes As SECURITY_ATTRIBUTES, _ ' атрибуты
ByVal nSize As Long _  ' размер буфера
) As Long
   Назначение этой функции понятно - создание нового (анонимного) канала. Мы передаём в неё две переменные, в которые она записывает дескрипторы (handles) начала и конца канала. Затем идёт параметр lpPipeAttributes, которая определяет аттрибуты создаваемого канала. Эти аттрибуты задаются структурой
Type SECURITY_ATTRIBUTES
    nLength As Long ' размер структуры
    lpSecurityDescriptor As Long ' дескриптор безопасности
    bInheritHandle As Long ' наследовать ли дескриптор
End Type
   В нашем случае устанавливаем bInheritHandle = 1& - наследование включено. Наследование дескрипторов означает, что дескрипторы текущего процесса будут действительны для создаваемых им (дочерних) процессов. В общем случае дескрипторы одного процесса не являются верными для другого и он должен копировать их для себя функцией DuplicateHandle. Поскольку процесс DOS не вызывает эту функцию, мы должны с самого начала передать ему верный дескриптор, то есть - использовать наследование. lpSecurityDescriptor = 0& (этот дескриптор связан с безопасностью в Windows NT/2K/XP и я с трудом представляю себе случай, когда он может понадобится). Length = Len(ваша_переменная).
   Наконец последний параметр, nSize - отвечает за размер создаваемого канала. Ставим ByVal 0& - размер канала по умолчанию. Кстати, & после имени параметра указывает на то, что передаваемая переменная имеет тип Long. Я не уверен в необходимости использования такого вида записи, но могу легко представить себе случаи, когда пренебрежение этим приведет к ошибке.

Следующая функция

Declare Function PeekNamedPipe Lib "kernel32" ( _
ByVal hNamedPipe As Long, _ ' дескриптор выходного конца канала
lpBuffer As Any, _ ' буффер
ByVal nBufferSize As Long, _ ' размер буфера
lpBytesRead As Long, _ ' сколько байт было прочитано
lpTotalBytesAvail As Long, _ ' сколько всего байт в канале
lpBytesLeftThisMessage As Long _ ' сколько байт осталось
) As Long
уже не очевидна. Она используется для того, чтобы определить, есть ли в канале новые данные, не считывая их. Попытка прочитать данные из канала в котором их нет вызывает серьёзное (на самом деле) торможение программы (это мистика, но проверено на опыте). Мы передаем функции дескриптор выходного конца нашего канала, затем передаём в качестве ссылки на буфер ByVal 0& (мы не считываем даннные, а только проверяем их наличие). Соответственно размер буфера = 0, а в качестве lpBytesRead, lpTotalBytesAvail и lpBytesLeftThisMessage - свои переменные, которые после выполнения функции будут заполнены соответствующими значениями.
   Если lpTotalBytesAvail > 0 - канал содержит непрочитанные данные.

   Наконец, для чтения из канала и записи в него применяются следующие функции (канал в некотором смысле является частным случаем файла)

Declare Function ReadFile Lib "kernel32" ( _
' откуда читать - ставим дескриптор выхода канала
ByVal hFile As Long, _
' переменная для прочитанных данных
ByVal lpBuffer As String, _ 
' сколько байт читать, обычно Len(наш_буфер)
ByVal nNumberOfBytesToRead As Long, _
' сколько байт прочиталось - устанавливается функцией
lpNumberOfBytesRead As Long, _
ByVal lpOverlapped As Any _ ' ByVal 0&
) As Long
Последний параметр используется для асинхронных операций. Нам он не понадобится.
Declare Function WriteFile Lib "kernel32" ( _
' куда писать - ставим дескриптор входа канала
ByVal hFile As Long, _
' что писать - наша строка
ByVal lpBuffer As String, _
' сколько байт писать: Len(lpBuffer)
ByVal nNumberOfBytesToWrite As Long, _
' сколько байт записалось - устанавливается функцией
lpNumberOfBytesWritten As Long, _
lpOverlapped As Any _ ' ByVal 0&
) As Long
Примечания:
  • Обязательно пишите ByVal перед 0& в параметре lpOverlapped ! Иначе функции (особенно WriteFile) будут выдавать ошибку API номер 6 - ERROR_INVALID_HANDLE (неверный дескриптор), и понять её причину будет очень непросто. Я потерял кучу времени на эту ошибку. (отдельное спасибо Сергею Карпушеву за помощь)
  • Буфер, передаваемый функции WriteFile - строка или байтовый массив. Visual Basic хранит свои строки в формате Unicode, в то время как функция требует строку в формате ANSI. Передать нашу строку можно двумя способами - написать в обьявлении функции ByVal lpBuffer As String вместо lpBuffer As Any (тогда VB сам выполнит преобразование - я сделал именно так). Или использовать массив байт, который заполняется примерно таким образом:
    Dim Buffer() As Byte
    
    Buffer = StrConv("Это тестовая строка", vbFromUnicode)
    
  • Буфер в функции ReadFile должен быть сперва инициализирован одним из следующих способов:
    Dim BufferStr As String * BufferLen
    Dim BufferStr As String: BufferStr = Space(BufferLen)
    
    Второй способ мне кажется более правильным.

3. Ошибки функций API

   Стоит сделать здесь краткое лирическое отступление и поговорить о возвращаемых значениях функций API. В случае неудачи возвращаемое функцией API значение обычно равно 0. В случае удачи это может быть либо это ожидаемое значение либо нечто неопредённое - в частности, 1. Никогда не стоит считать возвращаемое значение значением типа Boolean и писать что нибудь вроде If Not IsWindow. Тут очень хорошо подходит пример, приводимый Д. Апплеманом - IsWindow(hWnd) And Not IsWindow(hWnd) может в VB быть равно True.

   Единственное значение, которое можно ожидать - 0 как признак неудачи. Если значение равно 0, то номер ошибки можно получить через функцию API GetLastError.

Declare Function GetLastError Lib "kernel32" _
 Alias "GetLastError" () As Long
или, что проще, через Err.LastDllError.

4. Создание процессов

   Когда просто нужно запустить некий процесс, простейшим способом является использование встроенной функции Shell. Но в данном случае нам недостаточно её возможностей. Наибольшие возможности по настройке запуска процесса предоставляет функция API CreateProcess.
Declare Function CreateProcess Lib "kernel32" _
Alias "CreateProcessA" ( _
ByVal lpApplicationName As Any, _  ' имя приложения
ByVal lpCommandLine As String, _   ' командная строка
lpProcessAttributes As Any, _      ' атрибуты процесса
lpThreadAttributes As Any, _       ' атрибуты нити
ByVal bInheritHandles As Long, _   ' наследовать ли потоки
ByVal dwCreationFlags As Long, _   ' флаги 
ByVal lpEnvironment As Any, _      ' переменные окружения
ByVal lpCurrentDirectory As Any, _ ' рабочая директория
lpStartupInfo As Any, _	           ' параметры запуска	
lpProcessInformation As Any _	   ' информация о процессе
) As Long
   Первые два параметра кажутся очевидными, но и тут есть одна тонкость. Мы можем передавать имя файля как через lpApplicationName, так и через lpCommandLine (для этого lpApplicationName надо передать как ByVal 0&).
   Во втором случае не обязательно указывать полный путь к файлу. Но могут возникнуть некоторые проблемы, например еcли путь к файлу, содержит пробелы.Так как CreateProcess считает именем файла часть lpCommandLine до первого пробела, ошибка при значении lpCommandLine вроде "c:\program files\my program\myproc.exe /a /b /c" обеспечена. Для того, чтобы избежать таких ошибок, необходимо заключить путь к файлу в кавычки (внутри строки). В данном случае """c:\program files\my program\myproc.exe"" /a /b /c".
Если в имени файла не содержится пути, система ищет его в следующей последовательности:
  1. В каталоге, откуда запущено приложение
  2. В текущем каталоге приложения
  3. Windows 95/98/Me:
    В системном каталоге Windows
    Windows NT/2000/XP:
    В 32-х битном системном каталоге Windows
    В 16-ти битном системном каталоге Windows
  4. В каталоге Windows
  5. В каталогах, содержащихся в переменной окружения PATH
Третий и четвёртый параметры передают в функцию знакомую структуру SECURITY_ATTRIBUTES, lpProcessAttributes - для процесса, а lpThreadAttributes - для нити. Заполнение структуры в данном случае абсолютно идентично случаю с CreatePipe (я использовал одну и ту же переменную). Оба параметра идентичны, поскольку мы создаём однонитевый процесс (нити в рамках этой статьи я не буду обсуждать).

Следующие четыре параметры опять весьма просты:
bInheritHandles - ставим ByVal 1& - создаваемый процесс наследует все наследуемые дескрипторы текущего.
dwCreationFlags - комбинация (Const1 Or Const2) следующих констант

' процесс создаёт своё консольное окно
Public Const CREATE_NEW_CONSOLE = &H10
' процесс начинает новую группу
Public Const CREATE_NEW_PROCESS_GROUP = &H200
' консольное приложение не создаёт окна (только Windows NT/2000)
Public Const CREATE_NO_WINDOW = &H8000000
' процесс останавливается сразу после запуска
Public Const CREATE_SUSPENDED = &H4

' процесс имеет нормальный приоритет, обычный случай
Public Const NORMAL_PRIORITY_CLASS = &H20
' процесс имеет высокий приоритет
Public Const HIGH_PRIORITY_CLASS = &H80
' процесс имеет низкий приоритет
Public Const IDLE_PRIORITY_CLASS = &H40
lpEnvironment задаёт указатель на блок окружения.
Нам не понадобится - ByVal 0&.
lpCurrentDirectory - директория, которая становится текущей для нового процесса. В случае vbNullString (если обьявлена As String) или ByVal 0& (обьявлена As Long) - текущая директория родительского.

И, наконец два последних
lpStartupInfo - передаёт структуру STARTUPINFO с параметрами запуска процесса

 Type STARTUPINFO
    cb As Long ' размер структуры
    lpReserved As Long ' зарезервированно, 0
    ' рабочий стол процесса (только Windows 2000/XP)
    lpDesktop As Long
    lpTitle As Long ' заголовок процесса
    dwX As Long ' X координата первого окна процесса
    dwY As Long ' Y координата первого окна процесса
    dwXSize As Long ' ширина первого окна процесса
    dwYSize As Long ' высота первого окна процесса
    'размер окна консоли в ширину, символов (Windows 2000/XP)
    dwXCountChars As Long
    'размер окна консоли в высоту, символов (Windows 2000/XP)
    dwYCountChars As Long 
    dwFillAttribute As Long ' цвета консоли
    dwFlags As Long ' флаги
    ' режим отображения первого окна процесса
    wShowWindow As Integer
    cbReserved2 As Integer ' 0
    lpReserved2 As Long ' 0
    hStdInput As Long ' дескриптор потока ввода
    hStdOutput As Long ' дескриптор потока вывода
    hStdError As Long  ' дескриптор потока вывода ошибок
End Type
Нам из неё понадобятся только cb - размер структуры (устанавливается как cb=Len(наша_переменная)), wShowWindow, hStdInput, hStdOutput, hStdError и dwFlags.
wShowWindow установим в SW_HIDE
Private Const SW_HIDE = 0
так как нам нужно спрятать окно DOS (там всё равно ничего выводится не будет). hStdInput приравниваем выходному концу одного из наших каналов, hStdOutput - входному концу другого, hStdError - hStdOutput (поток stdError полностью эквивалентен потоку stdOutput во всём кроме передаваемых данных, и в случае процесса DOS лучше всего даже дать этим двум потокам один и тот же канал. Отдельно рассматривать stdError я его не буду).
dwFlags указывает функции CreateProcess, какие части структуры ей использовать. Поскольку мы переназначили потоки и отображение окна -ставим dwFlags = STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW.
Const STARTF_USESHOWWINDOW = &H1
Const STARTF_USESTDHANDLES = &H100
Последний параметр функции - lpProcessInformation заполняется функцией CreateProcess в случае удачи структурой PROCESS_INFORMATION следующего вида
Type PROCESS_INFORMATION
    hProcess As Long
    hThread As Long
    dwProcessId As Long
    dwThreadId As Long
End Type
Наиболее частая ошибка CreateProcess - ERROR_FILE_NOT_FOUND. ;)
Const ERROR_FILE_NOT_FOUND = 2
ю

5. Очистка

Так как наше приложение создает невидимый процесс, мы должны выгрузить его при завершении приложения. Это делается с помощью функции TerminateProcess
Declare Function TerminateProcess Lib "kernel32" _
 (ByVal hProcess As Long, ByVal uExitCode As Long) As Long
которой передаётся дескриптор hProcess структуры PROCESS_INFORMATION, полученной при его запуске. uExitCode стоит установить в 0 - нормальное завершение.

Кроме того при завершении приложения, работавшего с Windows API, желательно очистить все лишние указатели на обьекты Windows, чтобы позволить системе выгрузить неиспользуемые объекты. В теории, конечно, Windows выгружает объекты, используемые приложением, при его завершении. Но практика часто расходится с теорией, особенно для неNT. По этому хорошим стилем считается выгружать обьекты API вручную. Делается это с помощью функции CloseHandle

Declare Function CloseHandle Lib "kernel32" _
(ByVal hObject As Long) As Long
которой передаётся соответствующий дескриптор.

6. Завершение

Учитывая всё написанное выше, реализация взаимодействия становится чисто технической задачей. В качестве примера я приведу свой обьектный модуль, но очень не советую просто копировать его. При этом вы ничему не научитесь, да и мне было бы легче просто положить его в примеры. Попробуйте написать нечто подобное сами - это на самом деле крайне просто. На самом деле никогда не стоит использовать чужой код без его понимания. Я ведь тоже не застрахован от ошибок.
Кстати, в модуле можно найти ещё пару дополнительных возможностей, не упомянутых мной в статье.
Итак:
Option Explicit

Private Type SECURITY_ATTRIBUTES
    nLength As Long
    lpSecurityDescriptor As Long
    bInheritHandle As Long
End Type

Private Type STARTUPINFO
    cb As Long
    lpReserved As Long
    lpDesktop As Long
    lpTitle As Long
    dwX As Long
    dwY As Long
    dwXSize As Long
    dwYSize As Long
    dwXCountChars As Long
    dwYCountChars As Long
    dwFillAttribute As Long
    dwFlags As Long
    wShowWindow As Integer
    cbReserved2 As Integer
    lpReserved2 As Long
    hStdInput As Long
    hStdOutput As Long
    hStdError As Long
End Type

Private Type PROCESS_INFORMATION
    hProcess As Long
    hThread As Long
    dwProcessId As Long
    dwThreadId As Long
End Type

Private Type PIPE
    hReadPipe As Long
    hWritePipe As Long
End Type

Private Const INFINITE = -1&

Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const CREATE_NEW_CONSOLE = &H10

Private Const STARTF_USESHOWWINDOW = &H1
Private Const STARTF_USESIZE = &H2
Private Const STARTF_USEPOSITION = &H4
Private Const STARTF_USECOUNTCHARS = &H8
Private Const STARTF_USEFILLATTRIBUTE = &H10
Private Const STARTF_RUNFULLSCREEN = &H20
Private Const STARTF_FORCEONFEEDBACK = &H40
Private Const STARTF_FORCEOFFFEEDBACK = &H80
Private Const STARTF_USESTDHANDLES = &H100

Private Const SW_HIDE = 0
Private Const SW_SHOWNORMAL = 1
Private Const SW_NORMAL = 1
Private Const SW_SHOWMINIMIZED = 2
Private Const SW_SHOWMAXIMIZED = 3
Private Const SW_MAXIMIZE = 3
Private Const SW_SHOWNOACTIVATE = 4
Private Const SW_SHOW = 5
Private Const SW_MINIMIZE = 6
Private Const SW_SHOWMINNOACTIVE = 7
Private Const SW_SHOWNA = 8
Private Const SW_RESTORE = 9
Private Const SW_SHOWDEFAULT = 10
Private Const SW_MAX = 10

Private Const INVALID_HANDLE_VALUE = -1

Private Const STILL_ACTIVE = &H103&

Private Declare Function CreatePipe Lib "kernel32" _
 (phReadPipe As Long, phWritePipe As Long, lpPipeAttributes _
 As SECURITY_ATTRIBUTES, ByVal nSize As Long) As Long

Private Declare Function ReadFile Lib "kernel32" _
 (ByVal hFile As Long, ByVal lpBuffer As String, _
 ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead _
 As Long, ByVal lpOverlapped As Any) As Long
Private Declare Function WriteFile Lib "kernel32" _
 (ByVal hFile As Long, ByVal lpBuffer As String, _
 ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten _
 As Long, lpOverlapped As Any) As Long

Private Declare Function CreateProcess Lib "kernel32" _
 Alias "CreateProcessA" (ByVal lpApplicationName As Long, _
 ByVal lpCommandLine As String, lpProcessAttributes As Any, _
 lpThreadAttributes As Any, ByVal bInheritHandles As _
 Long, ByVal dwCreationFlags As Long, ByVal lpEnvironment _
 As Long, ByVal lpCurrentDirectory As Long, lpStartupInfo _
 As Any, lpProcessInformation As Any) As Long

Private Declare Function GetExitCodeProcess Lib _
 "kernel32" (ByVal hProcess As Long, lpExitCode _
 As Long) As Long
Private Declare Function TerminateProcess Lib _
 "kernel32" (ByVal hProcess As Long, ByVal uExitCode _
 As Long) As Long

Private Declare Function GetStdHandle Lib "kernel32" _
 (ByVal nStdHandle As Long) As Long
Private Declare Function SetStdHandle Lib "kernel32" _
 (ByVal nStdHandle As Long, ByVal nHandle As Long) As Long

Private Declare Function CloseHandle Lib "kernel32" _
 (ByVal hObject As Long) As Long

Private Declare Function PeekNamedPipe Lib "kernel32" _
 (ByVal hNamedPipe As Long, lpBuffer As Any, ByVal _
 nBufferSize As Long, lpBytesRead As Long, _
 lpTotalBytesAvail As Long, lpBytesLeftThisMessage _
 As Long) As Long

Private pipeOut As PIPE, pipeIn As PIPE

Private Process As PROCESS_INFORMATION

Private mvarCommandLine As String
Private mvarRunning As Boolean

Public Sub Terminate()
    TerminateProcess Process.hProcess, 0
    CloseHandle Process.hProcess
    CloseHandle Process.hThread
    CloseHandle pipeIn.hReadPipe
    CloseHandle pipeIn.hWritePipe
    CloseHandle pipeOut.hReadPipe
    CloseHandle pipeOut.hWritePipe
    
    mvarRunning = False
End Sub

Public Function Read(Optional ByVal Bytes As Long = -1)
                                                As String
    Dim tBytesR As Long, Buffer As String
    Dim tBytesA As Long, tMsg As Long
    Dim I As Long, Result As Long
    Dim ReturnStr As String
    
    If Not mvarRunning Then Exit Function
   
    Result = PeekNamedPipe(pipeErr.hReadPipe, ByVal 0&, 0, _
             tBytesR, tBytesA, tMsg)

    If Result <> 0 And tBytesA > 0 Then
        Buffer = String(tBytesA, " ")
        Result = ReadFile(pipeOut.hReadPipe, Buffer, _
                  IIf(Bytes = -1, Len(Buffer), _
            Bytes), tBytesR, ByVal 0&)
        If Result = 0 Then _
            Err.Raise vbObjectError + 504, "DOSShell Class", _
            "Error: ReadFile failed. " & Err.LastDllError
        ReturnStr = Left(Buffer, tBytesR)
        Read = DOSDecode(ReturnStr)
    End If
End Function

Public Function Write(ByVal Data As String) As Long
    Dim tBytesW As Long
    Dim I As Long, Result As Long

    If Not Right(Data, 2) = Chr(13) & Chr(10) Then _
             Data = Data & Chr(13) & Chr(10)

    Result = WriteFile(pipeIn.hWritePipe, Data, _
              Len(Data), tBytesW, ByVal 0&)
    If Result = 0 Then _
        Err.Raise 503, "DOSWrite", "Error: WriteFile failed. " _
                       & Err.LastDllError
    Result = FlushFileBuffers(pipe.hWritePipe)
    If Result = 0 Then _
        Err.Raise vbObjectError + 507, "DOSShell Class", _
         "Error: FlushFileBuffers failed. " & Err.LastDllError
    
    WriteIn = Len(Data) - 1
End Function

Public Function Execute(Optional ByVal CommandLine As _
           String = "") As Long
    Dim Result As Long
    Dim StartInfo As STARTUPINFO
    Dim Attribs As SECURITY_ATTRIBUTES
    Dim tIn As Long, tOut As Long
    
    On Error GoTo ErrHandler
    
    If CommandLine <> "" Then mvarCommandLine = CommandLine
    
    Attribs.nLength = Len(Attribs)
    Attribs.bInheritHandle = 1;
    Attribs.lpSecurityDescriptor = 0&
    
    Result = CreatePipe(pipeIn.hReadPipe, pipeIn.hWritePipe, _
 Attribs, ByVal 0&)
    If Result = 0 Then _
        Err.Raise vbObjectError + 501, "DOSShell Class", _
 "Error: CreatePipe failed. " & Err.LastDllError
    
    Result = CreatePipe(pipeOut.hReadPipe, pipeOut.hWritePipe, _
 Attribs, ByVal 0&)
    If Result = 0 Then _
        Err.Raise vbObjectError + 501, "DOSShell Class", _
 "Error: CreatePipe failed. " & Err.LastDllError
        
    StartInfo.cb = Len(StartInfo)
    StartInfo.hStdInput = pipeIn.hReadPipe
    StartInfo.hStdOutput = pipeOut.hWritePipe
    StartInfo.hStdError = pipeOut.hWritePipe
    StartInfo.dwFlags = STARTF_USESTDHANDLES + _
                              STARTF_USESHOWWINDOW
    StartInfo.wShowWindow = SW_HIDE

    Result = CreateProcess(0&, mvarCommandLine, Attribs, _
 Attribs, ByVal 1&, CREATE_NEW_CONSOLE, ByVal 0&, ByVal _
 0&, StartInfo, Process)
 
    If Result = 0 Then _
        Err.Raise vbObjectError + 502, "DOSShell Class", _
 "Error: CreateProcess failed. " & Err.LastDllError
    
    Execute = 1
    mvarRunning = True
    Exit Function

ErrHandler:
    Execute = Err.Number
    
End Function

Public Property Get Running() As Boolean
    Dim ExitCode As Long
    If Not mvarRunning Then
        Running = False
    Else
        GetExitCodeProcess Process.hProcess, ExitCode
        Running = (ExitCode = STILL_ACTIVE)
    End If
End Property

Public Property Let CommandLine(ByVal vData As String)
    mvarCommandLine = vData
End Property

Public Property Get CommandLine() As String
    CommandLine = mvarCommandLine
End Property

Private Function DOSDecode(ByVal Str As String) As String
    Dim I As Long

    For I = 239 To 192 Step -1
        Str = Replace(Str, Chr(I), Chr(I + 16))
    Next I

    For I = 191 To 128 Step -1
        Str = Replace(Str, Chr(I), Chr(I + 64))
    Next I
    Str = Replace(Str, Chr(0), "")

    DOSDecode = Str
End Function
Спасибо за внимание и успехов в изучении VB !

Хочу поблагодарить Сергея Карпушева за предоставленный PocketPC - на котором эта статья и была написана и Павла Чернорука за некоторые стоящие идеи.

Отзывы и комментарии можете слать мне на e-mail. Доп. литература для интересующихся

  1. Дан Апплеман "Win32Api и Visual Basic", издательства Питер
    Отличная книга - must have для любого VB программиста.
  2. MSDN - без комментариев.

Андрей Щёкин [darXeth]
darxeth@hotbox.ru

 
     

   
   
     
  VBNet рекомендует