Logo SNC     

Visual Basic - это круто!

 
О себе
 Словами
 Фото!
 
 Программирование 
Visual Basic
Download
 Trial Ware?
 
Plus
 Справка по RegMon
 Справка по FileMon
 
 
 
 
 

Яндекс цитирования

Рейтинг@Mail.ru
    

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

  1. Скромность украшает: левая установка VB6
  2. Считываем дату компиляции программы
  3. Позиционируем Audio CD
  4. Небольшой трюк в SaveSetting и GetSetting
  5. Используем свойства контролов по полной
  6. VB и ASM не совместимы? Да брехня!
  7. Очень секретная:-) функция InStrB
  8. Регулярные выражения в VB

Две версии Basic на одном компьютере

  Вообще-то я работаю с древним VB5. Но иногда мне нужно скомпилировать какую-нибудь программулину в VB6. Что, установить еще и его?! Плавали, знаем: он при установке столько всего напихает, так под себя все подомнет... Пробовал запустить прямо с CD. Так он мне говорит, что у меня Working Model Edition и не дает ничего серьезного делать. Поднатужился я, подключил магию в виде монитора доступа к реестру (RegMon) и заметил, что VB сразу после запуска какие-то 3 Licenses в недрах реестра выиcкивает и не находит. Заглянул в сценарий установки (файл VS98ENT.STF на CD) и нашел там эти ключи в таком вот виде:

Записал я их уже в виде REG-файлов типа:

Запускал их по очереди и проверял после как работает VB6. О, чудо! После первого - у нас уже Learning Edition, после второго - Professional Edition, после третьего - Enterprise Edition. Финита. Не, ще не финита. Чтобы CDROM не мучать, скопировал на жесткий диск всего 11 файлов общим размером 6,5 Мб. Если интересно, вот весь список: C2.EXE, CVPACK.EXE, LINK.EXE, MSPDB60.DLL, VB6.EXE, VB6.OLB, VB6DEBUG.DLL, VB6EXT.OLB, VB6IDE.DLL, VBA6.DLL, VBAEXE6.LIB. Если еще чего захочет - дайте. У меня он ничего не просит, работает. Кто умеет работать с ресурсами, может поменять ресурс 106__196 из VB6.exe на такой же из VB5.exe, тогда в диалоге о программе появятся Ваши данные. А еще там есть ресурс 256 - это стандартные иконки, если кому поднадоели - можно попробовать поменять.

Когда же я этот EXE-шник компилировал-то?

  Каждый раз, когда я в программе делал какой нибудь AboutBox, очень хотелось добавить в него информацию о времени компиляции. Подставить сюда дату файла просто нелепо: да мало ли какая у него будет дата после всякого там копирования, архивирования-разархивирования, сжатия EXE упаковщиками и т.п. :(. Нетушки, такой вариант не годится. Однажды случайно наткнулся на один документ, описывающий структуру заголовка PE файла. Эврика!!! Там есть 4 байта, в которые компилятор записывает дату в формате UTC (т.е количество секунд прошедших с 1.1.1970 00:00:00). Тут мне наконец-то пригодилась функция DateAdd. Получился вот такой код:

Private Sub ShowDateOfCompilation()
Dim d, TimeStamp As Long, MZ%, PE&, Pos&
'Открываем себя только для чтения
Open App.EXEName & ".exe" For Binary Access Read As #1
'Проверка валидности DOS Header
'Get #1, , MZ
'If MZ <> 23117 Then
'MsgBox "It's not valid executable file!"
'Close #1 : Exit Sub
'End if
'Определяем позицию IMAGE_NT_SIGNATURE

Get #1, 61, Pos
'Проверка валидности IMAGE_NT_SIGNATURE
'Get #1, Pos + 1, PE
'If PE <> 17744 Then
'MsgBox "It's not valid PE executable!"
'Close #1 : Exit Sub
'End If

Pos = Pos + 9 ' Позиция для даты
'Считываем TimeDateStamp

Get #1, Pos, TimeStamp
d = DateAdd("s", TimeStamp, "1/1/1970 00:00:00")
Close #1
MsgBox "Compiled at " & d & " (GMT)"
End Sub

  Такой подход можно использовать для определения даты компиляции любого исполнимого файла в формате Win32 PE (закомментированные блоки можно использовать для определения соответствия файла этому формату), но, как всегда, есть два НО:
Предупреждение 1: дата возвращается для GMT=0, т.е. без учета временного пояса и сдвига на летнее время. Лично мне это даже нравится: как то ближе становишься к глобальным процессам :) . Если Вас это не устраивает, дорабатывайте код сами.
Warning 2: some linkers tend to set this timestamp to absurd values which are not the time of linking in time_t format as described.
(По-нашенски: нет никакой гарантии, что конкретный компилятор положит правильные данные в указанную область заголовка.) Спокойcтвие: с компилятором VB вроде все нормально.



