С Новым годом! Форум программистов, компьютерный форум, киберфорум
The trick
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Класс для копирования в отдельном потоке с отображением прогресса

Запись от The trick размещена 17.11.2013 в 00:21
Показов 4617 Комментарии 0
Метки vb

Бывает ситуация, когда нужно скопировать большой файл(ы), при этом стандартная ф-ция FileCopy вешает всю программу до тех пор, пока не закончится копирование. Я разработал класс, в котором используется возможности ф-ции CopyFileEx (использовал ANSI версию), отображение прогресса копирования и возможности отмены, а также многопоточность для запуска всех функций в фоновом потоке. При запущенном процессе копирования, нельзя останавливать среду кнопкой стоп, только закрытием (нужно обязательно вызывать деструктор класса), иначе возможны глюки. Также желательно не запускать одновременно копирование большого количества файлов т.к. на каждое копирование создается отдельный поток, и при большом их количестве будут тормоза. Для отдельного потока использовал ассемблерную вставку со следующим кодом:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
; Основная функция вызываемая в новом потоке при копировании
Copy:
    xor eax,eax     ; eax <- 0
    push eax        ; Локальная переменная pbCancel
    mov ecx,esp     ; Сохраняем адрес переменной 
    push eax        ; dwCopyFlags
    push ecx            ; Указатель на pbCancel 
    push eax        ; lpData
    push 0x0        ; lpProgressRoutine
    push 0x0        ; lpNewFileName
    push 0x0        ; lpExitingFileName
    call 0x0        ; Вызов CopyFileEx
    mov dword [0],eax   ; Возвращаемое значение
    xor eax,eax     ; dwExitCode
    call 0x0        ; Вызов ExitThread
; Функция обратного вызова CopyProgressRoutine
CopyProgressRoutine:
    fild qword [esp+12]     ; LARGE_INTEGER в вещественное число TotalBytesTransferred
    fild qword [esp+4]  ; LARGE_INTEGER в вещественное число TotalFileSize
    fdivp           ; делим на TotalFileSize
    fstp dword [0]      ; Сохраняем в переменную
    mov eax, dword [0]  ; Возвращаемое значение
    ret 0x34
Вместо нулей, вписываются данные походу в процедурах LoadStaticValue - это те, которые не будут изменяться и LoadDynamicValue - это имена файлов. Использовать класс можно и один для нескольких копирований или же несколько для одновременного копирования.
Код класса:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
' Класс для фонового копирования файла, с отображением прогресса копирования
' Автор: © Кривоус Анатолий Анатольевич (The trick) 2013
Option Explicit
 
