Как же выключить компьютер в Windows XP ?

Я долго искал способ выключить компьютер в Windows XP:

Например: функция ExitWindows() вообще сдохла, а ExitWindowsEx() делает только LOGOFF.

И однажды нашёл на одном форуме:

Dim strComputer As String
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate, _
(Shutdown)}!\\" & strComputer & "\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery( _
 "Select * from Win32_OperatingSystem")
For Each ObjOperatingSystem In colOperatingSystems
   ObjOperatingSystem.Reboot ' Для перезагрузки
Next

и:
Dim strComputer As String

strComputer = "."

Set objWMIService = GetObject("winmgmts:" & _ 
"{impersonationLevel=impersonate,(Shutdown)}!\\" & strComputer & "\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery( _ 
"Select * from Win32_OperatingSystem")

For Each ObjOperatingSystem In colOperatingSystems

ObjOperatingSystem.ShutDown 'Для выключения

Next

Примечание: Данный код работает и для VBScript:

P.S. Пробуйте, у меня работает безотказно.

Добавлено: 22 Сентября 2013 12:47:13 Добавил: Андрей Ковальчук

Пишем анализатор текстовой информации

Меня просят как можно более понятнее объяснить метод написания анализатора текста, как-то: проектов VB. Как могу просто - объясняю. Например вам нужно определить какие объекты(компоненты), включены в данный проект. Для этого можно написать следующую программу:

Создаем Standard EXE, форму и на нее лист-бокс. Теперь напишем код:

Выполним основную функцию в процедуре:

'Для работы процедуре нужно передать имя файла
Private Sub FindOCX(FileName) 

'Открываем файл
Open FileName For Input As #1 

'Считываем все символы из файла
txt = Input$(LOF(1), 1) 

'Закрываем файл
Close #1 

'Цикл
Do 

'Ищем ; т.к. объекты в проекте идут после этого символа
p = InStr(p + 1, txt, ";") 

' Если не найдено, то заканчиваем поиск
If p  0 Then n = InStr(p, txt, Chr(13)) Else Exit Sub 

' Если не найдено символа перевода строки, то тоже выходим
If n = 0 Then Exit Sub 

'Получаем имя файла компонента
mOCX = Mid(txt, p + 2, n - p - 2) 

'Добавляем в список
List1.AddItem mOCX 
Loop 
End Sub 

Вот и все. Если вы хотите также определить ссылки References, то вместо ; поставьте \system32\, а вместо chr(13) поставьте #.

Надеюсь я вам помог.

Добавлено: 22 Сентября 2013 12:45:43 Добавил: Андрей Ковальчук

Особенности работы с управляющими структурами

Важной особенностью использования конструкций принятия решений является то, что с их помощью можно создавать так называемые вложенные структуры, когда одна конструкция располагается внутри другой. В качестве примера ниже приведен фрагмент программы, в котором в зависимости от результата заполняется один из двух массивов: любо таблица сложения, любо таблица умножения чисел от 1 до 10.

Option Explicit
Dim intI As Integer, intJ As Integer
Dim intX As Integer
Dim intAdd(1 To 10, 1 To 10) As Integer
Dim intMult(1 To 10, 1 To 10) As Integer

Private Sub Form_Load()
If intX = 0 Then
    For intI = 1 To 10
        For intJ = 1 To 10
        intAdd(intI, intJ) = intI + intJ
    Next intJ, intI
Else
    For intI = 1 To 5
        For intJ = 1 To 5
        intMult(intI, intJ) = intI * intJ
    Next intJ, intI
End If
End Sub

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

Следует обратить внимание на тот факт, что для окончания выполнения каждой пары вложенных циклов в этом случае использовалось одно ключевое слово Next с перечислением имен изменяемых в данный момент счетчиков. При этом первому по порядку указанному счетчику соответствует ближайшее служебное слово For. В том случае, если возникнет несовпадение имени счетчика с ключевым словом Next, компилятор выдает сообщение об ошибке.

Аналогичным образом действуют также вложенные конструкции принятия решений: в них первый по порядку End If применяется к последнему из имеющихся If, причем количество тех и других должно совпадать. Если же возникает преобладание какого-либо из этих ключевых слов, то на стадии компиляции возникает ошибка с выведением стандартного сообщения. К таким же последствиям приведет несоответствие количества служебных слов для начала и окончания различных разновидностей циклов.

Возможные системные сообщения при возникновении описанной ошибки компиляции:

Block If without End If - Отсутствие End If для блока If
Else without If - Использование Else без If
End If without block If - Отсутствие блока If для End If
For without Next - Использование For без Next
Next without For - Использование Next без For
Do without Loop - Использование Do без Loop
Loop without Do - Использование Loop без Do
В том случае, когда необходимо прервать выполнение цикла, применяются разновидности оператора Exit, который располагается в конструкции принятия решения. При этом для цикла For:Next используется Exit For, а для любого из циклов Do:Loop применяется Exit Do. После выполнения данного оператора происходит немедленное завершение цикла без выполнения, каких либо дополнительных операций.

Если аварийное завершение было использовано в цикле For:Next, то значение переменной-счетчика останется таким, каким оно было в момент выхода, в отличие от нормального завершения цикла, когда оно становится на единицу больше заданного конечного значения. Приведенная особенность зачастую может быть использована при решении различных задач.

Например:

Имеется массив из десяти числовых элементов. Необходимо определить среди них первое положительное значение и его индекс, после чего завершить дальнейшую проверку.
Option Explicit
Dim intArray(1 To 10) As Integer
Dim intI As Integer
Dim intResult As Integer

Private Sub Form_Load()
'Вывод элементов массива
For intI = 1 To 10
    If intArray(intI) > 0 Then
        intResult = intArray(intI)
Exit For
    End If
Next
'Теперь в переменной intI
'содержится значение индекса
'найденного элемента массива
End Sub

Добавлено: 22 Сентября 2013 02:18:35 Добавил: Андрей Ковальчук

Конструкции управления Visual Basic

Зачастую в определенном месте программы необходимо выполнять те или иные операторы, в зависимости от некоторых условий. Эта возможность в Visual Basic реализовать при помощи так называемых управляющих конструкций (или структур), которые в свою очередь состоят из структур принятий решений и циклов. Ниже приводится подробное описание двух существующих конструкций принятия решений.

Конструкция If:Then. Существует несколько разновидностей данной структуры. Если при выполнении какого-либо условия необходимо выполнять один оператор, то нужно использовать конструкцию следующего вида:

If условие Then оператор

В том случае, когда результатом проверки условия является значение True (истина), то выполняется оператор, находящийся после служебного слова Then. С другой стороны, если после проверки условия было получено значение False (ложь), то выполнить следующий по порядку оператор. Все описанные параметры данной структуры должны быть указаны в одной строке.

Если при выполнении условия требуется выполнить не один, а несколько операторов, то следует использовать такую конструкцию:
If условия Then
	Операторы
End If

В случае истинности проверяемого условия будут выполнены операторы, расположенные после ключевого слова Then. С другой стороны, если условие является ложным, то выполняется следующий после данной конструкции оператор. В том случае, когда в блоке операторы находится только один оператор, то данная структура все равно должна заканчиваться служебным словосочетанием End If.

При необходимости выполнении того или иного оператора (или блока операторов) в зависимости от результата проверки определенного условия, в языке Visual Basic следует использовать такую конструкцию:
If условия Then
	Операторы1
Else
	Операторы2
End If

Или
If условие Then
	Операторы1
Else: Операторы2
End If

Если результат проверки условия является значение True, то будет выполнен блок операторы1, находящийся после ключевого слова Then. С другой стороны, если проверка условия дала результат False, то будет выполнен блок операторы2, расположенный после служебного слова Else.

Во втором из приведенных вариантов в качестве блока операторы2 может использоваться как один оператор (тогда он записывается после знака ":" в той же строке, что и служебное слово Else), так и несколько (при этом каждый оператор, начинается со второго, записывается в отдельной строке).

В том случае, когда определенное действие (или набор действий) нужно выполнять после проверки не одного, а нескольких условий на языке Visual Basic следует использовать такую управляющую структуру:
 If условие Then
	Операторы1
ElseIf условие2 Then
	Операторы2
.
.
[Else 
ОператорыN]
End If

Если условие1, находящееся после ключевого слова If, истинно, то выполняется блок операторы1, расположенный после Then. Если же оно ложно, то осуществляется проверка условия2, находящегося после служебного слова ElseIf, в случае его истинности выполняется блок операторы2 и т.д. Если ни одно из условий не является истинным, то есть результатом всех проверок является значение False, то выполнятся блок операторыN, расположенный после ключевого слова Else (данный блок является необязательным).

В дополнение к приведенной выше структуре If:Then следует также рассмотреть функцию IIf, которая возвращает одно из двух значений, в зависимости от проверяемого условия.

Синтаксис данной функции имеет вид:
IIf(условие, значение1, значение2)

В том случае, когда результатом проверки условия является значение True, функция возвращает значение1, а когда проверка дает значение False, то возвращаемы результат - значение2. Например:
Dim intA As Integer, strA As String
intA = 6
strA = IIf(intA Mod 2=0, "Четное", "Не четное")

Если число intA делится на 2 без остатка, то строке strA будет присвоено значение "Четное", в противном случае - "Не четное".

Конструкция Select:Case. Когда существует несколько операторов (или блоков операторов), которые необходимо выполнять в случае истинности того или иного условия, то запись конструкции If:Then окажется достаточно громоздкой. Поэтому в подобных случаях следует использовать структуру Select:Case, которая улучшает читаемость программы. Ее конструкция выглядит следующим образом:
Select Case переменная
	Case значения1
		Операторы1
	Case значения2
		Операторы2
	.
	.
	.
	[Case Else
Операторы]
End Select

Если переменная содержит значение1, расположенное после первого по порядка ключевого слова Case, то выполняется блок операторы1. С другой стороны, если содержимое переменной равно значению2, то выполняется блок операторы2 и т.д. Когда содержимое переменной не равно ни одному из приведенных значений, то выполняется блок операторыN, находящийся после служебного словосочетания Case:Else, которое является необязательным в рассматриваемой конструкции.

В том случае, когда при нескольких значениях переменной необходимо выполнять один и тот же оператор (блок операторов), то список этих значений нужно указать после ключевого слова Case, разделяя их запятыми.

Например:
Select Case x
	Case 1
		x=x+1
	Case 2, 3, 4
		x=10
	Case Else
		x=20
End Select

Добавлено: 22 Сентября 2013 02:09:57 Добавил: Андрей Ковальчук

Разновидности циклов в Visual Basic

Кроме структур принятия решений, существуют еще одна разновидность управляющих конструкций, называемая циклом.

Цикл - это алгоритмическая структура, при помощи которой реализуется многократное повторение блоков операторов.

В языке Visual Basic существует три основных вида циклов, которые реализуется при помощи конструкций For:Next, Do:Loop и While:Wend.

Цикл For:Next. Используется в том случае, когда количество повторов заданного блока операторов известно заранее. Данная конструкция выглядит следующим образом:

For счетчик = начальное значение To конечное значение [Step шаг]
	Операторы1
[Exit For]
	Операторы2
Next [счетчик]

Когда приведенные операторы выполняются первый раз, то переменной счетчику присваивается начальное значение, после чего возможны два варианта действий. Если в результате проверки условия счетчик > конечное значение было получено значение True, то происходит завершение цикла, при этом блоки операторы1 и операторы2 ни разу не выполняются. С другой стороны, если результатом проверки условия является False, то в этом случае блоки операторов выполняются первый раз, после чего происходит переход на начало цикла. Далее значение переменной-счетчика увеличивается на шаг, расположенный после ключевого слова Step (в случае его отсутствия устанавливается шаг = 1). После этого снова проверяется истинность условия счетчик > конечное значение и т.д., окончание цикла происходит в тот момент, когда результатом данной проверки станет значение True.