Цветной :) CD плейер


  Если при применении MMControl для работы с Audio CD Вам понадобится быстро перейти в нужную позицию диска, почему бы Вам не воспользоваться функцией RGB :) для этого. Примерный вариант кода:

With MMControl1
.DeviceType = "cdaudio" 'Только раз
.Command = "open" 'Только раз
.TimeFormat = mciFormatTmsf 'Обязательно, но если нигде не менять
'по коду, достаточно только один раз при открытии CD player'а

.Command = "pause" 'Не всегда нужно, но желательно
.To = RGB(3, 2, 23) '3 дорожка 2 минута 23 секунда (поставьте свои)
.Wait = True 'Не брезгуйте, а то с некоторыми приводами
'(например Mitsumi) были глюки!

.Command = "seek"
.Command = "play"
End With



Некоторая реабилитация SaveSettings, GetSettings


  Встроенные команды для работы с реестром весьма просты в употреблении, но не устраивают многих по причине той же простоты :) . К примеру документация ничего не говорит о возможности создания более глубокой чем AppName > Section > Key структуры вложенности подключей. Но это легко реализуется добавлением в первый или во второй параметр символа \ в качестве разделителя между подключами. Попробуйте выполнить такой код:

Private Sub Form_Load()
Dim AppVersionKey$
AppVersionKey = "MyProgramm" & "\Version " & App.Major & "." & App.Minor
Call SaveSetting(AppVersionKey, "Forms\frmMain", "Width", 1100)
Call SaveSetting(AppVersionKey, "Forms\frmAbout", "Width", 500)
MsgBox GetSetting(AppVersionKey, "Forms\frmMain", "Width"), , _
  "Main form width"
MsgBox GetSetting(AppVersionKey, "Forms\frmAbout", "Width"), , _
  "About form width"
Unload Me
End Sub


  Теперь загляните в реестр с помощью какого-либо редактора и увидите гораздо более сложную структуру подключей. Если у Вас очень много параметров хранится в реестре, то можно их разместить более структурировано (во сказанул!), не сбивая все в одну кучу или, как в примере выше, создавать отдельные наборы параметров для разных версий вашей программы (такое практикуется многими программистами).

  Кроме того, любые попытки записать или прочесть значение по умолчанию передачей пустой строки ("") в качестве параметра Key обламываются с ошибкой №5. Неужто не обойдем? А то :) Да просто нужно подставлять туда или vbNullChar или Chr$(0), что, собственно, одно и то же. Например:

'Запись значения по умолчанию
Call SaveSetting("CompanyName", "SoftwareName", vbNullChar, "Ver.1.5.7")
...
'Чтение значения по умолчанию
strVer = GetSetting("CompanyName", "SoftwareName", vbNullChar)

  Думаю, что Вы и сами уже догадались, но на всякий случай все-таки скажу: если Вы замените на vbNullChar еще и первый параметр этих 2-х методов, тогда Вы, естественно, сможете записывать и считывать значения Реестра непосредственно в разделе HKCU\Software\VB and VBA Program Settings. Ну, если это Вам, конечно, на какой-нибудь ляд нужно :)

Используй то, что под рукой...

  Каждый VB контрол имеет большое число зачастую неиспользуемых свойств. У меня всегда был соблазн их применения в качестве переменных, констант и даже объектов внутри программ. Простой пример: при необходимости хранить графику, которая будет в процессе динамически выводиться на форму (меняющуюся иконку окна программы, указатель мыши и др.) имейте в виду, что хранилище для них обычно уже в избытке есть на форме. Простая кнопка (CommandButton) имеет 3 свойства типа Picture, в которые можно загрузить любую графику программно или на этапе конструирования. Если Вы не используете кнопку со стилем 1-Graphical, эти картинки нигде не отобразятся. Это избавит Вас от добавления на форму объектов типа Image, в которые обычно загоняют такую графику когда нежелательно использовать ImageList. Дополнительный плюс, что графику можно привязать к конкретному управляющему элементу, если это нужно по коду, например для отображения особого указателя курсора мыши при перетаскивании над этим элементом. Или например такой код:

Private Sub cmdStates_Click(Index As Integer)
Set Me.Picture = cmdStates(Index).Picture
Set Me.Icon = cmdStates(Index).DownPicture
Set picPresident.Picture = cmdStates(Index).DisabledPicture
Me.Caption = "Государство " & cmdStates(Index).Caption & _
  IIf(cmdStates(Index).UseMaskColor, _
  " (член НАТО)", " (не входит в НАТО)")
