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".
Если в имени файла не содержится пути, система ищет его в следующей последовательности:
- В каталоге, откуда запущено приложение
- В текущем каталоге приложения
- Windows 95/98/Me:
В системном каталоге Windows
Windows NT/2000/XP:
В 32-х битном системном каталоге Windows
В 16-ти битном системном каталоге Windows
- В каталоге Windows
- В каталогах, содержащихся в переменной окружения 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.
Доп. литература для интересующихся
- Дан Апплеман "Win32Api и Visual Basic", издательства Питер
Отличная книга - must have для любого VB программиста.
- MSDN - без комментариев.
Андрей Щёкин [darXeth]
darxeth@hotbox.ru