SAFEARRAY Tricks

Разговоры на любые темы: вы можете обсудить здесь какой-либо сайт, найти единомышленников или просто пообщаться...
GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

SAFEARRAY Tricks

Сообщение GSerg » 07.04.2004 (Ср) 8:32

alibek сказал нуно, значит нуно :)

Я тут изложил некоторые аспекты работы с массивами из VB :)
Прошу:
  1. Заценить
  2. Высказаться


Как мы знаем, все массивы в 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 приводит к изменению данных другого процесса.
Встаёт только одна проблема - синхронизация. Ведь один из процессов (завершающийся первым) должен просто уничтожить свой дескриптор, а второй - ещё и данные. Но проблему синхронизации мы рассматривать тут не будем, а то растечёмся мыслию по древу. Просто закрывайте сначала дочерний процесс, а потом родительский. Проект прилагается.
Вложения
Передача массива.zip
Interprocess shared array
(16.95 Кб) Скачиваний: 141
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 07.04.2004 (Ср) 9:58

Бурные аплодисменты! В зал вековой славы VBStreets его :)

Насчет
a. Заценить

Круто. Особенно насчет GetMem/PutMem, т.к. если использование msvbvmxx для получения указателей (*Ptr) еще более-менее оговорено, то о GetMem/PutMem я раньше не слышал.

А насчет
b. Высказаться

Пока рано, надо еще переварить. Только у меня имеются некоторые опасения насчет того, что общая память у разных процессов - это дело в среде WinNT опасное :)
Lasciate ogni speranza, voi ch'entrate.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 07.04.2004 (Ср) 11:43

Спасибо :) :oops:

Что касается get/put - то это я через PE Browse, уж звиняйте :oops:
Что касается работы под NT - высказываю осторожную надежу, что работать должно, ибо file mapping вроде как для того и предназначен...
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 07.04.2004 (Ср) 16:41

Глюки, глюки, глюки :)
Разбираться некогда, домой уже пора, но при попытке ввести что-либо в текстбоксы дочернего процесса вылетает "объектная переменная не установлена" или что-то вроде. И при закрытии дочернего процесса (или его падения) основная программа этого не замечает.
Lasciate ogni speranza, voi ch'entrate.

Cyrax
Cyberninja
Cyberninja
Аватара пользователя
 
Сообщения: 891
Зарегистрирован: 25.04.2002 (Чт) 21:20
Откуда: Magnitogorsk, Russia

Сообщение Cyrax » 07.04.2004 (Ср) 17:47

Win2k Server
запукаем Sender.exe -> запускается Reciver.exe (наверное так и должно быть)
при по пытке что-либо сделать с текстовыми полями в Sender.exe, тот отваливается с сообщением об ошибке за номером 91 "Object variable or With block variable not set". при этом сам процесс остается в памяти. приходится убивать вручную...
------------------------------
до кода еще не дошел...
Ты это ему расскажи. Я уже пять болтов отвинтил, и конца не видно... (озадаченно) А это в какую сторону тянуть? Ну-ка... Ага, этот был лишний, этот вообще не отсюда, и этот... Точно, два болта.

Welcome to IRC

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 08.04.2004 (Чт) 8:41

Мдя...
Видно, что-то не то на ядре NT.
На всех доступных мне 98 работает прекрасно :(
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 08.04.2004 (Чт) 9:22

Да, явные глюки. Просмотрел frmSender, хоть убей, но ничего объектного в Text1_KeyPress нет, так же как и в Command1_Click.
По ходу, объектной переменной, которая не установлена, как раз Text1 и является, программа слетает при попытке обратиться к свойствам Text1. Видимо, не такой уж и безопасный этот SAFEARRAY :)
Lasciate ogni speranza, voi ch'entrate.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 08.04.2004 (Чт) 9:41