Private Declare Function HeapCreate Lib "kernel32" (ByVal flOptions As Long, ByVal dwInitialSize As Long, ByVal dwMaximumSize As Long) As Long
Private Declare Function HeapDestroy Lib "kernel32" (ByVal hHeap As Long) As Long
Private Declare Function HeapAlloc Lib "kernel32" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function HeapFree Lib "kernel32" (ByVal hHeap As Long, ByVal dwFlags As Long, lpMem As Any) As Long
Private Declare Function GetMem4 Lib "msvbvm60" (pSrc As Any, pDst As Any) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function CreateThread Lib "kernel32" (lpThreadAttributes As Any, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, lpParameter As Any, ByVal dwCreationFlags As Long, lpThreadID As Long) As Long
Private Declare Function GetExitCodeThread Lib "kernel32" (ByVal hThread As Long, lpExitCode As Long) As Long
Private Declare Function SetThreadPriority Lib "kernel32" (ByVal hThread As Long, ByVal nPriority As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
 
Public Enum StateOperation
    COMPLETED                                                       ' Операция закончена успешно
    ACTIVE                                                          ' Операция выполняется
    FAILED                                                          ' Операция завершилась неудачей
End Enum
 
Private Const STILL_ACTIVE = &H103&
Private Const PROGRESS_CONTINUE = 0
Private Const PROGRESS_CANCEL = 1
Private Const COPY_FILE_FAIL_IF_EXISTS = &H1
Private Const HEAP_CREATE_ENABLE_EXECUTE = &H40000
Private Const HEAP_NO_SERIALIZE = &H1
Private Const INFINITE = &HFFFFFFFF
Private Const THREAD_BASE_PRIORITY_MIN = -2
Private Const THREAD_PRIORITY_LOWEST = THREAD_BASE_PRIORITY_MIN
 
Private Const AsmSize As Long = 64                                  ' Размер вставки в байтах
 
Private mSourceFileName As String                                   ' Путь, откуда копируем
Private mDestinationFileName As String                              ' Путь, куда копируем
Private mProgress As Single                                         ' Прогресс 0..1
 
Dim hHeap As Long                                                   ' Дескриптор кучи
Dim lpFunc As Long                                                  ' Адрес функции в ассемблерной вставке
Dim init As Boolean                                                 ' Инициализирован ли код потока
Dim Src() As Byte                                                   ' ASCII строка mSourceFileName
Dim Dst() As Byte                                                   ' ASCII строка mDestinationFileName
Dim ApiRet As Long                                                  ' Возвращаемое значение из API
Dim ProgressRet As Long                                             ' Возвращаемое значение из CopyProgressRoutine
Dim hThread As Long                                                 ' Хендл потока
 
Public Property Get SourceFileName() As String                      ' Возвращает путь откуда копировать
    SourceFileName = mSourceFileName
End Property
Public Property Let SourceFileName(FileName As String)              '
    mSourceFileName = FileName
End Property
Public Property Get DestinationFileName() As String                 ' Возвращает путь куда копировать
    DestinationFileName = mDestinationFileName
End Property
Public Property Let DestinationFileName(FileName As String)         '
    mDestinationFileName = FileName
End Property
Public Property Get Progress() As Single                            ' Возвращает значение от 0 до 1 прогресса копирования
    Progress = mProgress
End Property
Public Property Get State() As StateOperation                       ' Возвращает состояние выполнения операции
    If Process Then State = ACTIVE: Exit Property
    State = IIf(ApiRet, COMPLETED, FAILED)
End Property
Public Sub Copy()                                                   ' Запустить копирование
    Dim IDThrd As Long
    
    If Not init Or Process Then Exit Sub                            ' Если не инициализированы или уже идет процесс то выходим
    ProgressRet = PROGRESS_CONTINUE                                 ' Установка продолжения процесса
    LoadDynamicValue
    ApiRet = -1                                                     ' Проверка возвращаемого значения CopyFileEx
    hThread = CreateThread(ByVal 0, 0, lpFunc, ByVal 0, 0, IDThrd)  ' Запуск нового потока
    If hThread = 0 Then ApiRet = 0: Exit Sub                        ' Если не удалось создать поток, тогда устанавливаем ошибку
    SetThreadPriority hThread, THREAD_PRIORITY_LOWEST               ' Устанавливаем низкий приоритет потоку копирования
End Sub
Public Function Cancel(Optional Wait As Boolean = False) As Boolean ' Остановить текущий процесс, ждать завершения?
    If Process Then                                                 ' Имеет смысл только если идет процесс
        If Wait Then
            Call StopAll: Cancel = True                             ' Если ждем
        Else
            ProgressRet = PROGRESS_CANCEL                           ' Устанавливаем возвращаемое значение в CPR
            Cancel = True
        End If
    End If
End Function
Private Property Get Process() As Boolean                           ' Возвращает True если операция выполняется
    Dim Ret As Long
    If hThread = 0 Then Exit Property                               ' Если нет активного потока, тогда False
    GetExitCodeThread hThread, Ret                                  ' Запрашиваем, завершился ли поток
    If Ret = STILL_ACTIVE Then Process = True                       ' Если он активен, то возвращаем True
End Property
Private Sub StopAll()                                               ' Остановить все процессы
    ProgressRet = PROGRESS_CANCEL                                   ' Отменяем процессы
    If hThread Then
        WaitForSingleObject hThread, INFINITE                       ' Ждем завершения потока
    End If
    hThread = 0
End Sub
Private Sub CreateAsm(Asm() As Long)                                ' Создаем вставку
    ReDim Asm(-Int(-AsmSize / 4) - 1)                               ' Вычисляем нужный размер массива
    Asm(0) = &H8950C031: Asm(1) = &H505150E1: Asm(2) = &H68&
    Asm(3) = &H6800&: Asm(4) = &H680000: Asm(5) = &HE8000000
    Asm(6) = &H0&: Asm(7) = &HA3&: Asm(8) = &HE8C03100
    Asm(9) = &H0&: Asm(10) = &HC246CDF: Asm(11) = &H4246CDF
    Asm(12) = &H1DD9F9DE: Asm(13) = &H0&: Asm(14) = &HA1&
    Asm(15) = &H34C200
End Sub
Private Sub LoadDynamicValue()                                      ' Установка динамических значений в вставке
    Src = StrConv(mSourceFileName & vbNullChar, vbFromUnicode)      ' Переводим путь из Юникода в ANSI
    Dst = StrConv(mDestinationFileName & vbNullChar, vbFromUnicode) ' ...
    
    GetMem4 VarPtr(Src(0)), ByVal lpFunc + &H13&                    ' Установка указателя на Исходное размещение
    GetMem4 VarPtr(Dst(0)), ByVal lpFunc + &HE&                     ' Установка указателя на "Результирующее" размещение
End Sub
Private Sub LoadStaticValue(lpFunc As Long)                         ' Установка статичных значений в вставке
    Dim hKernel32 As Long                                           ' Хендл модуля Kernel32
    Dim lpCopyFileEx As Long                                        ' Адрес функции CopyFileEx
    Dim lpExitThread As Long                                        ' Адрес функции ExitThread
 
    hKernel32 = LoadLibrary("Kernel32.dll")                         ' Получаем хендл Kernel32.dll
    lpCopyFileEx = GetProcAddress(hKernel32, "CopyFileExA")         ' Получаем адреса функций ...
    lpExitThread = GetProcAddress(hKernel32, "ExitThread")          '
    
    GetMem4 lpFunc + &H28&, ByVal lpFunc + &H9&                     ' Установка указателя на CopyProgressRoutine
    GetMem4 lpCopyFileEx - (lpFunc + &H1C&), ByVal lpFunc + &H18&   ' Установка перехода на CopyFileExA
    GetMem4 lpExitThread - (lpFunc + &H28&), ByVal lpFunc + &H24&   ' Установка перехода на ExitThread
    
    GetMem4 VarPtr(ApiRet), ByVal lpFunc + &H1D&                    ' Установка указателя на возвращаемое значение CopyFileEx
    GetMem4 VarPtr(mProgress), ByVal lpFunc + &H34&                 ' Установка указателя на mProgress
    GetMem4 VarPtr(ProgressRet), ByVal lpFunc + &H39&               ' Установка указателя на возвращаемое значение CPR
End Sub
Private Sub Class_Initialize()
    Dim Asm() As Long                                               ' Буфер с ассемблерной вставкой
    
    CreateAsm Asm                                                   ' Создаем вставку
    hHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE Or _
            HEAP_NO_SERIALIZE, AsmSize, AsmSize)                    ' Создаем кучу, с разрешением для выполнения,
                                                                    ' размером с ассемблерную вставку
    If hHeap = 0 Then MsgBox "Error creating heap", vbCritical: _
            Exit Sub                                                ' При ошибке выходим
    lpFunc = HeapAlloc(hHeap, HEAP_NO_SERIALIZE, AsmSize)           ' Выделяем память в куче
    If lpFunc = 0 Then MsgBox "HeapAlloc return NULL", _
            vbCritical: Call Class_Terminate: Exit Sub              ' Не удалось выделить память
    CopyMemory ByVal lpFunc, Asm(0), AsmSize                        ' Копируем вставку в выделенную память
    LoadStaticValue lpFunc
    ApiRet = -1                                                     ' Чтобы отрабатывало свойство State
    init = True                                                     ' Инициализация успешно
