Я тут изложил некоторые аспекты работы с массивами из VB
Прошу:
- Заценить
- Высказаться
Как мы знаем, все массивы в VB являются SAFEARRAY. Хранятся они тремя кусками:
- существует переменная длиной 4 байта, содержащая адрес структуры SAFEARRAY (указатель на указатель)
- существует сама структура SAFEARRAY (именно по тому самому адресу), размер разный
- существуют данные массива (в третьем месте - на него указывает член pvData структуры SAFEARRAY).
Из этого описания уже видно, что мы могли бы, к примеру, заимев указатель на произвольные данные (апишка какая-нибудь вернула, к примеру), записать его в pvData (4 байта всего!) и таким образом получить готовый массив. Или могли бы сделать так, чтобы два массива ссылались на 1 участок данных. Или ещё чего - обязательно придёт в голову какое-нибудь применение, как столкнёшься с конкретной задачей.
Всё это, конечно, хорошо, но как получить указатель хоть на что-то, относящееся к самому массиву, а не к его данным? Если применить VarPtr к элементу массива, то получим указатель на данные, а получить из него указатель на структуру SAFEARRAY нельзя. А если укажем аргументом VarPtr сам массив, то получим ошибку компиляции.
Но не всё так страшно. Делаем небольшую хитрость: переобъявляем функцию VarPtr под именем ArrPtr:
- Код: Выделить всё
Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (arr() As Any) As Long
Вот, собственно, и всё. Вызываем эту функцию - и у нас в кармане не что нибудь, а адрес указателя на SAFEARRAY - то есть самое начало этой цепочки!
Для дальнейшей работы объявим ещё парочку функций. Дело в том, что порой нужно просто получить Long, находящийся по такому-то адресу (банально дереференсить указатель), и для этого мы постоянно используем RtlMoveMemory aka CopyMemory. Это работает, но функция эта всё же лучше подходит для копирования относительно больших участков памяти - в случае с парой-тройкой байт затраты на вызов сожрут большую долю времени выполнения (во много раз большую). Кроме того, копирование там побайтовое, а нам нужно сразу (ведь нужно? ). Поэтому, заюзаем опять же нашу Virtual Machine:
- Код: Выделить всё
'Копирует 2 байта из src в dest. Возвращает 0.
Public Declare Function GetMem2 Lib "msvbvm60" (ByVal ptrToIntSrc As Long, ByVal ptrToIntDest As Long) As Long
'Копирует 4 байта из src в dest. Возвращает 0.
Public Declare Function GetMem4 Lib "msvbvm60" (ByVal ptrToLongSrc As Long, ByVal ptrToLongDest As Long) As Long
'Копирует 8 байт из src в dest. Возвращает 0.
Public Declare Function GetMem8 Lib "msvbvm60" (ByVal ptrToDDWORDSrc As Long, ByVal ptrToDDWORDDest As Long) As Long
'Записывает 2 байта в dest. Возвращает 0.
'Будет использован только LOWORD второго параметра.
Public Declare Function PutMem2 Lib "msvbvm60" (ByVal ptrToIntDest As Long, ByVal NewValue As Long) As Long
'Записывает 4 байта в dest. Возвращает 0.
Public Declare Function PutMem4 Lib "msvbvm60" (ByVal ptrToLongDest As Long, ByVal NewValue As Long) As Long
'Записывает 8 байт в dest. Возвращает 0.
Public Declare Function PutMem8 Lib "msvbvm60" (ByVal ptrToQWORDDest As Long, ByVal NewValueLowDWORD As Long, ByVal NewValueHighDWORD As Long) As Long
Эти функции очень эффективны, поверьте мне
Ну так вот...
Начнём же прикладную часть.
Для начала напишем функцию, которая будет определять - а была ли присвоена размерность динамическому массиву, или он бесплотен? Помнится, на форуме всплывал этот вопрос. Решения найдено не было, сошлись на том, что придётся отлавливать ошибку.
Принцип работы этой функции очень прост. Дело в том, что когда VB отводит память под адрес указателя на SAFEARRAY, то в последствии используется именно эта память и никакая другая - что бы вы ни делали с динамическим массивом, как бы вы его ни переопределяли или erase'или, - адрес указателя на SAFEARRAY есть величина постоянная (в то время как сама SAFEARRAY может скакать как угодно). Так вот, если массив не был определён, то адрес указателя содержит не адрес, а ноль. Ну а раз так, то:
- Код: Выделить всё
Function ArrayExists(arr() As Long) As Long
GetMem4 ArrPtr(arr), VarPtr(ArrayExists)
End Function
И всё!
Прямо даже не верится, что всё так просто. Проверим:
- Код: Выделить всё
Option Explicit
Private Sub Form_Load()
Dim a() As Long, p As Long
Me.Print "Массив пока не определён."
Me.Print "Функция вернула: " + IIf(ArrayExists(a), "массив существует", "массив не существует")
Me.Print
ReDim a(1 To 10)
Me.Print "Массив определён через ReDim."
Me.Print "Функция вернула: " + IIf(ArrayExists(a), "массив существует", "массив не существует")
Me.Print
Erase a
Me.Print "Массив стёрт через Erase."
Me.Print "Функция вернула: " + IIf(ArrayExists(a), "массив существует", "массив не существует")
End Sub
Обращаю внимание на одну досадную вещь. Не получится объявить функцию, принимающую массив любого типа. Поэтому придётся для каждого типа используемых данных писать свою функцию: ArrayExitsLong, ArrayExitsVariant, ArrayExitsMyUserType и т.д. Шаблонов-то нет у нас с вами, и перегрузки тоже Но будет меняться только название функции и тип параметра - тело функции остаётся неизменным.
Нихаласо? Хотим всё-таки универсальность? Ладно, но тогда функция будет
- Код: Выделить всё
Function ArrayExists(byval ppArr As Long) As Long
GetMem4 ppArr, VarPtr(ArrayExists)
End Function
А вызывать её тогда нужно не "flag = ArrayExists(arrName)", а "flag = ArrayExists(ArrPtr(arrName))".
Но я тут везде буду оперировать массивами Long, ладно?
Продолжим...
Займёмся теперь... ну, допустим... созданием массивов с общими данными. Сделаем так: есть как бы главный массив, и есть два других, которые в нём полностью содержатся. Интересно, зачем это нужно? Ну а вдруг пригодится (хотя сомневаюсь ).
Объявим ещё пару функций:
- Код: Выделить всё
Private Declare Sub SafeArrayAllocDescriptor Lib "oleaut32.dll" (ByVal cDims As Long, ppsaOut As Any)
Private Declare Sub SafeArrayDestroyDescriptor Lib "oleaut32.dll" (psa As Any)
Private arrMain() As Long, arr1() As Long, arr2() As Long
Private Sub Form_Load()
Dim i As Long
ReDim arrMain(1 To 10)
'arr1 будет ссылаться на данные главного массива с 1 по 5
CreateSAFEARRAY ArrPtr(arr1), 4, VarPtr(arrMain(1)), 1, 5
'arr2 будет ссылаться на данные главного массива с 6 по 10
'два последних параметра могут быть любыми - главное, чтобы
'расстояние между ними было во столько элементов, сколь нужно.
CreateSAFEARRAY ArrPtr(arr2), 4, VarPtr(arrMain(6)), 6, 10
'Заполняем только основной массив:
For i = 1 To 10
arrMain(i) = i
Next
Me.AutoRedraw = True
Me.Print "Основной массив:"
For i = 1 To 10
Me.Print arrMain(i)
Next
Me.Print
Me.Print "Маленький 1:"
For i = 1 To 5
Me.Print arr1(i)
Next
Me.Print
Me.Print "Маленький 2:"
For i = 6 To 10
Me.Print arr2(i)
Next
End Sub
Private Function CreateSAFEARRAY(ByVal ppBlankArr As Long, ByVal ElemSize As Long, ByVal pData As Long, ParamArray Bounds()) As Long
Dim p As Long, i As Long
'ParamArray Bounds - это описание размерностей массива:
'bounds(0) - нижняя граница первой размерности
'bounds(1) - верхняя граница первой размерности
'bounds(2) - нижняя граница второй размерности
'bounds(3) - верхняя граница второй размерности и т.д.
SafeArrayAllocDescriptor (UBound(Bounds) + 1) / 2, ByVal ppBlankArr
GetMem4 ppBlankArr, VarPtr(p)
PutMem4 p + 4, ElemSize
PutMem4 p + 12, pData
For i = 0 To UBound(Bounds) Step 2
PutMem4 p + 16 + i * 4, Bounds(i + 1) - Bounds(i) + 1
PutMem4 p + 20 + i * 4, Bounds(i)
Next
End Function
Private Function DestroySAFEARRAY(ByVal ppArray As Long) As Long
Dim p As Long
GetMem4 ppArray, VarPtr(p)
SafeArrayDestroyDescriptor ByVal p
PutMem4 ppArray, 0
End Function
Private Sub Form_Unload(Cancel As Integer)
DestroySAFEARRAY ArrPtr(arr1)
DestroySAFEARRAY ArrPtr(arr2)
End Sub
Обратите внимание - созданные нами массивы нами же и уничтожаются. Это потому, что они ссылаются на один и тот же участок памяти, а мы не хотим три раза уничтожать один и тот же участок, тем более что после первого уничтожения в нём может оказаться уже что-то не наше. Так что мы просто уничтожаем дескрипторы дочерних массивов, не трогая данные. Данные уничтожит сам VB, вместе с arrMain.
Может возникнуть вопрос - а откуда это такие циферки в PutMem4. Это следует из описания структуры SAFEARRAY - как же мы её-то не осветили ещё:
- Код: Выделить всё
Private Type SAFEARRAYBOUND
cElements As Long 'Количество элементов в размерности
lLBound As Long 'Нижняя граница размерности
End Type
Private Type SAFEARRAY
cDims As Integer 'Число размерностей
fFeatures As Integer 'Флаг, юзается функциями SafeArray
cbElements As Long 'Размер одного элемента в байтах
cLocks As Long 'Сколько раз массив был locked, но пока не unlocked.
pvData As Long 'Указатель на данные.
rgsabound As SAFEARRAYBOUND 'Повторяется для каждой размерности.
End Type
Так что p + 4 - это cbElements, p + 12 - pvData, ну, вы поняли
Кстати, само описание структуры нам вроде как и не нужно Разве что для справки - какой мембер по какому оффсету. Да и то - написали один раз процедуру CreateSAFEARRAY и можно забыть даже про эти оффсеты. А всё для чего? А чтобы CopyMemory зазря не дёргать каждый раз Шучу. На самом деле это потому, что структура имеет переменный размер - последний её член повторяется столько раз, сколько размерностей у массива. Подобная вольность в объявлении структур на VB не поощряется - нам придётся объявлять структурки для одной размерности, для двух, для трёх... А оно нам надо? Вот и даём размещение этой структуры на откуп функции SafeArrayAllocDescriptor.
Так, что у нас там ещё? Ага, межпроцессно-массивная коммуникация.
Как мы вообще это сделаем? Сделаем мы это через file mapping. File в винде - понятие растяжимое, и файлом на диске не ограничивается.
Принцип следующий. Запускается главный процесс. Он создаёт file mapping с помощью функции CreateFileMapping. Тут фишка в том, что если этой функции в качестве hFile передать &HFFFFFFFF, то будет создан file mapping на основе чистой оперативки, без создания нового файла на диске. После вызова этой функции у нас будет hFileMapping. Его мы передаём в функцию MapViewOfFile - она, собственно, и открывает доступ к участку памяти, зарезервированному через CreateFileMapping, возвращая при этом - ура и наконец-то - указатель на него. А подставить указатель на данные в структурку SAFEARRAY мы уже умеем, не так ли!
После этого запускается дочерний процесс. Он знает, какое имя должно быть у "нашего" file mapping (да, именованные они), а потому легко открывает его через OpenFileMapping. Через MapViewOfFile он получает указатель на данные - тот же самый! Пишет его в свою SAFEARRAY - и вот у нас две копии SAFEARRAY, принадлежащие разным процессам и ссылающиеся на общий кусок памяти! Установка значений таких-то элементов массива в одном процессе сразу же и без всяких CopyMemory приводит к изменению данных другого процесса.
Встаёт только одна проблема - синхронизация. Ведь один из процессов (завершающийся первым) должен просто уничтожить свой дескриптор, а второй - ещё и данные. Но проблему синхронизации мы рассматривать тут не будем, а то растечёмся мыслию по древу. Просто закрывайте сначала дочерний процесс, а потом родительский. Проект прилагается.