alibek, знаешь чего - а сделай маленький лог, в котором были бы значения, возвращаемые функциями createfilemapping и mapviewoffile. И после каждого - значение err.lastdllerror.
Ну нет у меня NT, а фиксить надо :) Думаю, дело всё-таки в описателе прав доступа...

А массив text1 я и не трогаю, он автоматический :roll:
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 08.04.2004 (Чт) 10:44

Это главная форма:
Sender писал(а):Form_Load => CreateFileMapping() = hFileMapping = 560
Form_Load => LastDLLError = 0
Form_Load => MapViewOfFile() = pDest = 47972352
Form_Load => LastDLLError = 0
CreateSAFEARRAY => SafeArrayAllocDescriptor = ppBlankArr = 1557044
Form_Load => OpenProcess() = hProcess = 0
Form_Load => LastDLLError = 5


Это подчиненная форма:
Receiver писал(а):Form_Load => OpenFileMapping() = hFileMapping = 0
Form_Load => OpenFileMapping() => LastDLLError = 2
Form_Load => MapViewOfFile() = pData = 0
Form_Load => MapViewOfFile() => LastDLLError = 6
Lasciate ogni speranza, voi ch'entrate.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 08.04.2004 (Чт) 14:54

Ха ха
Ха ха ха :)

Главный процесс не может открыть хэндл дочернего, а дочерний не может открыть file mapping.