End Sub

  Здесь в каждую кнопку массива cmdStates для свойства Picture загружена карта государства, а для DownPicture - иконка с изображением флага, а для DisabledPicture - фотография руководителя соответствующего государства, в UseMaskColor - True если государство имеет членство в НАТО. Число кнопок, естественно, может быть почти любым и при этом никаких лишних элементов управления для хранения картинок и т.д.
  Другой случай: использование, например, свойства WhatsThisHelpID CheckBox'a для хранения константы, допустим значения по умолчанию для какой-то настройки. Это упрощает, по моему, обработку большого массива таких элементов. Что бы не быть голословным вот кусочек кода, который имеет совсем небольшой размер:

Dim i As Long
Private Sub cmdDefault_Click()
For i = Settings.LBound To Settings.UBound
  Settings(i).Value = Settings(i).WhatsThisHelpID
Next
End Sub

Private Sub Form_Load()
For i = Settings.LBound To Settings.UBound
  Settings(i).Value = GetSetting("Myprog", "Settings", _
  Settings(i).Tag, Settings(i).WhatsThisHelpID)
Next
End Sub

Private Sub Form_Unload(Cancel As Integer)
For i = Settings.LBound To Settings.UBound
  Call SaveSetting("Myprog", "Settings", _
  Settings(i).Tag, Settings(i).Value)
Next
End Sub

  Для работы этого кода при создании массива Settings в таг каждого чекбокса записываем соответствующее имя параметра в реестре, а в свойство WhatsThisHelpID (Вы его когда-нибудь используете? ежели да, то замените его, например, на MaskColor :)) запишите значение по умолчанию для данной настройки. Кнопка CmdDefault предназначена для сброса всех настроек в значение по умолчанию.

Ускоряем VB программу с помощью ASM-а


   Да, не очень быстры программы на Бейсике и тут даже оптимизация кода и активное использование Win32API не очень порой помогают. Жаль, что нельзя, как например это делается в Дельфи, внедрять ассемблер непосредственно в код вашей программы. Но, как выясняется, есть способ использовать ASM-код с VB уже в готовом откомпилированном виде (в машинных кодах). Суть этой методы такова: откомпилированный ASM-код Вы или сохраняется в ресурсе программы, или записывается как константу в виде строки шестнадцатеричных символов. После запуска программы этот код загружается в память (во втором случае нужно еще и пребразовать строку с кодом в байтовый массив). Теперь с помощью API-функции CallWindowProc этот код приводится в действие. Мудрено? Но эффект просто поразительный! Некоторые вещи можно ускорить таким методом в сотни раз.
   А чтобы Вы могли это прочувствовать, предлагаю Вам изучить и опробовать три демо-программы в исходниках, которые мне повезло найти в сети.
  Первая программа самая простая из троицы. Это удобный 64-разрядный Hex/Dec/Bin конвертер. Именно в этой программе показан метод загрузки машинного кода из пользовательского ресурса. Вторая демонстрирует, как внедрение ASM (длина машинного кода всего 94 байта(!)) ускоряет операцию шифрования файлов функцией XOR в сотни раз по сранению с чистым VB. А третья вообще вызывает одни восторги. Огромное количество графических эффектов и преобразований она производит так быстро, как, мне кажется, не всякая настоящая рисовалка может это делать. Кроме того в программе есть несложные (уже не на ассемблере, а на WinAPI) реализации диалоговых окон отрытия и сохранения файлов, а также диалога выбора цвета. Есть также функция отмены и вариант работы с палитрами. В общем смотрите и наслаждайтесь :)