Зачастую бывает необходимо "аварийно" завершать работу цикла при выполнении какого-либо дополнительного условия. В этом случае внутри цикла следует использовать служебное словосочетание Exit:For, которое обычно располагают в управляющей конструкции, например:
If условие Then Exit For

Если результатом проверки условия будет значение True, то выполнение цикла будет прекращено, причем блок операторы1 будет выполнен очередной раз, а блок операоры2 - нет.

Цикл Do:Loop. Применяется в том случае, когда число повторений операторов тела цикла заранее неизвестно. Существуют четыре разновидности данной конструкции. При использовании первых двух цикл либо выполнится много раз, либо не выполнится вообще.
Do Until условие
	Операторы
Loop

Если результатом проверки условия является значение False, то блок операторы выполняется, иначе осуществляется переход на оператор, расположенный после служебного слова Loop. С другой стороны, если первая проверка условия даст результат True, то цикл не выполнится ни разу.
Do While условие
	Операторы
Loop

Если условие истинно, то происходит выполнение блока операторы, если же ложно, то есть результатом проверки является False, то цикл не выполниться ни разу.

В случае использования последних двух конструкций цикл будет выполнен хотя бы один раз.
Do
	Операторы
Loop Until условие

Блок операторы выполняется до тех пор, пока результатом проверки условия является значение False, иначе выполнение цикла заканчивается.
Do 
	Операторы
Loop While условие

В случае ложности условия выполняется блок операторы, если же оно истинно, т.е. результатом проверки является значение True, то происходит окончание чикла.

Цикл While:Wend. Также используется в том случае, когда число повторений операторов тела цикла заранее неизвестно, и имеет следующий синтаксис:
While условие
	Операторы
Wend

Если результатом проверки условия является значение True, то блок операторы выполняется, иначе осуществляется переход на оператор, расположенный после служебного слова Wend. С другой стороны, если первая проверка условия даст результат False, то цикл не выполнится ни разу.

Добавлено: 22 Сентября 2013 02:08:13 Добавил: Андрей Ковальчук

MDI - Многодокументный интерфейс

Существуют два основных стиля интерфейсов пользователя: интерфейс с одним документом (single-document interface, SDI) и интерфейс со многими документами (multiple-document interface, MDI). Текстовый редактор с интерфейсом SDI позволяет открыть только один документ - чтобы открыть другой, следует закрыть предыдущий. В приложении MDI Вы можете открыть сразу несколько документов.

Контейнер для форм

В приложение Visual Basic можно добавить MDI форму, которая будет служить контейнером для подчиненных форм. Подчиненная форма - это обычная форма, у которой значение свойства MDIChild равно True. В режиме выполнения подчиненные окна помещаются внутри родительского окна MDI формы. Меню команд MDI формы может быть общим для всех подчиненных окон, или, если подчиненная форма имеет собственное меню команд, оно замещает меню формы контейнера. При закрытии формы-контейнера закрываются все подчиненные формы.

В приложении MDI можно открыть много окон построенных по одному образцу. При этом в режиме проектирования создается один экземпляр формы. Для того чтобы в режиме выполнения открыть много окон в коде программы создаются новые экземпляры подчиненной формы.

Dim NewDoc As New Form1
' Создать новый экземпляр формы
NewDoc.Show 
' Показать на экране

Поскольку много подчиненных форм имеют одинаковое имя и одинаковый код, то не следует в коде программы (если код используется несколькими экземплярами формы) использовать имя формы. В модуле формы для ссылок на текущую форму используйте ключевое слово Me.
Unload Me ' Выгрузить текущую форму

На форму MDI можно поместить управляющий элемент, если он имеет свойство Align (Выстроить) или является невидимым в режиме выполнения.

Создадим Текстовый редактор

Простой текстовый редактор имеет интерфейс MDI.

Создайте форму-контейнер командой Add => MDI Form, меню Project.

На форме Form1 создайте текстовое поле Text1 и установите для него свойство Multiline в True, Top и Left в 0.

Свойство MDIChild установите в True.

Создайте процедуру Form_Resize, чтобы размеры текстового окна изменялись при изменении размеров формы:
Text1.Height = Me.ScaleHeight
Text1.Width = Me.ScaleWidth

Создайте меню Файл для формы MDIForm1. В процедуру создания нового документа добавьте код:
Option Explicit

Private Sub mnuNew_Click()
Dim NewDoc As New Form1
' Создать новый экземпляр формы.
Static n As Integer
' Статическая переменная сохраняет значение после завершения работы процедуры
NewDoc.Show
' Показать на экране окно нового документа
n = n + 1
' Увеличиваем значение переменной на 1, при создании еще 1 экземпляра
MDIForm1.ActiveForm.Caption = Left(MDIForm1.ActiveForm.Caption, 4) + CStr(n)
End Sub

Код дочерней формы Form1:
Option Explicit

Private Sub Form_Resize()
Text1.Height = Me.ScaleHeight
Text1.Width = Me.ScaleWidth
' Необходимо использовать ключевое слово Me
' Так как последующий экземпляры класса будут работать не корректно
End Sub

Создайте новый пункт в меню Файл => Окна. Установите свойство WindowList - для отображения списка доступных окон, это свойство доступно лишь для MDI и подчиненных форм.

Меню в приложениях MDI

Обычно используется несколько наборов меню. Когда закрыты все документы, отображается меню MDI формы. Когда пользователь открывает документ, меню команд подчиненной формы замещает меню MDI формы. Панель инструментов располагается всегда в MDI форме.

Таким образом, некоторые команды могут быть в меню формы контейнера, в меню подчиненной формы и на панели инструментов. Чтобы не дублировать код, нужно создать открытую процедуру в подчиненной форме и вызывать ее из процедуры обработки событий.

Форма-контейнер может содержать несколько типов документов.

В меню Файл, MDI формы добавим пункт Новый Документ.

Меню команд подчиненной формы также содержит эту команд. Нужно написать процедуру создания нового документа и процедуры обработки для команд меню.

Код MDI формы:
Option Explicit
' Объявляем глобальную переменную для нумерации документов
Public n As Integer

Код подчиненной формы:
Option Explicit

Private Sub Form_Resize()
    Text1.Height = Me.ScaleHeight
    Text1.Width = Me.ScaleWidth
End Sub

Public Sub NewFile(m As Integer)
Dim NewDoc As New Form1
    NewDoc.Show
    m = m + 1
    MDIForm1.ActiveForm.Caption = Left(MDIForm1.ActiveForm.Caption, 4) + CStr(m)
End Sub

Private Sub mnuNew_Click()
    NewFile MDIForm1.n
End Sub

Загрузка и выгрузка форм MDI и подчиненных форм

При загрузке подчиненной формы автоматически загружается и форма MDI. Однако при загрузке формы MDI, подчиненные формы автоматически не загружаются. Это можно увидеть, установив в качестве стартового объекта MDI форму. Можно загрузить подчиненные формы как скрытые, установив значение свойства AutoShowChildren в True и отобразить их в нужный момент методом Show.

Выгрузка формы выполняется по команде Unload в коде программы или при закрытии окна.

При этом выполняются обработки событий QueryUnload и Unload для формы, в которых можно отследить сохранение введенных данных. При выгрузке MDI формы выгружаются и все подчиненные формы. Команда End вызывает немедленное завершение программы, при этом не выполняется процедуры обработки событий.

Проверка были ли сохранены данные

В приложении MDI отслеживается сохранение данных. Если есть не сохранные данные при завершении работы программы или при закрытии одного документа, то данные могут быть сохранены по умолчанию.

Для получения информации о состоянии документа в модуле подчиненной формы введем логическую переменную:
Public fs As Boolean

Переменная fs получает значение True при любых изменениях в текстовом поле.
Private Sub Text1_Change()
fs=True
End Sub

Пусть сохранение документа выполняется в процедуре SaveFile. Переменная fs получает значение False после сохранения документа.
Private Sub SaveFile()
:
fs=False
End Sub

Если при закрытии документа fs равно True, то функцию MsgBox выводит вопрос и возвращает ответ пользователя. Если ответ был Yes, то выполняется процедура SaveFile.
Private Sub Form_QueryUnload(Cancel As integer, UnloadMode As Integer)
If fs Then
	If MsgBox("Сохранить файл?", vbYesNo)=vbYes Then SaveFile
End If
End Sub

Добавлено: 22 Сентября 2013 01:56:15 Добавил: Андрей Ковальчук

Цикл For...Next или как работать с большим числом файлов

Данная статья рассчитана на начинающих программистов, постичь не постижимое, понять не понятное, еще раз убеждаюсь, что практика творит чудеса - так, что не бойтесь пробовать и спрашивать.

Так, как быть - нужно создать 200 "doc" или "txt" файлов, у всех цели могут быть разные, так что обсуждать это не вижу смысл. Расширение файла не имеет роли, так вы сами сможете изменить все под себя.

Любой начинающий программист сталкивается с проблемой повторения одних и тех же действий и в следствии увеличении строк кода и выполнения скучной рутинной работы. Давайте передадим всю эту работу компьютеру, уж он то был создан облегчать жизнь пользователям. Причем справиться во много раз быстрее, чем мы. И тут нам на помощь приходят циклы.

В Visual Basic'е есть несколько видов циклов - мы остановимся на одном, For :Next.

Синтаксис цикла:

For счетчик = начальное_значение To конечное_значение Step шаг
    Выполняемые операции
Next счетчик 

Например:
Option Explicit

Private Sub Command1_Click()
Dim i As Integer
'Объявляем переменную, для хранения текущего значения счетчика
For i = 1 To 10 Step 2
'По умолчанию значение Step = 1 и может не указываться
    Print i
'Выведем значения счетчика на форму
'Получим ряд 1 3 5 7 9
Next i
End Sub

Или, например, если опустить шаг
Private Sub Command2_Click()
Dim i As Integer
For i = 1 To 10
    Print i
'Получим ряд 12345678910
'Т.е. все значения от 1 до 10
Next i
End Sub

С циклом все должно быть понятно, так что приступим к главной задаче.

Если вы читали статье о работе с файлами - ничего нового вы не прочитаете, как создавать файлы. Но при сочетании цикла и кода создания файла, получим программу для массового создания файлов.

Создаем новый проект, с одной формой, нам понадобятся 1 TextBox и 2 CommandButton. Двойным щелчком по свойству Caption формы выделим текст, и заменим на CreateFiles, так будет называться наша программа.

Переименуем текстовое поле в txtCount, а кнопки cmdStart и cmdExit, наверное, тут все понятно.

txtCount - сюда будем вводить нужное нам число, т.е. количество файлов.
cmdStart - кнопка, запускающая проект.
cmdExit - кнопка, для выхода из программы.
Вот собственно код нашей программы:
Option Explicit
'Не буду разжевывать, почему этот оператор необходим, тем более для новичков
'Скажу коротко, после его использования необходимо явно объявлять все
'переменные. Т.е. если захотите использовать не объявленную переменную,
'то бейсик начнет ругаться

Dim intN As Integer
'Объявляем переменную для хранения числа файлов
Dim intI As Integer
'Объявляем счетчик
Dim intNumber As Integer
'Объявляем переменную для хранения номера файла