Ха ха ха :(

Ладно, чего там в msdn повествуется...
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Vi
Постоялец
Постоялец
 
Сообщения: 739
Зарегистрирован: 25.01.2002 (Пт) 11:03
Откуда: Россия, Ижевск

Сообщение Vi » 09.04.2004 (Пт) 16:06

Есть поле fFeatures As Integer 'Флаг, юзается функциями SafeArray, которое может быть с успехом задействоваться, т.к. массив с FADF_AUTO=&H1 или FADF_STATIC=&H2 не удаляется, а FADF_FIXEDSIZE=&H10 - не переразмещается.

К тому же работать с вариантом, наверное, безопаснее.
Код: Выделить всё
  Dim v, code As VbVarType
 
  code = vbArray Or vbVariant ' vbLong etc
  CopyMemory v, code, Len(code)
  ' здесь v - это массив Variant() динамически не выделенный

Теперь сделаем так
Код: Выделить всё
  Dim i As Long
  Dim v, code As VbVarType, vptr As Long
 
  v = Array() ' Как известно, это Variant(0 To -1)
  CopyMemory vptr, ByVal VarPtr(v) + 8, 4
  ' здесь vptr - это указатель на SAFEARRAY, содержащийся в Варианте
 
  i = 0: CopyMemory i, ByVal vptr, 2:         Debug.Print Hex(i)
  i = 0: CopyMemory i, ByVal vptr + 2, 2:   Debug.Print Hex(i)
           CopyMemory i, ByVal vptr + 4, 4:   Debug.Print Hex(i)
           CopyMemory i, ByVal vptr + 8, 4:   Debug.Print Hex(i)
           CopyMemory i, ByVal vptr + 12, 4: Debug.Print Hex(i)
           CopyMemory i, ByVal vptr + 16, 4: Debug.Print Hex(i)
           CopyMemory i, ByVal vptr + 20, 4: Debug.Print Hex(i)

Будут
Код: Выделить всё
1          cDims As Integer     'Число размерностей
&H880   fFeatures As Integer 'Флаг, юзается функциями SafeArray
  FADF_HAVEVARTYPE= 0x0080;  /* array has a VT type */
  FADF_VARIANT    = 0x0800;  /* an array of VARIANTs *
16        cbElements As Long   'Размер одного элемента в байтах
0          cLocks As Long       'Сколько раз массив был locked
0=NULL pvData As Long              'Указатель на данные.
0        cElements As Long    'Количество элементов в размерности
0        lLBound As Long      'Нижняя граница размерности


Соотвественно можно заменить и хранить БЕЗОПАСНО статический массив, не заморачиваясь на удаление и прочие тонкости.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! (с) КВН

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 10.04.2004 (Сб) 8:36

В общем, всё ясно.

OpenProcess failed: access denied.
OpenFileMapping failed: object with specified name not found (читай access denied).

Каждый именованный объект (и некоторые неименованные) в NT имеет SECURITY_DESCRIPTOR, который я, естественно, проигнорировал :) Поэтому меня и посылают.
С примерами пытался разобраться, но там такая муть, ужас...

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

Fedorfx
Постоялец
Постоялец
 
Сообщения: 371
Зарегистрирован: 10.10.2002 (Чт) 0:14

Сообщение Fedorfx » 12.04.2004 (Пн) 1:36

Ребята, а я правильно предпологаю, что по данной методике, я смогу в классе создать псевдо публичный массив??? Ведь нельзя в классе сделать Public Array а тут получается обьяви его приватным, создай в другом месте любой массив, и перенаправь адресацию. И не надо никакой синхронизации. Я правильно мыслю????
P.S. Жду с нетерпением продолжения эксперемента.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 12.04.2004 (Пн) 8:44

Правильно мыслишь :)
Смотри в тексте статьи (хех, статьи, ну надо же :)) про создание двух маленьких массивов из большого. У тебя дочерний массив будет такого же размера, и будет он один.
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Vi
Постоялец
Постоялец
 
Сообщения: 739
Зарегистрирован: 25.01.2002 (Пт) 11:03
Откуда: Россия, Ижевск

Сообщение Vi » 12.04.2004 (Пн) 9:09

Про ArrayExists...
Обращаю внимание на одну досадную вещь. Не получится объявить функцию, принимающую массив любого типа. Поэтому придётся для каждого типа используемых данных писать свою функцию: ArrayExitsLong, ArrayExitsVariant, ArrayExitsMyUserType и т.д.

Кстати, эта функция не работает и не будет работать с массивом строк.
Код: Выделить всё
Function ArrayExists(arr() As String) As Long
  GetMem4 ArrPtr(arr), VarPtr(ArrayExists)
End Function

Эта функция всегда будет возвращать 0. Если точнее, не ноль, а произвольное число, т.к. она расположена на стеке или памяти, уже не используемых в данный момент.
Код: Выделить всё
Private Sub Form_Load()
  Dim a() As String, p As Long
 
  Debug.Print "Массив пока не определён."
  Debug.Print "Функция вернула: " + IIf(ArrayExists(a), "массив существует", "массив не существует")
  Debug.Print
 
  ReDim a(1 To 10)
 
  Debug.Print "Массив определён через ReDim."
  Debug.Print "Функция вернула: " + IIf(ArrayExists(a), "массив существует", "массив не существует")
  Debug.Print
 
  Erase a
 
  Debug.Print "Массив стёрт через Erase."
  Debug.Print "Функция вернула: " + IIf(ArrayExists(a), "массив существует", "массив не существует")

End Sub
Последний раз редактировалось Vi 12.04.2004 (Пн) 9:17, всего редактировалось 1 раз.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! (с) КВН

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 12.04.2004 (Пн) 9:16

Ценное замечание! :?
Интересно, почему...
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Vi
Постоялец
Постоялец
 
Сообщения: 739
Зарегистрирован: 25.01.2002 (Пт) 11:03
Откуда: Россия, Ижевск

Сообщение Vi » 12.04.2004 (Пн) 9:51

GSerg писал(а):Ценное замечание! :?
Интересно, почему...

Потому что при передаче строк (отдельно или в составе массива) в функцию (не в СОМовский метод, а именно функцию в DLL) происходит преобразование. Вот как это описано в MSDN Chapter 6: Strings.

As we have seen, VB uses Unicode internally; that is, BSTRs use the Unicode format. Windows NT also uses Unicode as its native character code. However, Windows 9x does not support Unicode (with some exceptions). Let's examine the path that is taken by a BSTR argument to an external DLL function (Win32 API or otherwise).

In an effort to be compatible with Windows 95, VB always (even when running under Windows NT) creates an ABSTR, converts the BSTR's Unicode character array to ANSI, and places the converted characters in the ABSTR's character array. VB then passes the ABSTR to the external function. As we will see, this is true even when calling the Unicode entry points under Windows NT.

Или по-русски.
Как мы видели, VB использует Unicode внутренне, т. е. BSTR-ы используют формат Unicode. Windows NT также использует Unicode. Однако, Windows 9x не поддерживают Unicode (с некоторыми исключениями). Давайте исследовать путь прохождения BSTR аргумента в функцию внешней DLL (Win32 API или иначе).

Чтобы быть совместимым с Windows 95, VB всегда (даже под Windows NT) создает ABSTR, конвертирует Unicode строку к ANSI строке и размещает ее в ABSTR строке. Далее VB передает ABSTR внешней функции. Как мы будем видеть, это истинно даже при запросе Unicode функций под Windows NT.


GetMem4 ArrPtr(arr), VarPtr(ArrayExists)

Т.о. при вызове ArrPtr массив arr будет преобразован в новый массив, который будет передан в функцию и после вызова будет разрушен. Поэтому GetMem4 уже будет работать с неверным указателем.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! (с) КВН

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 12.04.2004 (Пн) 9:56

:evil:
И ведь ничего не сделаешь...
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Fedorfx
Постоялец
Постоялец
 
Сообщения: 371
Зарегистрирован: 10.10.2002 (Чт) 0:14

Сообщение Fedorfx » 12.04.2004 (Пн) 17:29

Всем привет. Сорри что пишу в эту ветку но думаю если мне и помогут то только здесь. У меня есть приложение для математических расчетов. Оно критично к времени исполнения некоторых функций. Я использую вызовы из DLL которые сам пишу на С++. Бьюсь с повышением производительности. Прошу посмотреть мой способ взаимодействия между VB and C++.

Файл DEF

Код: Выделить всё
LIBRARY 1DLL
EXPORTS
Max_Min         @1


Файл 1.С

Код: Выделить всё
int _stdcall Max_Min(float data_max[],float data_min[],int step,float* max,float* min,int fust_d,int end_d)
{
   int i;
   if (step==1)
   {
         *max=data_max[fust_d];
         *min=data_min[fust_d];
      for (i = fust_d+1; i <= end_d; i++)
         if (data_max[i]>*max) *max=data_max[i];
         else if (data_max[i]<*min)   *min=data_max[i];
   }
   else
   {
         *max=data_max[fust_d];
         *min=data_min[fust_d];
      for (i = fust_d+1; i <= end_d; i++)
      if (data_max[i]>*max) *max=data_max[i];
         else if (data_min[i]<*min)   *min=data_min[i];
   };
return 1;
}


Вызов из VB
Код: Выделить всё
Declare Function Max_Min Lib "c:\sati\ocx_dll\1.dll" (ByRef data_max As Any, ByRef data_min As Any, ByVal skolco_massivov As Long, ByRef max As Single, ByRef min As Single, ByVal fust_d As Long, ByVal end_d As Long) As Long

Данная функция ищет максимальное значение в первом массиве и минимальное во втором.
Обратите внимание что мне приходится передавать указатели на переменные, которые я предварительно обьявил в своей программе на VB.
1-Меня смущает тот факт что все массивы в VB SAFEARRAY а я обьявляю их в функции как Double. Это правильно или нет и что мне надо сделать для повышения производительности??
2- Так же мне кажется что если я создам массив Dim dd ( 10 to 100) и передам егом его то в c++ размерность массива все рано будет начинаться с 0??? ( т.е. если мне надо найти макс между 20 и 30 а массив размерности 10-40 то С++ будет искать не там где надо.)

3- Не хочется постоянно передавать аргументами функции всякую дополнительную информацию. Есть ли способы из DLL получать данные из моей проги на VB?? ( типа окно в вызывающую прогу)
4- Есть ли у кого примеры DLL в которых есть использование классов и написанных ( обьявленных) в VB и обрабатываемых в C++)

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 13.04.2004 (Вт) 12:28

1. Да, правильно. Ты передаёшь указатель не на сам SAFEARRAY, а на начало данных (то есть, pvData собственно).
2. Именно так, с нуля.
3. А придётся. Либо передай в библу указатель на какой-нибудь класс из твоей проги, и работай с ним (он суть IDispatch).
4. У меня нет :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Cyrax
Cyberninja
Cyberninja
Аватара пользователя
 
Сообщения: 891
Зарегистрирован: 25.04.2002 (Чт) 21:20
Откуда: Magnitogorsk, Russia

Сообщение Cyrax » 15.04.2004 (Чт) 7:39

значит так... все учим мат часть... а именно: подошли к своей книжной полке, достали от туда Дана Эпплмана "Win32 API и Visual Basic" и читаем очень внимательно главы 13, 14 и 15...
у кого нет книжной полки - срочно в мебельный магазин
у кого нет этой книги - в книжный... тоже срочно
исходники к этим главам, если надо, выложу в этом посте
Ты это ему расскажи. Я уже пять болтов отвинтил, и конца не видно... (озадаченно) А это в какую сторону тянуть? Ну-ка... Ага, этот был лишний, этот вообще не отсюда, и этот... Точно, два болта.

Welcome to IRC

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 15.04.2004 (Чт) 9:15

Значит так...
Кончаем понтоваться, тоже срочно...
И свободным, простым разговорным русским языком рассказываем общественности те части матчасти, которые она (общественность) не сможет учить ввиду отсутствия книжного и мебельного магазина...
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 15.04.2004 (Чт) 9:30

Глава 13, видимо имелось ввиду файл-маппинг?
File Mappings (Is It File, or Is It Memory?)

There is really no difference between a file and memory. Ah, I know what you’re thinking: Surely such a statement is the product of a hallucination. We all know that these are two different things.
A file represents a block of data on disk that you access using file operations. First you open a file, then you use special functions to read and write data to the file, then you close the file when you are done with it. Memory corresponds to physical memory in your computer. In languages such as C++ you can access memory directly using pointers. In Visual Basic, you need to use DLL functions that can copy blocks of memory from Visual Basic objects to and from a specified address in memory. Memory blocks are allocated differently from files, they are accessed differently, and the data is stored differently. They are not the same. There is a difference.

It is true that once up on a time (say, a few months ago), there was quite a large difference between files and memory—but the lines had become increasingly blurred. For example, Windows has long supported virtual memory in which your system has access to more memory than is physically present in your system. Windows accomplishes this by allocating a paging file on disk. When you try to access an address in memory that is not physically present in your system, an error called a “
page fault” occurs. The system loads the page of memory from the system paging file. If necessary, it frees space in physical memory by writing one of the other memory pages to the system paging file.

So when you access memory on a Windows system, you can’t really be sure if the data you are looking at is actually in memory or on disk—nor would you care in most cases.
But virtual memory has been around for a long time, and certainly is not enough to support a claim that files and memory are the same things. You still can’t access files using memory pointers, right?
Well actually, under Win32, you can. Consider again the concept of virtual memory. Virtual memory works by giving the system an address space that is greater than the available physical memory. Under Win32, the address space for each application is 2 gigabytes, even though it is unlikely that your typical desktop system will support that much physical memory for at least another few years. What could be the point of having such a large address space? Consider how it might be used: Portions of that 2GB address space might be mapped to physical memory, and parts to the system paging file. Since the operating system already knows how to map part of the address space to one file, how difficult would it be to map other portions of that address space to any arbitrary disk file? As it turns out, it’s not hard at all. Under Win32 this is accomplished using an object called a memory mapped file, or “file mapping.” A file mapping object can take a disk file and make it appear as if it was mapped into your application’s address space. You can then read and optionally modify the contents of the file as easily as you would modify a block of memory. Figure 13.2 illustrates how files are mapped into your address space. (Purists will note that this figure is a vast oversimplification of the way Win32 actually manages memory—
the point here is to communicate the concept of a file mapping, not to provide an in-depth look at the actual implementation of the Win32 memory management system under a particular operating system, a subject that will be covered further in Chapter 15).

Why Memory Mapped Files Are Important

You may be wondering why memory mapped files are important to Visual Basic programmers. At first glance, it seems to relate to lower level aspects of the operating system that are not easily used from Visual Basic. After all, it is still difficult to access memory directly using Visual Basic so there really is no benefit to accessing files as memory instead of using file operations. Or is there?
Take a look at the compile tab of the project-options dialog box for an ActiveX DLL project.You will see an option that lets you set the DLL base address. The Visual Basic online help explains that assigning a unique base address can speed the load time for your DLL. Why is this? Because Win32 uses a new executable format that takes advantage of the file mapping capabilities built into the operating system. Under Win16, when you loaded a DLL the operating system would open the file, load whatever segments are necessary for the program to start running into memory, then fix up any pointers in the program so that they would be correct for the address at which the program was actually loaded (a process called relocation). Only after all of these steps were complete would code in the newly loaded DLL actually begin executing.

Under Win32, the operating system looks at the file to see if it specified a preferred loading address. If that address is not currently in use by another DLL, the system simply maps the file into your address space and starts executing it. No time-consuming relocation, no need to preload large parts of the DLL—the operating system will load additional pages of memory as necessary. If the address requested by the DLL is already in use by another DLL or application, relocation is necessary, but the process is still relatively fast and file mapping is still used to map the DLL into the memory address space of your application. This process is also used to load executable files, though it is not as critical there since each process tends to have only a single executable file.

Why Memory Mapped Files Are Really Important

The fact that Win32 uses file mappings to load DLLs and executables is a nice tidbit of knowledge, but there is a far more important use for memory mapped files.
You see, memory mapped files provide the most efficient way to share large amounts of data between Win32 applications. If you’ve been using global memory to transfer blocks of memory between Win16 applications, you may have found that the technique no longer works with 32-bit Visual Basic. This is because these global memory blocks are no longer shared. You see, when we say that each application has a 2GB address space, it truly means that each application has its own 2GB address space. Memory allocated in one application’s address space simply does not exist in the address space of any other application. (Note: This explanation is a bit of a simplification as well, as there are some code and memory objects which appear in the same address space for all applications—especially under Windows 95 which does not provide the high level of security offered by Windows NT).

Under Win32 you share memory by creating a file mapping object. The handle to that object can be duplicated and passed to another application in order to share the underlying file, or you can take the easier approach of assigning the file mapping object a name. This allows other applications to access the object using the unique object name. Once you have a handle to a file mapping object, you can map the object into your application’s address space, making the underlying file appear to the program as a block of memory. If multiple applications are accessing the same file mapping object, Win32 makes sure that the object remains coherent—that the contents of the object are identical for all of the applications (though this does not apply if you have opened the object remotely across a network or if you mix memory access to the file mapping object with file commands on the underlying disk file).

You may be wondering if this scheme is not, in a sense, a step backward—after all, one of the advantages of sharing global memory blocks was that they provided excellent performance when compared to writing and reading a disk file. Fear not, the Win32 designers took this into account as well. You see, a file mapping object need not actually reference a disk file—
you can create a file mapping object that references a block of memory. This effectively lets you share a memory block among applications. There’s just one catch though—you can’t count on the block of memory being at the same address for each process. (Though they will be the same under Windows 95, they are often different under Windows NT.) This means that each process must keep track individually of where in memory a particular file mapping object appears.

The functions used for file mappings are shown in Table 13.8.

Table 13.8 File Mapping Functions

Function Description
CreateFileMapping Creates a file mapping object
FlushViewOfFile Ensures that all changes to a file mapping have been written
MapViewOfFile, MapViewOfFileEx Maps a file mapping object into the address space of the current process
OpenFileMapping Opens an existing file mapping object
UnmapViewOfFile Unmaps a file mapping object from the address space of the current process


Глава 14 видимо имелось ввиду Interprocess Communication?
В принципе, это не относится к данному вопросу. Там говориться что для передачи данных между процессами используется File Mapping, кроме того в Win32 можно использовать майлслоты и пайпы.

А из 15 главы очевидно имелось ввиду работа со стрингами.
Using Visual Basic to Create Buffers

There are two easy ways to obtain memory buffers using Visual Basic. One is to use a string, the other is to use a byte array. A string buffer of a specified length can be created in two ways: first, by creating a fixed length string using


Dim FixedSample As String * N
where N is the length of the string and second, by using the Visual Basic String$ function on a variable-length string variable as follows:
Dim VarSample As String
VarSample = String$(N,0)
where N is the number of bytes in the string.


The API functions that require pointers to buffers expect 32-bit address parameters. Under Win16 it was possible to obtain the address of a string using any popular Visual Basic support DLL, including the APIGUIDE.BAS dynamic link library that was provided with the Visual Basic Programmer’s Guide to the Windows API. You could then pass the address as a long (ByVal) to the function that needed a memory buffer. The only caveat was that you could not hold onto the address for long periods of time because of Visual Basic’s tendency to relocate strings in memory as needed. However, as long as you used the address immediately after calling the function to obtain the address, this was a safe approach.

This approach does not work with 32-bit Visual Basic.
The reason for this is simple and goes back to the discussion in Chapter 3. Visual Basic stores strings internally as Unicode strings. When you call a DLL function, VB makes a temporary ANSI copy of the string. When the function returns, it converts this temporary ANSI string back into Unicode. The problem? The DLL function receives the address of the ANSI string, which is the address of a temporary buffer. As soon as the function returns, the buffer is no longer valid. Thus, any function that obtains the address of a string by passing the string to a DLL will not return a useful value.

There are several approaches that you can take to solve this problem. The first is to simply change the declaration of the function that requires a buffer to “ByVal As String” and pass it the string buffer. This will work in many cases—especially when the buffer is designed to hold text, but don’t forget that Visual Basic will be converting the data to and from Unicode. If the buffer is intended to hold binary data, this conversion could corrupt the information in the buffer.

Another approach is to use a byte array. You can obtain the address of a byte array in memory by using the agGetAddressForObject function in APIGID32.DLL and passing it the first element in the byte array by reference as follows:


arrayaddress& = agGetAddressForObject(myarray(Ø))


Note that byte arrays—like all VB arrays—start at index zero. You can, of course, pass the address of the second byte (in this case, myarray(1)) and everything will work properly as long as you remain consistent and don’t try to perform a conversion of the array to a string. Byte arrays are safe from conversions and provide a very easy way to obtain memory buffers. If a DLL or API function loads a string into a byte buffer, you can convert it to a string using a simple assignment, however you will need to perform an explicit conversion to Unicode in that case.

Finally, you can allocate your own global memory buffers using API functions and pass addresses to those buffers to the function using long parameters (ByVal As Long).


Если есть желающие, могу выложить Help-файл, он хоть и устаревший, но полезного в нем есть.
Lasciate ogni speranza, voi ch'entrate.

Cyrax
Cyberninja
Cyberninja
Аватара пользователя
 
Сообщения: 891
Зарегистрирован: 25.04.2002 (Чт) 21:20
Откуда: Magnitogorsk, Russia

Сообщение Cyrax » 15.04.2004 (Чт) 10:33

alibek, ты совершенно прав...
Вложения
filemapping.zip
небольшая выдержка из 13 главы
в формате MS Word
(348.27 Кб) Скачиваний: 129
Ты это ему расскажи. Я уже пять болтов отвинтил, и конца не видно... (озадаченно) А это в какую сторону тянуть? Ну-ка... Ага, этот был лишний, этот вообще не отсюда, и этот... Точно, два болта.

Welcome to IRC

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 15.04.2004 (Чт) 10:38

Ну так всё это мы знаем.
А облом именно в том, что нельзя получить SAFEARRAY** стрингового массива.
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Cyrax
Cyberninja
Cyberninja
Аватара пользователя
 
Сообщения: 891
Зарегистрирован: 25.04.2002 (Чт) 21:20
Откуда: Magnitogorsk, Russia

Сообщение Cyrax » 15.04.2004 (Чт) 12:10

я понял... я понял почему у тебя оно не работает, а у Эпплмана работает...
Вложения
ch13_samples.zip
примеры к 13 главе
(66.96 Кб) Скачиваний: 110
ckServe_ckClient.zip
а это небольшие коментарии к интересующим нас примерам
(266.03 Кб) Скачиваний: 106
Ты это ему расскажи. Я уже пять болтов отвинтил, и конца не видно... (озадаченно) А это в какую сторону тянуть? Ну-ка... Ага, этот был лишний, этот вообще не отсюда, и этот... Точно, два болта.

Welcome to IRC

Fedorfx
Постоялец
Постоялец
 
Сообщения: 371
Зарегистрирован: 10.10.2002 (Чт) 0:14

Сообщение Fedorfx » 15.04.2004 (Чт) 16:08

А нам раскажете что поняли???
Туговато с английским :-((

Cyrax
Cyberninja
Cyberninja
Аватара пользователя
 
Сообщения: 891
Зарегистрирован: 25.04.2002 (Чт) 21:20
Откуда: Magnitogorsk, Russia

Сообщение Cyrax » 15.04.2004 (Чт) 16:45

Fedorfx писал(а):А нам раскажете что поняли???
Туговато с английским :-((

дык в приложениях к моим постам как раз на русском выдержки из книги... Дан Эпплман "Win32 API и Visual Basic"
ну нету у меня пока возможности всю ее сюда выложить, да и сложно это сделать...
а заказать ее можно, скажем, на Озоне или в Питер-Пресс. я именно так ее и приобрел
Ты это ему расскажи. Я уже пять болтов отвинтил, и конца не видно... (озадаченно) А это в какую сторону тянуть? Ну-ка... Ага, этот был лишний, этот вообще не отсюда, и этот... Точно, два болта.

Welcome to IRC

Fedorfx
Постоялец
Постоялец
 
Сообщения: 371
Зарегистрирован: 10.10.2002 (Чт) 0:14

Сообщение Fedorfx » 15.04.2004 (Чт) 23:59

Судя по всему знатная книга. Обязательно ее приобрету.
Но все же, нельза выложить пример, в котором будет передача стринговых массивов между приложениями под НТ ( я так понимаю XP на ядре NT построена) а я уже 5 лет как 98 выкинул на помойку.

Fedorfx
Постоялец
Постоялец
 
Сообщения: 371
Зарегистрирован: 10.10.2002 (Чт) 0:14

Сообщение Fedorfx » 22.04.2004 (Чт) 7:45

Ребята подскажите плиз где я на грабельки ступаю.

Код: Выделить всё

  Dim Adr as long
  Dim r1(1 To 10) as long
  Dim r2(1 To 1) as long

  r1(1) = 33
  GetMem4 ArrPtr(r1), ArrPtr(r2)
' До сих пор все ок
' Пытаюсь удалить массив r1
  GetMem4 r2, VarPtr(Adr)
  SafeArrayDestroyDescriptor ByVal Adr
' Вот после этой строчки глюки ( массив становится интом
  PutMem4 12, 0


Где что нетак???

След.

Вернуться в Народный треп

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 54

    TopList