End Sub
Private Sub Class_Terminate()
    If Process Then
        StopAll                                                     ' Останавливаем
    End If
    If lpFunc Then
        HeapFree hHeap, HEAP_NO_SERIALIZE, ByVal lpFunc             ' Освобождаем выделенную память
    End If
    If hHeap Then
        HeapDestroy hHeap                                           ' Удаляем кучу
    End If
End Sub
Код формы (для теста):
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
Option Explicit
 
Dim Fle As Collection                                                                   ' Коллекция объектов копируемых файлов
 
Private Sub cmdCancel_Click()                                                           ' Если жмем Cancel
    Dim Obj As clsProgressCopy
    If Not lstFileProgress.SelectedItem Is Nothing Then                                 ' Если выделен в листе
        Set Obj = Fle(lstFileProgress.SelectedItem.Index)                               ' Устанавливаем во временную переменную объект
        Obj.Cancel                                                                      ' Отменяем копирование
        RefreshList                                                                     ' Обновляем список
    End If
End Sub
Private Sub cmdCancelAll_Click()                                                        ' Если жмем Cancel All
    Dim Obj As clsProgressCopy
    For Each Obj In Fle                                                                 ' Для каждого объекта
        Obj.Cancel                                                                      ' отменяем копирование
    Next
    RefreshList                                                                         ' Обновляем список