Private Sub cmdStart_Click()
intN = Val(txtCount)
'Присваиваем переменной значение, введенное в текстовое поле
'Функция Val возвращает числовое представление строки из текстового поля
'Т.е. если в текстовое поле введено не числовое значение, то
'функция возвратит 0, а, следовательно, файлов создано не будет
Debug.Print intN
'Для проверки - действительно ли функция возвратит 0
'Используем такой код и запустим проект
'После нажатия на кнопку старт
'в окне Immediate внизу экрана появится значение переменной intN
'Используем Debug - только для проверки и далее просто удаляем эту строку
For intI = 1 To intN
'Цикл от 1 до значения_переменной intN
'Цикл будет повторяться в зависимости от того, какое число введено в текстовое поле
    intNumber = FreeFile
    'Чтобы мы могли работать с файлами нам необходимо знать следующий
    'свободный номер файла, функция FreeFile возвращает этот номер
    'Он необходим для использования в операторе Open
    Open App.Path & "\" & intI & ".doc" For Output As intNumber
    'App.Path - возвращает путь к папке, где был запущен файл, он необходим
    'для того чтобы файлы создавались в папке с программой
    'intI & ".doc" - формируется имя файлов из переменной счетчика и расширения для файлов
    'Почему из переменной счетчика, для того, чтобы имена файлов шли по порядку, с 1 до
    'максимального значения.
    'С помощью Output можно открыть файл для сохранения данных, но так как его нет,
    'он создается. А если файл уже будет в папке - он будет перезаписан
    'Так что это дело остается за вами, проверок мы делать не будем
    'Следите, чтобы не испортить нужный вам файл
    Close intNumber
    'Закрываем файл с номером, который хранится в intNumber
Next intI
'После того, как был создан файл, цикл повторяется, пока не будет достигнуто
' конечное значение переменной intI
End Sub

Private Sub cmdExit_Click()
Unload Me
'Выходим из программы
'Так же можно использовать Unload Form1
'или просто End
End Sub


Private Sub Form_Load()
txtCount = ""
'При загрузке формы очистим текстовое поле
txtCount.MaxLength = 3
'Свойство MaxLength ограничивает ввод в поле 3 символами
End Sub

Ну, вроде бы и все.

Компилируем проект в "exe" файл и запускаем!

Ура!

Программа работает, вводим в текстовое поле 5 и нажимаем Старт.

В папке с программой появились 5 "doc" файлов.

Удачи в труде.

Добавлено: 22 Сентября 2013 01:46:06 Добавил: Андрей Ковальчук

Реверс строки, или как из ТОПОРа сделать РОПОТ

Да я знаю, что специальной АПИшки для этого нет, Но нужна ли она нам? Когда используя 3 переменные, 1 цикл и пару функций Можно реализовать данное превращение.

Для тех, кто в танке и не понял из названия, о чем идет речь, объясняю.

В данной статье я объясню, как написать программу для переворачивания строк, т.е. как ТОПОР превратить в РОПОТ.

Раньше этот вопрос, должно быть, был актуален, но в 6 версии Visual Basic появилась функция StrReverse, которая позволяет, написав всего несколько строчек кода перевернуть необходимый вам текст. Помимо StrReverse в 6 версии добавлены еще такие функции работы со строками как Split, InstrRev, Filter, Join. Так же включены специализированные функции FormatNumber, FormatDateTime, FormatCurrency и FormatPercent.

Синтаксис:

StrReverse(Expression As String)

Изменяет порядок следования символов в строке, на обратный. Т.е. если переменная содержит "ТИП", функция возвратит "ПИТ".

Пример использования функции StrReverse:
Option Explicit
'Делаем объявление переменных обязательным
Dim strStroka As String
'Объявляем переменную, для хранения текста, строкового типа

Private Sub Form_Load()
'При загрузке формы уберем весь текст из полей
Text1.Text = ""
Text2.Text = ""
End Sub

Private Sub Text1_Change()
'Чтобы отслеживать изменения в текстовом поле, весь код помести в событие поля Change.
strStroka = Text1.Text
'Текст введенный в текстовое поле присваиваем переменной strStroka
Text2.Text = StrReverse(strStroka)
'Выводим результат работы функции во второе текстовое поле
End Sub

А как же поступали раньше, да, интересно.

А чего тут думать давайте напишем программу, без использования функции.

Работа со строками может приносить интересные плоды, и я не совсем давно написал одну программу.

Объясню, зачем нужны переменные, строка из поля будет попадать для обработки в первую переменную - strA, во вторую переменную, strB, после обработки будет помещен результат, после чего выведен во второе текстовое поле. Ну а третья переменная, I, понадобится для хранения значения счетчика, используемого в цикле.

Что нам необходимо?

Не будем терять время и приступим, вначале создадим новый проект и форму переименуем из Form1 в ReversThis.

На форме расположим 2 TextBox (текстовых поля) и 2 CommandButton (кнопки).

Переименуем соответственно txtFirst и txtSecond, cmdCls и cmdExit. Думаю, название элементов говорят сами за себя, в первое текстовое поле мы будем вводить текст, а во втором получать результат разворота. Чтобы получилась не сырая недоработка, а законченный проект - нажатие на одну кнопку будет очищать текстовые поля, а на вторую завершать приложение.

Для выполнения поставленной задачи нам потребуется использовать цикл For :Next и 2 функции работы со строками: Len и Mid.

Функция Len возвращает число символов содержащихся в строковой переменной, Len(strA).

Например:

Помести на форме TextBox, Label.
Dim strA as String
strA = Text1.text
Label1.Caption = Len(strA)
Чтобы программа отслеживала изменения, происходящие в текстовом поле, этот код должен происходить по событию Change.

Private Sub Text1_Change()

End Sub

Для очищения текстового поля и метки по событию формы Load, приравняем: Text1 = "" и Label1 = ""
Private Sub Form_Load()

End Sub

Но вернемся к нашей программе.

Для чего же нужна будет нам функция Len?

Мы вводим текст в поле и с помощью функции отслеживаем длину строки, чтобы использовать ее в цикле.

Задумались?

Это значение будет указывать нам максимальное значение в цикле. Т.е. цикл будет происходить от 1 до Len(strA).

Ладно, дальше станет понятнее. Еще нам нужна функция Mid.

Синтаксис:
Mid("переменная", "начальная_позиция", "количество_символов")
Например:

Private Sub Form_Load()
Dim strA As String
strA = "Пример Использования Функции"
Label1 = Mid(strA, 8, 13)
'Результат "Использования"
End Sub

С помощью функции Mid мы будем обрабатывать переменную strB.

Вот собственно итог нашей работы:
Option Explicit
'Откройте пункт в меню Tools=>Options...
'И поставьте галочку, Require Variable Declaration
'Теперь Option Explicit, будет появляться автоматически при создании нового проекта
'Это нужно для предотвращения лишних ошибок, в работе с переменными
Dim strA As String
'Объявляем переменную strA как строковую
'Строка взятая из ТБ1
Dim strB As String
'Объявляем переменную strB как строковую
'Строка помещена в ТБ2
Dim I As Integer

Private Sub Form_Load()
'при загрузке формы очистим оба текстовых поля
txtFirst.Text = ""
txtSecond.Text = ""
End Sub

Private Sub txtFirst_Change()
'Событие Change возникает при любом действии с текстовым полем
'Это нажатие на клавишу или вставка из буфера
strB = ""
'При каждом изменении strB будет очищаться, и строиться заново
strA = txtFirst.Text
'Помещаем в переменную текст из поля
For I = 1 To Len(strA)
'Цикл For ...Next
'Начальное значение 1, конечное это - длина строки из переменной
    strB = Mid(strA, I, 1) & strB
    'Самая важная строка.
    'Строим переменную, обрезая переменную strA
    'Где I меняется в цикле от 1 до длины строки
    'В переменную обрезается 1 символ, & strB - сохраняет прежнее значение
Next I
'Продолжение цикла
txtSecond.Text = strB
'Вывод результата в текстовое поле
End Sub

Private Sub cmdCls_Click()
'При нажатии на кнопку очищаем текстовые поля и переменные
txtSecond.Text = ""
strB = ""
txtFirst.Text = ""
strA = ""
End Sub

Private Sub cmdExit_Click()
'Выгружаем форму и завершаем программу
Unload Me
End Sub

Добавлено: 22 Сентября 2013 01:44:03 Добавил: Андрей Ковальчук

Работа с текстовыми файлам

Текстовые файлы встречаются нам повсюду. И эта статья покажет вам основные методы работы с текстовыми файлами - создание/редактирование, чтение/сохранение, поиск слова в тексте.

Приступим !

Создайте стандартный проект и наклейте на него текст-бокс с именем "txtText", укажите у него свойство "MultiLine" равным "True" - "Истина" и две коммандные кнопки с именами "btnOpen" и "btnSave". Свойство "Caption" укажите у "btnOpen" - "Открыть", а у "btnSave" - "Сохранить". Сделайте двойной клик по кнопке "Сохранить" и в программный код впишите

Open "c:\temp.txt" For Output As #1
Print #1, txtText.Text
Close #1

Поясняю: "Open" это открытие файла "c:\temp.txt" это имя открываемого файла, если его нет, то он будет создан, если есть, то весь текст в этом файле будет очищен и заменём текстом из "txtText". "For output as #1" = "Для выхода под номером 1" т.е открываем файл под порядковым номером один, для записи информации в него. Вместо единицы можно указываль любое число от 1 до 500 - это является обязательным параметром. Зато #'ом можно пренебречь - он как бы для удобства, сразу будет понятно, что эта единица или другое число относится к файлу, а не к какой-нибудь переменной, хотя в некоторых местах его нельзя использовать!

"Print #1, txtText" - "Print"(Печать) - Метод позволяющий влить содержимое "txtText" в файл под номером один.

"Close #1" - это для закрытия текстового файла, так как если он не будет закрыт, то пока программа не будет закрыта, он не будет доступен другим программам.

Всё! С этой частью мы разобрались. Запускайие проект, вводите какую-нибудь информацию в "txtText" и нажимайте кнопку "Сохранить".

Теперь у вас на диске "C:" создан файл с именем "temp.txt". Но сохранить сохранили, надо же ещё и прочесть...

Чтение файла немного посложнее... Делайте двойной клик по кнопке "Открыть" и вписывайте
Open "c:\temp.txt" For Input As #1
Do Until EOF(1) 
Line Input #1, txt 
alltxt = alltxt + txt + vbCrLf 
Loop 
Close #1
txtText.Text = alltxt

Поясняю: "Open "c:\temp.txt"" - это тоже самое, что и при сохранении, а "For Input As #1" - это "Для входа под номером 1" т.е только чтение файла.

Do Until EOF(1) - "Читать, пока не будет достигнуть конец файла" т.е считывать построчно пока не будет достигнута последняя строка в читаемом файле и это как раз та самая ситуация, в которой нельзя указывать знак # в скобках.

Line Input #1, txt - "Линия входа файла под номером 1 = переменной txt"

alltxt = alltxt + txt + vbCrLf - переменная alltxt равна самой себе, плюс переменная txt. А vbCrLf - это уникальная константа, которая заменят "Chr(13)+Chr(10)" т.е переход на новую строку. Если мы её не вставим, то текстовый файл будет одной строкой в txtText.

Close #1 - закроем файл.

txtText.Text = alltxt - здесь выливаем в текст-бокс открытый файл, содержащийся в переменной alltxt.

Всё! Можете проверить.

Сохранять и открывать файлы мы научились...а как искать текст в окне...чем же наша программа хуже обычного блокнота Windows:)

Наклеем на форму ещё один текст-бокс с именем "txtSearch" и ещё одну кнопку с именем "btnSearch" и свойством "Caption" равным "Поиск"

Сделаем двойной клик по форме и в раздел "General Declarations" впишем "Dim P As Integer" - это переменная, которая будет содержать в себе позицию курсора в текстовом поле. В событие Form_Load впишите "P = 1".

Теперь сделаем двойной клик по кнопке "Поиск" и в программный код впишем
' Если искомое слово присутствует то...
If InStr(P, txtText.Text, txtSearch.Text) <> 0 Then 
' Текстовое поле с главным текстом получает фокус:)
txtText.SetFocus
'Начинаем выделять слово...
txtText.SelStart = InStr(P, txtText.Text, txtSearch.Text) - 1 
' Завершаем выделение
txtText.SelLength = Len(txtSearch.Text) 
' Изменяем переменную на позицию курсора
P = InStr(P, txtText.Text, txtSearch.Text) + Len(txtSearch.Text) 
' Если в тексте больше нет искомого слова, то выводим об этом сообщение.
Else 
MsgBox "Искомое слово не найдено"
End If

Добавлено: 22 Сентября 2013 01:36:12 Добавил: Андрей Ковальчук

Проверка орфографии с помощью Microsoft Word

Зачем делать то, что уже сделано другими? Поэтому для проверки орфографии в ваших приложениях можно использовать один из самых популярных текстовых редакторов - Word от Microsoft. На примере создания простого приложения я покажу, как это сделать. Создаете стандартный проект, натягиваете не форму text1, multiline=true и command1. Теперь самое важное - подключаете библиотеку Microsoft Word 9.0 Object Library (это для Office 2000, для `97 - Microsoft Word 7.0 Object Library), весит она порядка 536 KB, но таскать ее за собой не надо, т.к. уж Office установлен если не у всех, то Word уж точно почти у всех. Кстати в этой библиотеке еще много полезных свойств, которые есть в Word, но это уже к теме не относится, да и теперь уже вы сами можете с помощью Object Browser в VB, посмотреть их. Использование аналогично тому, которое я приведу здесь далее. Так, я отвлекся, после того, как подключили библиотеку добавьте такой код:

Dim WordApplication As Object
Private Sub Command1_Click()
Set WordApplication = New Word.Application
WordApplication.Documents.Add
WordApplication.Visible = False
WordApplication.Selection.Text = Text1.Text
WordApplication.ActiveDocument.CheckSpelling
Text1.Text = WordApplication.Selection.Text
WordApplication.ActiveDocument.Close wdDoNotSaveChanges
WordApplication.Quit
Set WordApplication = Nothing
End Sub

Теперь поясню. Вначале мы объявляем переменную типа объект. Все она объявлена и может работать. При нажатии на кнопку происходит следующее: создается новый объект Word.Application, добавляется новый документ, который делается невидимым пользователя, в этот документ копируется содержимое текстового окна нашего приложения, проверяется орфография. Если во время проверки обнаружится ошибка, то появится стандартное вордовское окошко с предложением вариантов. После проверки обновленный или оставленный без изменений текст возвращается в текстовое окно нашего приложения, а Word закрывается без запроса на сохранения, т.к. указана константа wdDoNotSaveChanges, потом закрывается сам Word, а память очищается от нашего объекта. Примечания:
Set WordApplication = New Word.Application
Set WordApplication = Nothing

Нужно располагать именно в процедуре проверки, т.к. это позволит избежать сообщения об ошибке при повторной попытки проверки за сеансом, т.к. если поместить в событие загрузки и выгрузки формы, то объект будет создавать ошибку. Для использования этого метода нужно, чтобы на машине клиента стоял Office или как минимум Word

Добавлено: 22 Сентября 2013 01:33:42 Добавил: Андрей Ковальчук

Звук через внутренний динамик

В этой статье я расскажу вам о том, как выдавать звуковой сигнал через внутренний динамик. Для этого мы воспользуемся API функцией Beep. Ну что, перейдём к делу:

1. Создайте новый проект.

2. На созданную вместе с проектом форму добавьте две метки (lbl1, lbl2) , две горизонтальные полосы прокрутки (srlChastota и srlTime) и одну кнопку (cmdGen):

Имя элемента управления: Свойство: Значение:
lbl1 Caption Частота звука:
lbl2 Caption Продолжительность звука:
srlChastota Min 37
srlChastota Max 32767
srlTime Min 0
srlTime Max 10000
cmdGen Caption &Сгенерировать сигнал

После всех перечисленных действий форма должна выглядеть примерно так:



3 Добавьте следующий код:

Option Explicit
'Функция генерирует звуковой сигнал
Private Declare Function Beep Lib "kernel32"
     (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long
'dwFred - частота звука
'dwDuration - продолжительность сигнала в миллисекундах
Private Sub cmdGen_Click()
'Генерируем звук
Beep srlChastota.Value, srlTime.Value
End Sub

Вот и всё! Вы создали программу для генерации звука из системного динамика.

Добавлено: 21 Сентября 2013 12:14:45 Добавил: Андрей Ковальчук

Блокировка CTRL+ALT+DEL и ALT+TAB в Windows 95/98

Иногда возникает необходимость в Visual Basic приложении отключить на время для пользователя возможность использования комбинаций CTRL+ALT+DEL и ALT+TAB. Ниже на примере показано как можно этого добиться.

Выберем проект как Standard EXE.
Добавим на форму две кнопки CommandButton. Назовем их Command1 и Command2 соответственно.
Затем добавим приведенный ниже код.

Private Const SPI_SCREENSAVERRUNNING = 97&
Private Declare Function SystemParametersInfo Lib "User32" _
Alias "SystemParametersInfoA" _
(ByVal uAction As Long, _
ByVal uParam As Long, _
lpvParam As Any, _
ByVal fuWinIni As Long) As Long

Private Sub Form_Load()
Command1.Caption = "Отключить комбинации !"
Command2.Caption = "Включить комбинации !"
End Sub

Private Sub Form_Unload(Cancel As Integer)
'Восстановить возможность использ. комбинаций CTRL+ALT+DEL и ALT+TAB
Command2_Click
End Sub

Private Sub Command1_Click()
Dim lngRet As Long
Dim blnOld As Boolean
lngRet = SystemParametersInfo(SPI_SCREENSAVERRUNNING, True, _
blnOld, _
0&)
End Sub

Private Sub Command2_Click()
Dim lngRet As Long
Dim blnOld As Boolean
lngRet = SystemParametersInfo(SPI_SCREENSAVERRUNNING, False, _
blnOld, _
0&)
End Sub

Весь секрет работы программы в использовании Win32 API функции SystemParametersInfo. Меняя второй параметр этой функции в True или в False можно добиться нужного результата. Т.е. True - соответствует ОТКЛЮЧИТЬ, а False - ВКЛЮЧИТЬ возможность использования комбинаций CTRL+ALT+DEL и ALT+TAB. В нашем примере нажимая кнопки "Отключить !" и "Включить !" можно блокировать и разблокировать использование комбинаций CTRL+ALT+DEL и ALT+TAB.

Добавлено: 21 Сентября 2013 12:12:37 Добавил: Андрей Ковальчук

Потоки в Visual Basic

Только потому, что Вы должны ,
что-то делать не всегда означает,
что у Вас получится...

С появлением оператора AddressOf, часть индустрии ПО стала ориентироваться на авторов, показывающих как с использованием Visual Basic решать ранее невозможные задачи. Другая часть быстро охватила консультантов, помогающих пользователям, имеющим проблемы при решении таких задач.

Проблема не в Visual Basic или в технологии. Проблема в том, что большинство авторов применяют одно и тоже правило к AddressOf методикам, что большинство компаний по разработке ПО считают, что если Вы должны что-то сделать, то Вы сможете. Идея о том, что применение самой новой и последней технологии должно, по определению, быть самым лучшим решением проблемы, широко распространена в индустрии ПО. Эта идея неверна. Развертывание технологии должно управляться прежде всего проблемой, которую необходимо решить решить, а не технологией, которую кто-то пробует Вам впарить;).

Очень плохо, что из-за того, что компании часто пренебрегают упоминанием об ограничениях и недостатках их инструментальных средств, авторы иногда бывают не в состоянии обратить внимание читателей на следствия некоторых методик, которые они описывают. И журналы и книги иногда пренебрегают своей ответственностью, чтобы удостовериться, что практика программирования, которую они описывают, является приемлемой.

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

Недавние статьи в Microsoft Systems Journal и Visual Basic Programmer's Journal представили программистам на Visual Basic возможность использования функции API CreateThread, чтобы непосредственно поддерживать многопоточный режим под Visual Basic. После этого, один читатель пожаловался, что моя книга Visual Basic Programmer's Guide to the Win32 API является неполной, потому что я не описал в ней эту функцию и не продемонстрировал эту технологию. Эта статья - частично является ответом этому читателю, и частично - ответом на другие статьи, написанными на эту тему. Эта статья также является дополнением к главе 14 моей книги "Разработка ActiveX компонент на Visual Basic 5.0" относительно новых возможностей, обеспечиваемых Visual Basic 5.0 Service Pack 2.

Быстрый обзор Многопоточности

Если Вы уже хорошо разбираетесь в технологии многопоточного режима, то Вы можете пропустить этот раздел и продолжать чтение с раздела, названного "Что нового в Service Pack 2."

Каждый, кто использует Windows, знает, что Windows способно делать больше чем одну вещь одновременно. Может одновременно выполнять несколько программ, при одновременном проигрывании компакт-диска, посылке факса и пересылке файлов. Каждый программист знает (или должен знать) что ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР компьютера может только выполнять одну команду одновременно (проигнорируем существование многопроцессорных машин). Как единственный ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР может выполнять множество задач?

Это делается быстрым переключением между многими задачами. Операционная система содержит в памяти все программы, которые запущены в настоящий момент. Это позволяет ЦЕНТРАЛЬНОМУ ПРОЦЕССОРУ выполнять программы по очереди. Каждый раз происходит переключение между программами, при этом меняется содержимое внутренних регистров, включая указатель команды и указатель вершины стека. Каждая из таких "задач" называется потоком выполнения (thread of execution).

В простой многозадачной системе, каждая программа имеет емеет единственный поток. Это означает, что ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР начинает выполнение команд в начале программы и продолжает следуя инструкциям в последовательности, определенной программой до тех пор, пока программа не завершается.

Скажем, программа имеет пять команд: B C D и E, которые выполняются последовательно (никаких переходов нет в этом примере). Когда приложение имеет один поток, команды будут всегда выполнять в точно том же самом порядке: A, B, C, D и E. Действительно, ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР может потребовать времени для выполнения других команд в других программах, но они не будут влиять на это приложение, если не имеется конфликт над общими ресурсами системы, но это уже отдельная тема для разговора.

Продвинутая многопоточная операционная система типа Windows позволяет приложению выполнять больше чем один поток одновременно. Скажем, команда D в нашем типовом приложении могла создать новый поток, который стартовал командой B и далее выполнял последовательность команд C и E. Первый поток был бы все еще A, B, C, D, E, но когда команда D выполнится, возникнет новый поток, который выполнит команды бы B, C, E (здесь команды D уже не будет, иначе мы получим еще один поток).

В каком порядке будут следовать команды в этом приложении?

Это могло бы быть:

Thread 1 A B C D E
Thread 2 B C E
Или так:

Thread 1 A B C D E
Thread 2 B C E
Или этак:

Thread 1 A B C D E
Thread 2 B C E
Другими словами, когда Вы начинаете новый поток выполнения в приложении, Вы никогда не можете знать точный порядок, в котором команды в двух потоках выполнятся относительно друг друга. Два потока полностью независимы.

Почему - это проблема?

Имитатор Многопоточности

Рассмотрим проект MTDemo:

Проект содержит один модуль кода, в котором содержится две глобальных переменных:

' MTDemo - Multithreading Demo program
' Copyright © 1997 by Desaware Inc. All Rights Reserved
Option Explicit
Public GenericGlobalCounter As Long
Public TotalIncrements As Long
' Этот проект содержит одну форму - frmMTDemo1, которая содержит
' следующий код:
' MTDemo - Multithreading Demo program
' Copyright © 1997 by Desaware Inc. All Rights Reserved
Option Explicit
Dim State As Integer
	' State = 0 - Idle
	' State = 1 - Loading existing value
	' State = 2 - Adding 1 to existing value
	' State = 3 - Storing existing value
	' State = 4 - Extra delay	
Dim Accumulator As Long
Const OtherCodeDelay = 10
Private Sub Command1_Click()
	Dim f As New frmMTDemo1
	f.Show
End Sub
Private Sub Form_Load()
	Timer1.Interval = 750 + Rnd * 500
End Sub
Private Sub Timer1_Timer()
	Static otherdelay&
	Select Case State
		Case 0
			lblOperation = "Idle"
			State = 1
		Case 1
			lblOperation = "Loading Acc"
			Accumulator = GenericGlobalCounter
			State = 2
		Case 2
			lblOperation = "Incrementing"
			Accumulator = Accumulator + 1
			State = 3
		Case 3
			lblOperation = "Storing"
			GenericGlobalCounter = Accumulator
			TotalIncrements = TotalIncrements + 1
			State = 4
		Case 4
			lblOperation = "Generic Code"
			If otherdelay >= OtherCodeDelay Then
				State = 0
				otherdelay = 0
			Else
				otherdelay = otherdelay + 1
			End If
		End Select
	UpdateDisplay
End Sub
Public Sub UpdateDisplay()
	lblGlobalCounter = Str$(GenericGlobalCounter)
	lblAccumulator = Str$(Accumulator)
	lblVerification = Str$(TotalIncrements)
End Sub

Эта программа для моделирования многопоточного режима использует таймер и простой конечный автомат. Переменная State описывает пять команд, которые эта программа выполняет. State = 0 - неактивное состояние. State = 1 загружает локальную переменную глобальной переменной GenericGlobalCounter. State = 2 увеличивает на единицу локальную переменную. State = 3 запоминает результат в переменной GenericGlobalCounter и увеличивает переменную TotalIncrements (которая считает количество приращений переменной GenericGlobalCounter). State = 3 добавляет дополнительную задержку, представляющую собой время, затраченное на выполнение других команд в программе.

Функция UpdateDisplay обновляет три метки на форме, которые показывают текущее значение переменной GenericGlobalCounter, локального сумматора, и общего количества приращений.

Каждый сигнал таймера моделирует цикл ЦЕНТРАЛЬНОГО ПРОЦЕССОРА в текущем потоке. Если Вы запустите программу, то увидете, что значение переменной GenericGlobalCounter будет всегда точно равно переменной TotalIncrements, потому что переменная TotalIncrements показывает количество увеличений счетчика GenericGlobalCounter потоком.

Но что случится, когда Вы нажимаете кнопку Command1 и запустите второй экземпляр формы? Эта новая форма смоделирует второй поток.

Время от времени, команды выстроятся в линию таким образом, что обе формы загрузят одинаковое значение GenericGlobalCounter, увеличат и сохранят его. В результате, значение счетчика увеличится только на единицу, даже при том, что каждый поток полагал, что он независимо увеличивает значение счетчика. Другими словами, переменная была увеличена дважды, но значение увеличилось только на единицу. Если Вы запускаете несколько форм, то сразу заметите, что число приращений, представляемой переменной TotalIncrements, растет намного быстрее, чем счетчик GenericGlobalCounter.

Что, если переменная представляет объектный счет блокировки - который следит, когда объект должен быть освобожден? Что, если она представляет собой сигнал, который указывает, что ресурс находится в использовании?

Такая проблема может привести к появлению ресурсов, постоянно недоступных в системе, к объекту, блокируемому в памяти, или преждевременно освобожденному. Это может привести к сбоям приложения.

Этот пример был разработан, чтобы достаточно просто увидеть проблему, но попробуйте поэкспериментировать со значением переменной OtherCodeDelay. Когда опасный код относительно небольшой по сравнению со всей программой, проблемы появятся менее часто. Хотя это и звучит обнадеживающе, но истина состоит в следующем. Проблемы Многопоточного режима могут быть чрезвычайно неустойчивы и их трудно обнаружить. Это означает, что многопоточный режим требует осторожного подхода к проектированию приложения.

Решение проблем Многопоточности

Имеются два относительно простых способа избежать проблем многопоточного режима.

Избегайте всеобщего использования глобальных переменных.

Добавьте код синхронизации везде, где используются глобальные переменные.

Первый подход используется в основном в Visual Basic. Когда Вы включаете многопоточный режим в Visual Basic приложения, все глобальные переменные станут локальными для специфического потока. Это свойственно способу, с которым Visual Basic выполняет apartment model threading - подробнее об этом позднее.

Первоначальный выпуск Visual Basic 5.0 позволял использовать многопоточность только в компонентах, которые не имели никаких элементов пользовательского интерфейса. Так было потому что они не имели безопасного потока управления формами. Например: когда Вы создаете форму в Visual Basic, VB дает ей имя глобальной переменной (таким образом, если Вы имеете форму, именованную Form1, Вы можете непосредственно обращаться к ее методам, используя Form1.метод вместо того, чтобы объявить отдельную переменную формы). Этот тип глобальной переменной может вызывать проблемы многопоточного режима, которые Вы видели ранее. Имелись несомненно другие проблемы внутри управления формами.

С service pack 2, управление формами Visual Basic было сделано безопасным потоком. Это говорит о том, что каждый поток имеет собственную глобальную переменную для каждой формы, определенной в проекте.

Что нового в Service Pack 2

Сделав поток управления формами безопасным, Service pack 2 предоставил возможность с помощью Visual Basic создавать клиентские приложения, использующие многопоточный режим.

Приложение должно быть определено как программа ActiveX Exe с установкой запуска из Sub Main:
' MTDemo2 - Multithreading demo program
' Copyright © 1997 by Desaware Inc. All Rights Reserved
Option Explicit
Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) _
							As Long
Sub Main()
	Dim f As frmMTDemo2
	' We need this because Main is called on each new thread
	Dim hwnd As Long
	hwnd = FindWindow(vbNullString, "Multithreading Demo2")
	If hwnd = 0 Then
		Set f = New frmMTDemo2
		f.Show
		Set f = Nothing
	End If
End Sub

Первый раз программа загружает и отображает основную форму приложения. Подпрограмма Main должна выяснить, является ли это первым потоком приложения, поэтому этот код выполняется при старте каждого потока. Вы не можете использовать глобальную переменную, чтобы это выяснить, потому что Visual Basic apartment model хранит глобальные переменные специфическими для одиночного потока. В этом примере используется функция API FindWindow, чтобы проверить, была ли загружена основная форма примера. Имеются другие способы выяснить, является ли это основным потоком, включая использование объектов синхронизации системы - но это отдельная тема для разговора.

Многопоточный режим реализуется созданием объекта в новом потоке. Объект должен быть определен, используя модуль класса. В этом случае, простой модуль класса определяется следующим образом:
' MTDemo2 - Multithreading demo program
' Copyright © 1997 by Desaware Inc. All Rights Reserved
Option Explicit
Private Sub Class_Initialize()
	Dim f As New frmMTDemo2
	f.Show
	Set f = Nothing
End Sub

Мы можем установить переменную формы как nothing после того, как она создана, потому что после отображения формы она будет сохранена.
' MTDemo2 - Multithreading demo program
' Copyright © 1997 by Desaware Inc. All Rights Reserved
Option Explicit
Private Sub cmdLaunch1_Click()
	Dim c As New clsMTDemo2
	c.DisplayObjPtr Nothing
End Sub
Private Sub cmdLaunch2_Click()
	Dim c As clsMTDemo2
	Set c = CreateObject("MTDemo2.clsMTDemo2")
End Sub
Private Sub Form_Load()
	lblThread.Caption = Str$(App.ThreadID)
End Sub

Форма отображает идентификатор потока в метке на форме. Форма содержит две командные кнопки, одна из которых использует оператор New, другая -использует оператор CreateObject.

Если Вы запустите программу внутри среды Visual Basic, то увидите, что формы всегда создаются в одном и том же потоке. Это происходит, потому что среда Visual Basic поддерживает только одиночный поток. Если Вы скомпилируете и запустите программу, то увидите, что подход, использующий CreateObject создает и clsMTDemo2 и ее форму в новом потоке.

Почему многопоточность

Откуда вся суета относительно многопоточного режима, если он включает так много потенциальной опасности? Потому что, в некоторых ситуациях, многопоточный режим может значительно улучшать эффективность приложения. В некоторых случаях это может улучшать эффективность некоторых операций синхронизации типа ожидания завершения приложения. Это позволяет сделать архитектуру приложения более гибкой. Например, операция Add a long в форме MTDEMO2 со следующим кодом:
Private Sub cmdLongOp_Click()
Dim l&
Dim s$
For l = 1 To 1000000
    s = Chr$(l And &H7F)
Next l
End Sub

Запустите несколько экземпляров формы, используя кнопку cmdLaunch1. Когда Вы нажимаете на кнопку cmdLongOp на любой из форм, то увидите, что это действие замораживает операции на всех других формах. Так происходит, потому что все формы выполняются в одиночном потоке - и этот поток занят выполнением длинного цикла. Если Вы запустите несколько экземпляров формы кнопкой cmdLaunch2 и нажимете кнопку cmdLongOp на форму, то только эта форма будет заморожена - другие формы будут активными. Они выполняются в собственных потоках, и длинный цикл будет выполняться только в собственном потоке. Конечно, в любом случае, Вы вероятно не должны размещать длительные операции такого типа в ваших формах.

Дальше краткое резюме, когда важен многопоточный режим:

Сервер ActiveX EXE – без общих ресурсов.

Когда Вы имеете ActiveX EXE сервер, который Вы собираетесь совместно использовать среди нескольких приложений, многопоточный режим предотвращает приложения от нежелательных взаимодействий с друг другом. Если одно приложение выполняет длинную операцию на объекте в однопоточном сервере, другие приложения будут вытеснены, т.е. будут ждать, когда освободится сервер. Многопоточный режим рещает эту проблему. Однако, имеются случаи, где Вы можете хотеть использовать ActiveX EXE сервер, чтобы регулировать доступ к общедоступнному ресурсу (shared resource). Например, сервер stock quote, описанный в моей книге Developing ActiveX Components. В этом случае сервер stock quote выполняется в одиночном потоке и который доступен для всех приложений, использующих сервер по очереди.

Многопоточный клиент – выполняемый как ActiveX EXE сервер

Простая форма этого подхода продемонстрирована в приложении MTDEMO2. Этот подход используется, когда приложение поддерживает множественные окна, которые должны исходить из одного приложения, но работать полностью независимо. Интернет-браузер - хороший пример такого многопоточного клиента, где каждое окно выполняется в собственном потоке. Здесь следует обратить внимание на то, что многопоточный режим не должен использоваться как замена для хорошего событийно управляемого проекта.

Многопоточные серверы DLL или EXE

В архитектуре клиент-сервер, многопоточный режим может увеличить эффективность, если Вы имеете смесь длинных и коротких клиентских запросов. Будьте внимательным, хотя - если все ваши клиентские запросы имеют подобную длину, многопоточный режим может фактически замедлять среднее время ответа сервера! Никогда не принимайте на веру тот факт, что если ваш сервер является многопоточным, то обязательно его эффективность увеличится.

Соглашение о потоках

Верите или нет, но все это было введением. Часть этого материала является обзором материала, который описан в моей книге Developing ActiveX Components, другая часть материала описывает новую информацию для service pack 2.

Теперь, позволите задавать вопрос, который имеет отношение к многопоточному режиму, использующему COM (модель многокомпонентных объектов, на которой основаны не только все Visual Basic объекты, но и другие windows приложения, использующие технологии OLE).

Дано:

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

Вопрос:

Как это возможно, что Visual Basic позволяет Вам создавать объекты и использовать их с одиночными и многопоточными средами безотносительно к тому, разработаны ли они для одиночного или многопоточного использования?

Другими словами - Как многопоточные Visual Basic приложения могут использовать объекты, которые не разработаны для безопасного выполнения в многопоточной среде? Как могут другие многопоточные приложения использовать однопоточные объекты Visual Basic?

Коротко: как COM поддерживает потоки?

Если Вы знаете COM, то Вы знаете, что COM определяет структуру соглашения. Объект COM соглашается следовать некоторым правилам так, чтобы этим можно было успешно пользоваться из любого приложения или объекта, который поддерживает COM.

Большинство людей сначала думает о интерфейсной части соглашения - о методах и свойствах, которые предоставляет объект.

Но Вы не можете не знать того, что COM также определяет поточность как часть соглашения. И подобно любой части соглашения COM - если Вы нарушаете эти условия, то будете иметь проблемы. Visual Basic, естественно, скрывает от Вас большинство механизмов COM, но чтобы понять как использовать многопоточность в Visual Basic, Вы должны разобраться COM модели потоков.

Модель одиночного потока:

Однопоточный сервер - самый простой тип реализации сервера. И самый простой для понимания. В этом случае EXE сервер выполняется в одиночном потоке. Все объекты создаются в этом потоке. Все вызовы методов каждого объекта, поддерживаемого сервером должны прибыть в этот поток.

Но что будет, если клиент выполняется в другом потоке? В том случае, для объекта сервера должен быть создан промежуточный объект (proxy object). Этот промежуточный объект выполняется в потоке клиента и отражает методы и свойства фактического объекта. Когда вызывается метод промежуточного объекта, он выполняет операции, необходимые для подключению к потоку объекта, а затем вызывает метод фактического объекта, используя параметры, переданные к промежуточному объекту. Естественно, что этот подход требует значительного времени на выполнение задачи, однако он позволяет выполнить все соглашения. Этот процесс переключения потоков и пересылки данных от промежуточного объекта к фактическому объекту и обратно называется marshalling. Эта тема обсуждается в главе 6 моей книги Developing ActiveX Components.

В случае DLL серверов, одиночная потоковая модель требует, чтобы все объекты в сервере создавались и вызывались в том же самом потоке что и первый объект, созданный сервером.

Модель Apartment Threading

Обратите внимание, что модель Apartment Threading как определено COM не требует, чтобы каждый поток имел собственный набор глобальных переменных. Visual Basic таким образом реализует модель Apartment Threading. Модель Apartment Threading декларирует, что каждый объект может быть создан в собственном потоке, однако, как только объект создан, его методы и свойства могут вызываться только тем же самым потоком, которая создал объект. Если объект другого потока захочет иметь доступ к методам этого объекта, то он должен действовать через промежуточный объект.

Такая модель относительно проста для реализации. Если Вы устраняете глобальные переменные (как делает Visual Basic), модель Apartment Threading автоматически гарантирует безопасность потока - так как каждый объект действительно выполняется в собственном потоке, и благодаря отсутствию глобальных переменных, объекты в разных потоках не взаимодействуют друг с другом.

Модель свободных потоков

Модель свободных потоков (Free Threading Model) заключается в следующем.. Любой объект может быть создан в любом потоке. Все методы и свойства любого объекта могут быть вызываны в любое время из любого потока. Объект принимает на себя всю ответственность за обработку любой необходимой синхронизации.

Это самая трудная в реализации модель, так как требуется, чтобы всю синхронизацию обрабатывал программист. Фактически до недавнего времени, технология OLE непосредственно не поддерживала эту модель! Однако, с тех пор marshalling никогда не требуется и это наиболее эффективная модель потоков.

Какую модель поддерживает ваш сервер?

Как приложение или сама Windows узнает, которую модель потоков использует сервер? Эта информация включена в системный реестр (registry). Когда Visual Basic создает объект, он проверяет системный реестр, чтобы определить, в каких случаях требуется использовать промежуточный объект (proxy object) и в каких - marshalling.

Эта проверка является обязанностью клиента и необходима для строгой поддержки требований многопоточности для каждого объекта, которого он создает.

Функция API CreateThread

Теперь давайте посмотрим, как с Visual Basic может использоваться функция API CreateThread. Скажем, Вы имеете класс, что Вы хотите выполненять в другом потоке, например, чтобы выполнить некоторую фоновую операцию. Характерный класс такого типа мог бы иметь следующий код (из примера MTDemo 3):
' Class clsBackground
' MTDemo 3 - Multithreading example
' Copyright © 1997 by Desaware Inc. All Rights Reserved
Option Explicit
Event DoneCounting()
Dim l As Long
Public Function DoTheCount(ByVal finalval&) As Boolean
Dim s As String
	If l = 0 Then
		s$ = "In Thread " & App.threadid
		Call MessageBox(0, s$, "", 0)
	End If
	l = l + 1
	If l >= finalval Then
		l = 0
		DoTheCount = True
		Call MessageBox(0, "Done with counting", "", 0)
		RaiseEvent DoneCounting
	End If
End Function

Класс разработан так, чтобы функция DoTheCount могла неоднократно вызываться из непрерывного цикла в фоновом потоке. Мы могли бы поместить цикл непосредственно в сам объект, но вы вскоре увидите, что были веские причины для проектирования объекта как показано в примере. При первом вызове функции DoTheCount появляется MessageBox, в котором показан идентификатор потока, по которому мы можем определить поток, в котором выполняется код. Вместо VB команды MessageBox используется MessageBox API, потому что функция API, как известно, поддерживает безопасное выполнение потоков. Второй MessageBox появляется после того, как закончен подсчет и сгенерировано событие, которое указывает, что операция закончена.

Фоновый поток запускается при помощи следующего кода в форме frmMTDemo3:
Private Sub cmdCreateFree_Click()
	Set c = New clsBackground
	StartBackgroundThreadFree c
End Sub

Функция StartBackgroundThreadFree определена в модуле modMTBack следующим образом:
Declare Function CreateThread Lib "kernel32" _
	(ByVal lpSecurityAttributes As Long, ByVal _
	dwStackSize As Long, ByVal lpStartAddress As Long, _
	ByVal lpParameter As Long, ByVal dwCreationFlags _
	As Long, lpThreadId As Long) As Long
Declare Function CloseHandle Lib "kernel32" _
	(ByVal hObject As Long) As Long
' Start the background thread for this object
' using the invalid free threading approach.
Public Function StartBackgroundThreadFree _
			(ByVal qobj As clsBackground)
	Dim threadid As Long
	Dim hnd&
	Dim threadparam As Long
	' Free threaded approach
	threadparam = ObjPtr(qobj)
	hnd = CreateThread(0, 2000, AddressOf _
		BackgroundFuncFree, threadparam, 0, threadid)
	If hnd = 0 Then
		' Return with zero (error)
		Exit Function
	End If
	' We don't need the thread handle
	CloseHandle hnd
	StartBackgroundThreadFree = threadid
End Function

Функция CreateThread имеет шесть параметров:

lpSecurityAttributes - обычно устанавливается в нуль, чтобы использовать заданные по умолчанию атрибуты защиты.

dwStackSize - размер стека. Каждый поток имеет собственный стек.

lpStartAddress - адрес памяти, где стартует поток. Он должен быть равен адресу функции в стандартном модуле, полученном при использовании оператора AddressOf.

lpParameter - long 32 разрядный параметр, который передается функции, запускающей новый поток.

dwCreationFlags - 32 бит переменная флагов, которая позволяет Вам управлять запуском потока (активный, приостановленный и т.д.). Подробнее об этих флагах можно почитать в Microsoft's online 32 bit reference.

lpThreadId - переменная, в которую загружается уникальный идентификатором нового потока.

Функция возвращает дескриптор потока.

В этом случае мы передаем указатель на объект clsBackground, который мы будем использовать в новом потоке. ObjPtr восстанавливает значение указателя интерфейса в переменную qobj. После создания потока закрывается дескриптор при помощи функции CloseHandle. Это действие не завершает поток, - поток продолжает выполняться до выхода из функции BackgroundFuncFree. Однако, если мы не закрыли дескриптор, то объект потока будет существовать даже после выхода из функции BackgroundFuncFree. Все дескрипторы потока должны быть закрыты и при завершении потока система освобождает занятые потоком ресурсы.

Функция BackgroundFuncFree имеет следующий код:
' A free threaded callback.

' A free threaded callback.
' This is an invalid approach, though it works
' in this case.
Public Function BackgroundFuncFree(ByVal param As _
					IUnknown) As Long
	Dim qobj As clsBackground
	Dim res&
	' Free threaded approach
	Set qobj = param
	Do While Not qobj.DoTheCount(100000)
	Loop
	' qobj.ShowAForm ' Crashes!
	' Thread ends on return
End Function

Параметром этой функции является- указатель на интерфейс (ByVal param As IUnknown). При этом мы можем избежать неприятностей, потому что под COM каждый интерфейс основывается на IUnknown, так что такой тип параметра допустим независимо от типа интерфейса, передаваемого функции. Мы, однако, должны немедленно определить param как тип объекта, чтобы затем его использовать. В этом случае qobj установливается как объект clsBackground, который был передан к объекту StartBackgroundThreadFree.

Функция затем выполняет бесконечный цикл, в течение которого может выполняться любая требуемая операция, в этом случае повторный счет. Подобный подход мог бы использоваться здесь, чтобы выполнить операцию ожидания, которая приостанавливает поток пока не произойдкт системное событие (типа завершения процесса). Поток затем мог бы вызвать метод класса, чтобы сообщить приложению, что событие произошло.

Доступ к объекту qobj чрезвычайно быстр из-за использования подхода свободного потока (free threading) - никакая переадресация (marshalling) при этом не используется.

Обратите внимание на то, что если Вы попробуете использовать объект clsBackground, который показывает форму, то это приведет к сбоям приложения. Обратите также внимание на то, что событие завершения никогда не происходит в клиентской форме. Действительно, даже Microsoft Systems Journal, который описывает этот подход, содержит очень много предупреждений о том, что при использовании этого подхода есть некоторые вещи, которые не работают.

Некоторые разработчики, кто пробовали развертывать приложения, применяющие этот тип многопоточности, обнаружили, что их приложения вызывают сбои после обновления к VB5 service pack 2.

Является ли это дефектом Visual Basic?
Означает ли это, что Microsoft не обеспечила совместимость?
Ответ на оба вопроса: Нет
Проблема не в Microsoft или Visual Basic.
Проблема состоит в том, что вышеупомянутый код является мусором.
Проблема проста - Visual Basic поддерживает объекты и в модели одиночного потока и в apartment model. Позвольте мне перефразировать это: объекты Visual Basic являются COM объектами и они,согласно COM соглашению, будут правильно работать как в модели одиночного потока так и в apartment model. Это означает, что каждый объект ожидает, что любые вызовы методов будут происходить в том же самом потоке, который создал объект.

Пример, показанный выше, нарушает это правило.
Это нарушает соглашение COM.
Что это означает?

Это означает, что поведение объекта подчиненно изменениям, так как Visual Basic постоянно модифицируется.

Это означает, что любая попытка объекта обратиться к другим объектам или формам может потерпеть неудачу и что причины отказов могут изменяться, поскольку эти объекты модифицируются.

Это означает, что даже код, который сейчас работает, может внезапно вызвыть сбой, поскольку другие объекты добавляются, удаляются или изменяются.

Это означает, что невозможно характеризовать поведение приложения или предсказать, будет ли оно работать или может ли работать в любой данной среде.

Это означает, что невозможно предсказать, будет ли код работать на любой данной системе, и что его поведение может зависеть от используемой операционной, числа используемых процессоров и других проблем конфигурации системы.

Вы видите, что как только Вы нарушаете соглашение COM, Вы больше не защищены теми возможностями COM, которые позволяют объектам успешно взаимодействовать друг с другом и с клиентами.

Этот подход является программной алхимией. Это безответственно и ни один программист не должен когда-либо использовать это. Точка.

Обратно к функции API CreateThread

Теперь, когда я показал Вам, почему подход к использованию CreateThread API, показанный в некоторых статьях, является мусором, я покажу Вам, как можно использовать эту API функцию безопасно. Прием прост - Вы должны просто твердо придержаться соглашения COM о потоках. Это займет немного больше времени и усилий, но практика показала, что получаются очень надежные результаты.

Пример MTDEMO3 демонстрирует этот подход в форме frmMTDemo3, имеющей код, который запускает класс фона в apartment model следующим образом:
Private Sub cmdCreateApt_Click()
	Set c = New clsBackground
	StartBackgroundThreadApt c
End Sub

Пока это выглядит очень похоже на подход свободных потоков. Вы создаете экземпляр класса и передаете его функции, которая запускает фоновый поток. В модуле modMTBack появляется следующий код:
' Structure to hold IDispatch GUID
Type GUID
	Data1 As Long
	Data2 As Integer
	Data3 As Integer
	Data4(7) As Byte
End Type
Public IID_IDispatch As GUID
Declare Function CoMarshalInterThreadInterfaceInStream Lib _
	"ole32.dll" (riid As GUID, ByVal pUnk As IUnknown, _
	ppStm As Long) As Long
Declare Function CoGetInterfaceAndReleaseStream Lib _
	"ole32.dll" (ByVal pStm As Long, riid As GUID, _
	pUnk As IUnknown) As Long
Declare Function CoInitialize Lib "ole32.dll" (ByVal _
	pvReserved As Long) As Long
Declare Sub CoUninitialize Lib "ole32.dll" ()
' Start the background thread for this object
' using the apartment model
' Returns zero on error
Public Function StartBackgroundThreadApt(ByVal qobj _
					As clsBackground)
	Dim threadid As Long
	Dim hnd&, res&
	Dim threadparam As Long
	Dim tobj As Object
	Set tobj = qobj
	' Proper marshaled approach
	InitializeIID
	res = CoMarshalInterThreadInterfaceInStream _
			(IID_IDispatch, qobj, threadparam)
	If res <> 0 Then
		StartBackgroundThreadApt = 0
		Exit Function
	End If
	hnd = CreateThread(0, 2000, AddressOf _
		BackgroundFuncApt, threadparam, 0, threadid)
	If hnd = 0 Then
		' Return with zero (error)
		Exit Function
	End If
	' We don't need the thread handle
	CloseHandle hnd
	StartBackgroundThreadApt = threadid
End Function

Функция StartBackgroundThreadApt немного более сложна чем ее эквивалент при применении подхода свободных потоков. Первая новая функция называется InitializeIID. Она имеет следующий код:
' Initialize the GUID structure
Private Sub InitializeIID()
	Static Initialized As Boolean
	If Initialized Then Exit Sub
	With IID_IDispatch
		.Data1 = &H20400
		.Data2 = 0
		.Data3 = 0
		.Data4(0) = &HC0
		.Data4(7) = &H46
	End With
	Initialized = True
End Sub

Вы видите, нам необходим идентификатор интерфейса - 16 байтовая структура, которая уникально определяет интерфейс. В частности нам необходим идентификатор интерфейса для интерфейса IDispatch (подробная информация относительно IDispatch может быть найдена в моей книге Developing ActiveX Components). Функция InitializeIID просто инициализирует структуру IID_IDISPATCH к корректным значениям для идентификатора интерфейса IDispatch. Значение Это значение получается с помощью использования утилиты просмотра системного реестра.

Почему нам необходим этот идентификатор?

Потому что, чтобы твердо придерживаться соглашения COM о потоках, мы должны создать промежуточный объект (proxy object) для объекта clsBackground. Промежуточный объект должен быть передан новому потоку вместо первоначального объекта. Обращения к новому потоку на промежуточном объекте будут переадресованы (marshaled) в текущий поток.

CoMarshalInterThreadInterfaceInStream выполняет интересную задачу. Она собирает всю информацию, необходимую при создании промежуточного объекта, для определенного интерфейса и загружает ее в объект потока (stream object). В этом примере мы используем интерфейс IDispatch, потому что мы знаем, что каждый класс Visual Basic поддерживает IDispatch и мы знаем, что поддержка переадресации (marshalling) IDispatch встроена в Windows - так что этот код будет работать всегда. Затем мы передаем объект потока (stream object) новому потоку. Этот объект разработан Windows, чтобы быть передаваемым между потоками одинаковым способом, так что мы можем безопасно передавать его функции CreateThread. Остальная часть функции StartBackgroundThreadApt идентична функции StartBackgroundThreadFree.

Функция BackgroundFuncApt также сложнее чем ее эквивалент при использовании модели свободных потоков и показана ниже:
' A correctly marshaled apartment model callback.
' This is the correct approach, though slower.
Public Function BackgroundFuncApt(ByVal param As Long) As Long
	Dim qobj As Object
	Dim qobj2 As clsBackground
	Dim res&
	' This new thread is a new apartment, we must
	' initialize OLE for this apartment
	' (VB doesn't seem to do it)
	res = CoInitialize(0)
	' Proper apartment modeled approach
	res = CoGetInterfaceAndReleaseStream(param, _
					IID_IDispatch, qobj)
	Set qobj2 = qobj
	Do While Not qobj2.DoTheCount(10000)
	Loop
	qobj2.ShowAForm
	' Alternatively, you can put a wait function here,
	' then call the qobj function when the wait is satisfied
	' All calls to CoInitialize must be balanced
	CoUninitialize
End Function

Первый шаг должен инициализировать подсистему OLE для нового потока. Это необходимо для переадресации (marshalling) кода, чтобы работать корректно. CoGetInterfaceAndReleaseStream создает промежуточный объект для объекта clsBackground и реализует объект потока (stream object), используемый для передачи данных из другого потока. Интерфейс IDispatch для нового объекта загружается в переменную qobj. Теперь возможно получить другие интерфейсы - промежуточный объект будет корректно переадресовывать данные для каждого интерфейса, который может поддерживать.

Теперь Вы можете видеть, почему цикл помещен в эту функцию вместо того, чтобы находиться непосредственно в объекте. Когда Вы впервые вызовите функцию qobj2.DoTheCount, то увидите, что код выполняется в начальном потоке! Каждый раз, когда Вы вызываете метод объекта, Вы фактически вызываете метод промежуточного объекта. Ваш текущий поток приостанавливается, запрос метода переадресовывается первоначальному потоку и вызывается метод первоначального объекта в той же самом потоке, который создал объект. Если бы цикл был в объекте, то Вы бы заморозили первоначальный поток.

Хорошим результатом применения этого подхода является то, что все работает правильно. Объект clsBackground может безопасно показывать формы и генерировать события. Недостатком этого подхода является, конечно, его более медленное исполнение. Переключение потоков и переадресация (marshalling) - относительно медленные операции. Вы фактически никогда не захотите выполнять фоновую операцию как показано здесь.

Но этот подход может чрезвычайно хорошо работать, если Вы можете помещать фоновую операцию непосредственно в функцию BackgroundFuncApt! Например: Вы могли бы иметь фоновый поток, выполняющий фоновые вычисления или операцию ожидания системы. Когда они будут завершены, вы можете вызывать метод объекта, который сгенерирует событие в клиенте. Храня количество вызовов метода, небольшое относительно количества работы, выполняемой в фоновой функции, Вы можете достигать очень эффективных результатов.

Что, если Вы хотите выполнить фоновую операцию, которая не должна использовать объект? Очевидно, проблемы с соглашением COM о потоках исчезают.

Добавлено: 20 Сентября 2013 06:09:47 Добавил: Андрей Ковальчук

Добавление иконки в SystemTray средствами Visual Basic

Эта статья является самодостаточной, то есть в ней дана исчерпывающая информация по созданию иконки в SystemTray с помощью VB. Однако при этом она является компиляцией общедоступных источников, то есть заслуга автора состоит лишь в сборе этой информации в одном месте и пояснениях.

Основы создания иконки изложены в FAQ Льва Серебрякова. Используется пример на VB от Alexander Shherbakov. Описания функций и констант из книги Daniel Applemana и API.TXT. Вопросы связанные с редактором ресурсов не рассматриваются.

Единственная функция для работы с иконкой Shell_NotifyIcon. Ее описание на VB выглядит так:

Declare Function Shell_NotifyIcon Lib "shell32.dll" Alias "Shell_NotifyIconA" _ 
(ByVal dwMessage As dwMess, lpData As NOTIFYICONDATA) As Long

Возвращает ноль в случае ошибки

Тип dwMess описывается так:
Public Enum dwMess


NIM_ADD = &H0 ' Добавление иконки
NIM_DELETE = &H2 ' Удаление иконки 
NIM_MODIFY = &H1 ' Изменение параметров иконки
End Enum

Переменная dwMessage должна иметь одно из этих значений.

Тип NOTIFYICONDATA имеет следующую структуру:
Type NOTIFYICONDATA

cbSize As Long ' Размер переменной типа NOTIFYICONDATA
hwnd As Long ' Указатель окна создающего иконку 
uID As Long ' Указатель на иконку в пределах приложения 
uFlags As uF ' Маска для следующих параметров 
uCallbackMessage As CallMess ' Возвращаемое событие 
hIcon As Long ' Указатель на изображение для иконки 
szTip As String * 64 ' Всплывающий над иконкой текст
End Type 


Где тип uF имеет вид:
Public Enum uF

NIF_MESSAGE = &H1 ' Значение имеет uCallbackMessage
NIF_ICON = &H2 ' Значение имеет hIcon 
NIF_TIP = &H4 ' Значение имеет szTip

End Enum 

Эти константы можно применять в любых сочетаниях, для определения какой из параметров имеет значение.


Тип CallMess:
Public Enum CallMess

WM_MOUSEMOVE = &H200 
WM_LBUTTONDOWN = &H201 
WM_LBUTTONUP = &H202
WM_LBUTTONDBLCLK = &H203
WM_RBUTTONDOWN = &H204
WM_RBUTTONUP = &H205
WM_RBUTTONDBLCLK = &H206


WM_MBUTTONDOWN = &H207
WM_MBUTTONUP = &H208
WM_MBUTTONDBLCLK = &H209
WM_SETFOCUS = &H7
WM_KEYDOWN = &H100
WM_KEYFIRST = &H100
WM_KEYLAST = &H108
WM_KEYUP = &H101

End Enum 


Эти константы обозначают, какое событие возвращается вызывающей форме. Буквально, все, что будет происходить с иконкой, будет вызывать у формы одно из перечисленных событий. Ясно, что самое частое событие самой иконки это MouseMove, но для формы оно будет выглядеть как событие заданное переменной uCallbackMessage. Как же узнать, что в действительности произошло с иконкой? Это можно узнать через переменные X и Y событий MouseMove, MouseDown и MouseUp вызывающей формы. При этом Y, если событие произошло с иконкой, а не формой, всегда будет равно нулю, а X несет информацию о событии с иконкой.


О параметре X следует сказать отдельно. Действительно, он передает информацию о событиях с иконкой, однако эти значения зависят от масштабного коэффициента системного шрифта, но не напрямую, а через параметр свойства TwipsPerPixelX объекта Screen. То есть для одной и той же системы, при разных величинах системного шрифта, значения будут разными. Начальными значениями событий являются следующие:

MouseMove - 512

LeftButtonDown - 513

LeftButtonUp - 514

LeftButtonDblClick - 515

RightButtonDown - 516

RightButtonUp - 517

RightButtonDblClick - 518


Для того чтобы узнать действующие в данной системе значения их следует умножить на Screen.TwipsPerPixelX

Как же узнать, что событие произошло с иконкой, а не с формой? Просто, по значению Y, равному нулю. Но есть и другой способ, если используется двухкнопочная мышь то параметр Button в событиях MouseDown и MouseUp формы, будет принимать значения 1 и 2, и при uCallbackMessage равно WM_MBUTTONDOWN=&H207 или WM_MBUTTONUP = &H208 Button равен 4, если событие с иконкой. Само собой разумеется, что возвращаемые X значения следуют одно за другим, как и события (Down->Up->DbClick),поэтому невозможно на одну кнопку мыши назначить два события, к примеру, Click и DbClick. События не связанные с мышью не несут практически ни какой информации, и обычно не используются, следует так же отметить, что количество констант uCallbackMessage намного больше и здесь приведена лишь небольшая часть

Из описанного видно, что с иконкой можно совершить одно из следующих действий: добавить, модифицировать и удалить, при этом, модифицируя можно заменить возвращаемое событие, картинку (указатель при этом останется тем же) и всплывающую надпись (ToolTips).

Следующий момент, который нужно осветить это получение hIcon (указателя на картинку). Предполагается, что иконка будет находится в исполняемом файле или в DLL с ресурсами, но ни в коем случае не валяется в виде ICO файла. Если иконка запакована в DLL, то нам понадобятся две функции:
Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal _ 
lpLibFileName As String) As Long

Возвращающая hInstance библиотеки с именем lpLibFileName. Достаточно указать только имя файла с расширением, без пути. Возвращает ноль в случае ошибки
Declare Function LoadIconA Lib "user32" (ByVal hInstance As Long, ByVal _
lpIconName As String) As Long

Возвращающая hIcon для иконки указанной параметром lpIconName в библиотеке. Этот параметр может быть String или Long, в зависимости от данного вами наименования в Res файле, соответственно надо изменить декларацию. Можно передать и число как строку, для этого перед числом ставится знак #, а все это берется в кавычки. Следует заметить, что использование срокового параметра не желательно из за значительно большего размера занимаемой памяти и соответственно, большего времени на передачу параметра. Функция возвращает ноль в случае ошибки

Понадобится так же функция:
Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long

Выгружающая библиотеку из памяти. Параметр hLibModule это hInstanse, возвращаемое LoadLibrary. Возвращает ноль в случае ошибки.

Обязательно надо не забыть выгрузить из памяти библиотеку, для освобождения памяти. Выгрузку можно произвести сразу же после добавления иконки в SystemTray.

Обязательно надо не забыть выгрузить из памяти библиотеку, для освобождения памяти. Выгрузку можно произвести сразу же после добавления иконки в SystemTray.
Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" _ 
(ByVal lpModuleName As String) As Long

Возвращающей hInstanse нашего приложения. В качестве lpModuleName передается имя EXE файла с расширением. Следует быть внимательным, так как имя процесса в TaskMenager не всегда соответствует имени процесса для Windows. Я использую для определения имени DLLView, можно воспользоваться, встроенным в VB System Information. Функция возвращает действительное значение только при работе скомпилированного приложения, а в режиме отладки возвращает ноль, ведь реального процесса при отладке не существует. Свойство hInstanse объекта App всегда возвращает действительное значение, однако при отладке из за отсутствия процесса LoadIcon возвращает 0, и создается "пустая" иконка, тем не менее годная для отладки (реагирующая на все события).

Полученное hInstanse передаем LoadIconA, в качестве lpIconName указываем номер или имя иконки в Res файле, как и в случае с DLL. Выгружать в этом случае ничего не надо.

Создание иконки можно проиллюстрировать следующим примером.

Считается, что иконка с номером 101 находится в файле Project1.exe. Понятно, что пока мы его не скомпилировали, ее там нет (да и самого файла нет). Форма приложения называется Form1.
Dim NID As NOTIFYICONDATA 

Sub AddIcon() 

Dim IDLib As Long ' Указатель на библиотеку
Dim IDIcon As Long ' Указатель на иконку 
Const IDMyIcon = 101 ' Идентификатор иконки внутри приложения
Dim AddResult As Long ' Результат добавления иконки

IDLib = GetModuleHandle("Project1.exe") ' Получаем hInstanse
IDIcon = LoadIcon(IDLib, "#101") ' Получаем hIcon 

' Заполняем структуру NID типа NOTIFYICONDATA

NID.cbSize = Len(NID) ' Размер структуры
NID.hwnd = Form1.hWnd ' Указатель на форму
NID.uID = IDMyIcon ' Идентификатор иконки
'Указываем, что действующими являются поля 
NID.uFlags = NIF_MESSAGE + NIF_ICON + NIF_TIP 
'uCallBackMessage, hIcon и szTip.
' Указываем, что событием возвращаемым в форму 
NID.uCallbackMessage = WM_LBUTTONDOWN 
'является MouseDown с параметром Button = 2
NID.hIcon = IDIcon ' Указатель на иконку в файле
' Передаем всплывающую фразу "MyIcon", при этом обрезаем 
NID.szTip = Left$("MyIcon", 63) & Chr(0) 
'ее до 63 символов и добавляем 64-й символ с кодом ноль
' Вызываем функцию, через параметр dwMessage указываем, 

AddResult = Shell_NotifyIcon(NIM_ADD, NID) 
'что следует добавить иконку, и передаем заполненный NID

End Sub

Удаление созданной иконки можно сделать так:
Sub DeleteIcon()

Dim DeletResult As Long
DeleteResult = Shell_NotifyIcon(NIM_DELETE, NID) 

' Вызываем функцию, через dwMessage указываем, 
'что следует удалить иконку, при этом, раз переменная NID
' описана на уровне модуля, не следует 
'заполнять ее заново 

End Sub


Размер структуры достаточно указывать один раз, так как за время жизни переменной он измениться не может, и в данном виде составляет 88 байт. Даже при изменении всплывающей строки ее длина (строки) не будет больше 64 байт.

Для модификации иконки надо вызвать Shell_NotifyIcon с параметром dwMessage равным NIM_MODIFY и NID с внесенными изменениями, при этом параметр uFlags будет указывать, какие из параметров изменены.

В форме Form1 для обработки, к примеру, DbClick левой кнопкой мыши по иконке можно применить следующий код:
Private Sub Form_MouseDown(Button As Integer, Shift As Integer _
X As Single, Y As Single)
' Событие MouseDown происходит не потому, 
'что пользователь нажал на кнопку мыши над иконкой, а из-за того, 
'что параметр uCallbackMessage имеет значение WM_LBUTTONDOWN 

If Y = 0 Then ' Y = 0 если событие с иконкой 
   Select Case X
      Case 515*Screen.TwipsPerPixelX ' Значение X при LeftDblClick 
                                     ' Код, выполняемый в случае LeftDblClick 
   End Select
End If

End Sub

Добавлено: 20 Сентября 2013 06:03:09 Добавил: Андрей Ковальчук

Приоритеты в Windows

Введение

По определению, Windows является многозадачной операционной системой. То есть одновременно в ней могут выполняться несколько задач. Однако задача задаче рознь. И различие это заключается в приоритете. То есть, при выполнении какой-либо программы Windows распределяет ресурсы определенным образом, в зависимости от того, какой приоритет у какого процесса (программы). Программы в дальнейшем я буду называть процессами, так проще. Каждая запущенная программа в Windows представляет собой процесс. Приоритеты бывают разные - большие, маленькие, но об этом потом. Чем это может нам помочь? А вот чем. Многие сетуют на медленность выполнения программ на Vb. В свое время я на это наткнулся на самом наглядном примере. Представьте себе программу, которая должна через определенный интервал времени выполнять какое-либо действие - ну, например, что-то выводить пользователю. Просто - скажете Вы. Берем таймер и вперед. А если помимо этого периодически программа должна что-то еще считать, и не один раз, а таймеров много? Будет тормозить. Причем не Windows, а только программа. Несправедливо? Да. Это значит, что Windows отделяет мало ресурсов Вашей программе, которых ей явно недостаточно. Значит попросить у Windows больше, только как?

Вот так

Значит надо менять приоритет. Самое время о них поговорить. В Windows'98 приоритетов меньше, чем в Windows Me или 2000, но они таки есть J. Вот они.

Приоритет реального времени (real time) - программа выполняется так, как будто она одна и есть цель жизни Windows. Все ресурсы отдаются ей.

Высокий приоритет (high) - программа выполняется так, чтобы оставить чуть-чуть остальным приложениям.

Нормальный (normal) - выполнение идет обычным путем.

Низкий (idle) - если выполнение и идет, то только когда Windows делать нечего.

Ну так вот, запущенная программа сначала получает приоритет Normal, и выполняется своим чередом. При этом приоритете, что бы программа ни делала, она не сможет съесть ресурсов столько, чтобы повис Windows. В теории. Но нам так не подходит. Нам надо приоритет high или, если все очень напряжно, real time. Но real time - вещь опасная. Может запросто затормозиться даже мышка. А об остальных программах я и говорить на хочу. Просто копец и все тут. Итак, как же все таки приоритет поменять.

Снова API

Да, за установку приоритета отвечает функция SetPriorityClass. То есть, вызвав ее, и передав все, что ей надо, мы получим нужный приоритет.

Private Declare Function SetPriorityClass _
Lib "kernel32" (ByVal hProcess As Long, _
ByVal dwPriorityClass As Long) As Long

hProcess - это Handle процесса. О нем ниже.
dwPriorityClass - приоритет. Он то нам и нужен.

Константы приоритетов:
Private Const REALTIME_PRIORITY_CLASS = &H100
Private Const HIGH_PRIORITY_CLASS = &H80
Private Const NORMAL_PRIORITY_CLASS = &H20
Private Const IDLE_PRIORITY_CLASS = &H40

Все просто - берем константу и вперед, но нам надо получить Handle процесса. То есть, процесс и окно для Windows вещи хоть и почти синонимичные, но не всегда. Handle окна - одна вещь, Handle процесса - совсем другая. То есть, у процесса может быть много окон, но у окна только один процесс владелец. Значит, нам надо научиться находить Handle именно процесса. В этом нам поможет функция OpenProcess, которая после передачи ей параметров, скажет, что за Handle у процесса.
Private Declare Function OpenProcess _
Lib "kernel32" (ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long

dwDesiredAccess - что мы хотим узнать (Handle у процесса бывают разные, для завершения процесса - одни, для получения информации - другой). Мы будем использовать константу PROCESS_QUERY_INFORMATION, то есть опрашивать процесс.
Private Const PROCESS_QUERY_INFORMATION = &H400

bInheritHandle - переменная Boolean. Если ее передать True, то полученный Handle может в будущем быть использован другим процессом. Но нам это не надо. Передаем туда False.

DwProcessId - так, а это очередная вещь - идентификатор (id) процесса. Нам надо и его получить. Как? Просто! Функция GetCurrentProcessId, вызываемая без параметров (слава Богу) просто возвращает id текущего процесса.
Private Declare Function GetCurrentProcessId _ 
Lib "kernel32" Alias "GetCurrentProcessId" () As Long

Все. Можно вздохнуть и все сделать красиво.

Немного кода
Option Explicit
Private Declare Function SetPriorityClass _
Lib "kernel32" (ByVal hProcess As Long, _
ByVal dwPriorityClass As Long) As Long
Private Const REALTIME_PRIORITY_CLASS = &H100
Private Const HIGH_PRIORITY_CLASS = &H80
Private Const NORMAL_PRIORITY_CLASS = &H20
Private Const IDLE_PRIORITY_CLASS = &H40
Private Declare Function OpenProcess _
Lib "kernel32" (ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Const PROCESS_QUERY_INFORMATION = &H400
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long

Private Sub SetPriority(Priority As Long)
Dim ProcId As Long 'переменная для id процесса
Dim ProcHandle As Long 'переменная для handle процесса
ProcId = GetCurrentProcessId 'получим id
ProcHandle = OpenProcess(PROCESS_QUERY_INFORMATION, False, ProcId)
'получили Handle
Call SetPriorityClass(ProcHandle, Priority) ' ставим приоритет
End Sub

Private Sub Form_Load()
'ставим приоритет
SetPriority HIGH_PRIORITY_CLASS
End Sub

Но сразу предупрежу - приоритет лучше всего менять не постоянно, а на время. То есть, перед критическим участком кода ставим приоритет High или RealTime, а потом снова Normal. Еще раз повторюсь, что использование RealTime приоритета - вещь опасная. Ну вот и все, заставьте Windows поделиться пирогом ресурсов!

Добавлено: 20 Сентября 2013 02:31:25 Добавил: Андрей Ковальчук