Функция InStrB не только для поиска текста


  Обычную InStr Вы, конечно же, использовали не раз. Да это и понятно: функция просто необходимая. Но в VB есть еще и ее модификация: InStrB. Описание: "InStrB используется с бинарными данными, помещенными внутрь строковой переменной. Вместо того, чтобы возвратить позицию первого символа вхождения одной строки в другую, InStrB возвращает позицию первого байта". Попытка найти в документации по VB и даже в MSDN хоть какие-либо примеры использования этой функции, скорее всего приведут Вас к мысли, что функция добавлена просто так, "для галочки". Так и я думал долгое время. Но когда докатился:) до необходимости оптимизации работы своих программ, функция неожиданно мне пригодилась. Приведу наглядный, как мне кажется, пример: поиск в нетекстовых файлах некоторой определенной последовательности байтов (например, некоторой сигнатуры). Обычный способ проделать это - прочесть содержимое файла в строку и, предварительно преобразовав разыскиваемый блок данных тоже в строку, использовать обычную функцию InStr. И это вполне работает. Недостатки: необходимость поместить содержимое файла в строке приводит к удвоению используемой для этого оперативной памяти (Вы помните, что VB хранит строки в формате Unicode, используя при этом 16, а не 8 бит на один символ?). Кроме этого, когда при чтении бинарных данных в строку VB производит их конвертирование, это, естественно, занимает процессорное время и, возможно, опять же память. Напрашивается простое решение: использовать не строку, а байтовый массив. Да нет проблем! Но как же тогда фукция InStrB, она ведь работает со строками? Преобразовывать массив в строку? Абсурд какой-то! Да нет, не понадобится это. Cообщаю об обнаруженной мной недокументированной (или просто ужасно плохо описанной) способности этой функции работать непосредственно с байтовыми массивами.
Касаемо нашего случая (поиск в файле) это можно изобразить так:
(ниже пример от фонаря, но с реальными величинами)

Private Sub cmdTestForEXIF_Click()

Dim Signature() As Byte
Dim Buffer() As Byte, Pos&
'Будем искать заголовок блока данных EXIF, применяемого
'в JPG-формате для цифровой фотографии: 0x45786966
'Для этого заполняем массив побайтово

ReDim Signature(3)
Signature(0) = &H45
Signature(1) = &H78
Signature(2) = &H69
Signature(3) = &H66
Open "c:\some_file.jpg" For Binary As #100
'Считываемый весь файл (возможно, в Вашем конкретном
'случае нужна будет только некоторая его часть)

ReDim Buffer(LOF(100) - 1)
Get #100, , Buffer
Close #100
Pos = InStrB(1, Buffer(), Signature(), vbBinaryCompare)
If Pos > 0 Then
  MsgBox "Блок EXIF обнаружен в позиции 0x" & Hex(Pos - 1)
Else
  MsgBox "JPG файл не сождержит EXIF блока!"
End If

End Sub

  Могу вам сказать, что во время тестов (поиск внутри бинарных файлов) скорость подобного кода в сравнении с кодом, использующим чисто строковый набор функций, увеличивалась до 2-х раз, что, согласитесь, немало для программы, активно такой поиск использующей.


Использование объекта Regexp в VB


  Несмотря на то, что внутренний набор функций для работы со строками в VB весьма широк, все равно в некоторых случаях разборки текста его Вам может оказаться недостаточно. Это касается, например, поиска определенного текста по сложной маске, проверки введенных пользователем данных на соответствие некоторому шаблону и др. Вот именно в такой ситуации Вас выручит использование Регулярных выражений, реализованных в объекте Regexp. Regexp не является внутренним объектом VB, но он давно уже реализован во всех последних версиях WSH. И грех будет этим не воспользоваться. Надо сказать, что если Вы раньше не работали с Регулярными выражениями, то MSDN-овская справка, их описывающая, может Вам показаться просто китайской (точнее Английской:-) грамотой. Кроме того, как выяснилось, она содержит откровенные ошибки и просто пропуски (посмотрите, например, что там есть про свойство Multiline). Пытался я найти русифицированный вариант этой доки, но ничего реального не нашел. Так, отдельные фрагменты только. Ну вот и решил я (блин, ну придет же такое в мою глупую башку!) сделать свой перевод. И сделал. Естественно, при переводе подправил там все, что смог. В результате получилась компактная, но при этом IMHO полноценная справка по Регулярным выражениям, причем это не только описание всех методов и свойств объекта RegExp с примерами их использования, но плюс еще и вводные статьи, достаточно подробно объясняющие общие идеи применения Регулярных выражений. Я не думаю, что все было сделано мною идеально (хотя, почему бы и нет :-)), посему принимаются любые замечания. Буду исправлять, чесслово.

   Перед проверкой примеров в VB IDE не забудьте в свойствах проекта сослаться на объект RegExp. Для этого в Project >> References подключите ссылку на "Microsoft VBScript Regular Expressions 5.5" (номер версии может быть и ниже если установлена более старая версия WSH). Вроде бы все просто, но вот у VB5 почему-то с этим есть какие-то проблемы, поэтому там будет проще отрыть в Блокноте файл проекта (.VBP) и в его начале добавить следующую строку:

Этого будет вполне достаточно.

  

  

Я, конечно, понимаю, что пока инфы не густо, но начало положено и продолжение следует. А, главное, что ссылка на "Visual Basic" теперь работает :-)


    
Copyright © 2002-2006, SNC- О компании...