End Sub
Private Sub cmdCopy_Click()
    Dim fName As String, Obj As clsProgressCopy, SrcPath As String, DstPath As String
    
    SrcPath = dirFoldersSrc.List(dirFoldersSrc.ListIndex) & "\"                         ' Устанавливаем пути
    DstPath = dirFoldersDst.List(dirFoldersDst.ListIndex) & "\"
    
    If StrComp(SrcPath, DstPath, vbTextCompare) = 0 Then _
              MsgBox "Выберите другую директорию": Exit Sub                             ' Если пути совпадают то выходим
    
    lstFileProgress.ListItems.Clear                                                     ' Очищаем список
    Do While Fle.Count: Fle.Remove (1): Loop                                            ' Очищаем коллекцию
    
    fName = Dir(SrcPath)                                                                ' Ищем первый файл в папке
    
    Do While Len(fName) And Fle.Count <= 64                                             ' Пока есть файлы и их количество <=64
        Set Obj = New clsProgressCopy                                                   ' Создаем объект фонового копирования
        Obj.SourceFileName = SrcPath & fName                                            ' Задаем пути
        Obj.DestinationFileName = DstPath & fName                                       ' ...
        Fle.Add Obj                                                                     ' Добавляем в коллекцию
        lstFileProgress.ListItems.Add , , fName                                         ' Добавляем в список
        Obj.Copy                                                                        ' Запускаем копирование
        fName = Dir()                                                                   ' Ищем следующий файл
    Loop
    
    If Fle.Count = 0 Then Exit Sub                                                      ' Если не было файлов, выходим
    
    SetState True                                                                       ' Сменяем контролы
   
    tmrRefresh.Enabled = True                                                           ' Запускаем таймер обновления
End Sub
Private Sub drvVolumeSrc_Change()
    dirFoldersSrc.Path = drvVolumeSrc.Drive                                             ' Смена диска
End Sub
Private Sub drvVolumeDst_Change()
    dirFoldersDst.Path = drvVolumeDst.Drive                                             ' Смена диска
End Sub
Private Sub SetState(Value As Boolean)                                                  ' Сменить рабочие контролы
    cmdCopy.Enabled = Not Value
    cmdCancel.Enabled = Value
    cmdCancelAll.Enabled = Value
    drvVolumeSrc.Enabled = Not Value
    drvVolumeDst.Enabled = Not Value
    dirFoldersSrc.Enabled = Not Value
    dirFoldersDst.Enabled = Not Value
End Sub
Private Sub RefreshList()                                                               ' Обновить список
    Dim Obj As clsProgressCopy, I As Long, s As Boolean
    For Each Obj In Fle
        With lstFileProgress.ListItems(I + 1)
            .Text = GetFileTitle(Obj.DestinationFileName)
            .SubItems(1) = Format(Obj.Progress, "0.00%")
            .SubItems(2) = Choose(Obj.State + 1, "Завершено", "Активно", "Ошибка")
        End With
        s = s Or (Obj.State = ACTIVE)                                                   ' Если хоть один еще работает, тогда s=True
        I = I + 1
    Next
    If Not s Then                                                                       ' Если все закончилось, очищаем все
        tmrRefresh.Enabled = False
        Do While Fle.Count: Fle.Remove (1): Loop
        SetState False
    End If
End Sub
Private Function GetFileTitle(Path As String) As String                                 ' Получить имя по пути
    Dim L As Long, P As Long
    L = InStrRev(Path, "\")
    P = Len(Path) + 1
    If P > L Then
        L = IIf(L = 0, 1, L + 1)
        GetFileTitle = Mid$(Path, L, P - L)
    ElseIf P = L Then
        GetFileTitle = Path
    Else
        GetFileTitle = Mid$(Path, L + 1)
    End If
End Function
Private Sub Form_Load()
    Set Fle = New Collection
    dirFoldersSrc.Path = drvVolumeSrc.Drive
    dirFoldersDst.Path = drvVolumeDst.Drive
End Sub
Private Sub Form_Resize()
    lstFileProgress.ColumnHeaders(1).Width = lstFileProgress.Width / 3
    lstFileProgress.ColumnHeaders(2).Width = lstFileProgress.Width / 3
    lstFileProgress.ColumnHeaders(3).Width = lstFileProgress.Width / 3
End Sub
 
Private Sub tmrRefresh_Timer()
    RefreshList
End Sub
PS. Т.к. не рекомендуется завершать потоки через TerminateThread, я использовал ExitThread в самом потоке, поэтому при большом количестве файлов, обрабатываемых одновременно, при закрытии окна, каждый класс ждет завершения своего потока и VB6 замирает на это время.
Миниатюры
Нажмите на изображение для увеличения
Название: Безымянный.png
Просмотров: 593
Размер:	23.4 Кб
ID:	1876  
Вложения
Тип файла: rar CopyProgress.rar (12.2 Кб, 447 просмотров)
Метки vb
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Модель микоризы: классовый агентный подход 3
anaschu 06.01.2026
aa0a7f55b50dd51c5ec569d2d10c54f6/ O1rJuneU_ls https:/ / vkvideo. ru/ video-115721503_456239114
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR
ФедосеевПавел 06.01.2026
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR ВВЕДЕНИЕ Введу сокращения: аналоговый ПИД — ПИД регулятор с управляющим выходом в виде числа в диапазоне от 0% до. . .
Модель микоризы: классовый агентный подход 2
anaschu 06.01.2026
репозиторий https:/ / github. com/ shumilovas/ fungi ветка по-частям. коммит Create переделка под биомассу. txt вход sc, но sm считается внутри мицелия. кстати, обьем тоже должен там считаться. . . .
Расчёт токов в цепи постоянного тока
igorrr37 05.01.2026
/ * Дана цепь постоянного тока с сопротивлениями и напряжениями. Надо найти токи в ветвях. Программа составляет систему уравнений по 1 и 2 законам Кирхгофа и решает её. Последовательность действий:. . .
Новый CodeBlocs. Версия 25.03
palva 04.01.2026
Оказывается, недавно вышла новая версия CodeBlocks за номером 25. 03. Когда-то давно я возился с только что вышедшей тогда версией 20. 03. С тех пор я давно снёс всё с компьютера и забыл. Теперь. . .
Модель микоризы: классовый агентный подход
anaschu 02.01.2026
Раньше это было два гриба и бактерия. Теперь три гриба, растение. И на уровне агентов добавится между грибами или бактериями взаимодействий. До того я пробовал подход через многомерные массивы,. . .
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru