Как устроить такой фокус: строка как байтовый массив
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
| ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
| [216.73.216.23] |
|
|
Правила раздела Visual Basic: Общие вопросы
FAQ Сайта
FAQ Раздела
Кладовка
Наши Исходники
API-Guide
Поиск по Разделу
MSDN Library Online
Google
Как устроить такой фокус: строка как байтовый массив
|
Сообщ.
#1
,
|
|
|
|
Возможно ли, и как, если возможно, проделать такой фокус: чтобы некая переменная-строка и некая переменная - массив байтов ссылались на один и тот же участок памяти? Чтобы изменение байтового массива было бы одновременно и изменением строки?
На сколько я понимаю, такое должно получиться, если та и другая переменная будут пользоваться одним и тем же указателем... Я правильно понимаю? |
|
Сообщ.
#2
,
|
|
|
|
Неужели на VBS не ответили?
|
|
Сообщ.
#3
,
|
|
|
|
B.V.
Там направили. Правда, как раз туда, откуда у меня сама мысль появилась. А чё, если в одном месте спросил, в другом уже нельзя спрашивать? |
|
Сообщ.
#4
,
|
|
|
|
Фокус:
![]() ![]() Option Explicit Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (ByRef pArr() As Any) As Long Private Declare Sub GetMem4 Lib "msvbvm60" (ByVal pSrc As Long, ByVal pDst As Long) Private Sub Form_Load() Dim bArr(0 To 23) As Byte Dim strFoo As String Dim pSA As Long strFoo = "some text" GetMem4 ArrPtr(bArr), VarPtr(pSA) Dim pStr As Long pStr = StrPtr(strFoo) GetMem4 VarPtr(pStr), (pSA + 12) bArr(2) = Asc("n") MsgBox strFoo End Sub Аплодисменты... |
|
Сообщ.
#5
,
|
|
|
|
Бинго! Здорово!!!
А как это работает, не расскажешь? Секрет фонкуса, так сказать? Что такое 12? какой-то сдвиг в три лонга? Переписал в процедуру, взгляни, всё правильно: ![]() ![]() Public Sub stringIsBytes(s As String, b() As Byte) ReDim b(LenB(s) - 1) As Byte Dim pSA As Long GetMem4 ArrPtr(b), VarPtr(pSA) Dim pStr As Long pStr = StrPtr(s) GetMem4 VarPtr(pStr), (pSA + 12) End Sub Надо ли как-то по-особенному обращаться с полученным массивом (как-нибудь не просто так удалять или ещё чего)? |
|
Сообщ.
#6
,
|
|
|
|
Цитата Артур @ А как это работает, не расскажешь? Все уже рассказано в тех статьях, к которым тебя направили. Если коротко -- из указателя на указатель на SAFEARRAY я получаю указатель на SAFEARRAY, затем просто копирую в SAFEARRAY::pvData указатель на строку. Цитата Артур @ Что такое 12? Это смещение, по которому находится pvData (указатель на данные). Единственное, что не помешало бы проверить -- чему равен pvData сразу после ReDim. Если он указывает на данные, то их, наверно, надо как-то освобождать перед подменой указателя (SafeArrayDestroyData?). |
|
Сообщ.
#7
,
|
|
|
|
B.V.
Спасибо! Даже уже почти понял. А строки устроены не так, как байтовые массивы? в смысле, у них нет SAFEARRAY? Насчёт редимов методом тыка выяснил следующее: если обнулить или переписать строку, связь прерывается (как мне объяснили, под строку выделяется новый адрес и новый участок памяти). Но данные в b() не пропадают. Интересно, почему - ведь если обычную строку обнулить, данные в ней вроде как должны очиститься? Если редимить b(), то связь сохраняется, но строка не увеличивается (это вроде бы понятно), но и не уменьшается - получается, данные опять же не очищаются? (есть подозрение, что они просто помечаются как неиспользуемые и через какое-то время в них может оказаться что-то совсем левое?) SafeArrayDestroyData попробовал прикрутить так: ![]() ![]() Public Sub stringIsBytes(s As String, b() As Byte) ReDim b(LenB(s) - 1) As Byte Dim psa As Long GetMem4 ArrPtr(b), VarPtr(psa) SafeArrayDestroyData ByVal psa Dim pStr As Long pStr = StrPtr(s) GetMem4 VarPtr(pStr), (psa + 12) End Sub Правильно? Добавлено Всё-таки, мне кажется, что если уж очищать данные массива, то перед редимом, а не после. Может, вот так: ![]() ![]() Public Sub stringIsBytes(s As String, b() As Byte) Dim psa As Long GetMem4 ArrPtr(b), VarPtr(psa) If psa Then SafeArrayDestroyData ByVal psa ReDim b(LenB(s) - 1) As Byte Else ReDim b(LenB(s) - 1) As Byte GetMem4 ArrPtr(b), VarPtr(psa) End If Dim pStr As Long pStr = StrPtr(s) GetMem4 VarPtr(pStr), (psa + 12) End Sub |
|
Сообщ.
#8
,
|
|
|
|
Цитата Артур @ А строки устроены не так, как байтовые массивы? в смысле, у них нет SAFEARRAY? [Общее] Устройство некоторых типов данны Цитата Артур @ Но данные в b() не пропадают. Интересно, почему Вероятно, потому, что память не затирается при удалении, а просто помечается как свободная. Цитата Артур @ есть подозрение, что они просто помечаются как неиспользуемые и через какое-то время в них может оказаться что-то совсем левое Весьма вероятно ![]() Цитата Артур @ SafeArrayDestroyData попробовал прикрутить так: Я SafeArrayDestroyData предложил просто как вариант. Я не знаю, стоит (и можно) ли её тут использовать или нет. |
|
Сообщ.
#9
,
|
|
|
|
Нет народ, то что вы намутили с SafeArrayDestroyData - глупость.
Нужно вместо redim использовать Erase (для очистки от старых данных если они были) а затем ручками выставлять все поля структуры. |
|
Сообщ.
#10
,
|
|
|
|
ANDLL
То есть, можно вот так: ![]() ![]() Public Sub stringIsBytes(s As String, b() As Byte) Erase b ReDim b(LenB(s) - 1) As Byte Dim psa As Long GetMem4 ArrPtr(b), VarPtr(psa) Dim pStr As Long pStr = StrPtr(s) GetMem4 VarPtr(pStr), (psa + 12) End Sub |
|
Сообщ.
#11
,
|
|
|
|
Нет, именно так и нельзя.
Я же говорю, инициализировать поля структуры лучше без Redim, ручками. |
|
Сообщ.
#12
,
|
|
|
|
ANDLL
Ну так раскрой мысль. И почему нельзя - чем черевато? |
|
Сообщ.
#13
,
|
|
|
|
Ну видимо тем что после каждой такой итерации память куском в LenB(s) - 1 окажется неприкаянной.
|
|
Сообщ.
#14
,
|
|
|
|
ANDLL
Но ведь это та же самая память, которая в строке - она не освободится вместе со строкой? |
|
Сообщ.
#15
,
|
|
|
|
Нет, я про ту память которую выделил Redim
|
|
Сообщ.
#16
,
|
|
|
|
Кажется, я понял. После редима мы выделяем под массив LenB(s) - 1 байт. После подмены указателя наш массив ссылается уже на другие данные, а те, что мы самы выделили, остаются где-то в пустоте. Правильно понял?
Значит, LenB(s) нужно записать в SAFEARRAY.rgsabound.cElements? так что ли? Добавлено Написал так: ![]() ![]() Public Sub stringIsBytes(s As String, b() As Byte) Erase b ' ReDim b(LenB(s) - 1) As Byte ReDim b(0) As Byte Dim pSa As Long GetMem4 ArrPtr(b), VarPtr(pSa) Dim pStr As Long pStr = StrPtr(s) GetMem4 VarPtr(pStr), (pSa + 12) PutMem4 pSa + 16, LenB(s) End Sub Вроде, работает. Не редимить совсем не получилось - у пустого массива pSa=0 |
|
Сообщ.
#17
,
|
|
|
|
Цитата Артур @ а те, что мы самы выделили, остаются где-то в пустоте. Правильно понял? Правильно Добавлено Цитата Артур @ Значит, LenB(s) нужно записать в SAFEARRAY.rgsabound.cElements? так что ли? Вот тут не уверен. Не уверен, что SafeArrayDestroyData затирает это поле. Цитата Артур @ Вроде, работает. Не редимить совсем не получилось - у пустого массива pSa=0 Надо объявить структуру SAFEARRAY, выделить под неё память, присвоить указатель, ручками проинициализировать... Слушай, а оно тебе надо? |
|
Сообщ.
#18
,
|
|
|
|
B.V.
Мне кажется, что после redim b(0) все поля, кроме длинны массива, уже нормально проинициализованы. Длину я могу вписать PutMem4 pSa + 16, LenB(s) А один неприкаяный байт прога как-нибудь переживёт. А может, вообще не редимить, если хоть однажды проредимлен? Тогда ведь неприкаяный байт возникнет только при первом обращении к функции. Просто массив нигде снаружи не редимить, а управление памятью оставить за ведущей строкой. Получится по однму неприкаяному байту на каждый массив. ![]() ![]() Public Sub stringIsBytes(s As String, b() As Byte) Dim psa As Long GetMem4 ArrPtr(b), VarPtr(psa) 'в pSa получить адрес SAFEARRAY If psa = 0 Then 'массив ещё не проредимлен ReDim b(0) As Byte GetMem4 ArrPtr(b), VarPtr(psa) End If Dim pStr As Long pStr = StrPtr(s) ' адрес данных строки GetMem4 VarPtr(pStr), (psa + 12) 'подмена данных массива данными строки PutMem4 psa + 16, LenB(s) 'записать длинну массива в SAFEARRAY.rgsabound.cElements End Sub А чтобы бейсик сам не уничтожал массивы, делать так: ![]() ![]() Public Sub DestroySAFEARRAY(b() As Byte) Dim pSa As Long, pAr As Long pAr = ArrPtr(b) GetMem4 pAr, VarPtr(pSa) If pSa Then SafeArrayDestroyDescriptor ByVal pSa PutMem4 pAr, 0 End If End Sub А до того, как байт станет неприкаеным, мы ведь можем узнать, где он лежит - может его можно освободить как-нибудь вручную? |
|
Сообщ.
#19
,
|
|
|
|
Цитата Артур @ один неприкаяный байт прога как-нибудь переживёт Это уже моветон. Утечек допускать нельзя. Еще раз по поводу всей этой затеи с массивом и строкой... если SafeArrayDestroyData освобождает только данные, не изменяя остальные поля структуры, то делай себе ReDim на здоровье (разумеется, перед вызовом SafeArrayDestroyData). Если же SafeArrayDestroyData все-таки изменяет остальные поля -- то Цитата B.V. @ Надо объявить структуру SAFEARRAY, выделить под неё память, присвоить указатель, ручками проинициализировать |
|
Сообщ.
#20
,
|
|
|
|
B.V.
Вроде бы, как раз изменяет - после неё массив пуст. Ну а если самому заполнить структуру - как её потом пристегнуть к массиву-то? А может всё же можно как-нибудь вручную освободить этот единственный байт - ведь его адрес известен? Как вариант борьбы с моветоном: может, запоминать адрес данных массива после redim b(0) и до подмены данных? А затем вместо уничтожения возврощать b исходное состояние. Тогда ведь он сам уничтожится без всякой утечки? |
|
Сообщ.
#21
,
|
|
|
|
Цитата Артур @ Ну а если самому заполнить структуру - как её потом пристегнуть к массиву-то? Гм. Так... ![]() ![]() GetMem4 pMySA, ArrPtr(bArr) Цитата Артур @ А может всё же можно как-нибудь вручную освободить этот единственный байт - ведь его адрес известен? Можно, конечно. Только надо знать, чем (CoTaskMemFree/LocalFree/HeapFree...). |
|
Сообщ.
#22
,
|
|
|
|
Цитата B.V. @ Можно, конечно. Только надо знать, чем (CoTaskMemFree/LocalFree/HeapFree...). А от чего это зависит? Цитата B.V. @ А эта пристёгнутая структура должна же будет где-то жить? Под неё же тоже нужно выделять память? а потом и удалять? и всё это во что-то запоминать?GetMem4 pMySA, ArrPtr(bArr) Цитата Артур @ Может, лучше этот способ? Или с ним что-нибудь не так? может, запоминать адрес данных массива после redim b(0) и до подмены данных? А затем вместо уничтожения возврощать b исходное состояние |
|
Сообщ.
#23
,
|
|
|
|
Цитата Артур @ А от чего это зависит? От того, чем выделялась память. Цитата Артур @ А эта пристёгнутая структура должна же будет где-то жить? Под неё же тоже нужно выделять память? а потом и удалять? и всё это во что-то запоминать? Ну разумеется. Цитата Артур @ Может, лучше этот способ? Можешь сделать так. |
|
Сообщ.
#24
,
|
|
|
|
В общем, родил такую вот штуку:
![]() ![]() Option Explicit Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (ByRef pArr() As Any) As Long Private Declare Sub GetMem4 Lib "msvbvm60" (ByVal pSrc As Long, ByVal pDst As Long) Public Declare Function PutMem4 Lib "msvbvm60" (ByVal pDst As Long, ByVal NewValue As Long) As Long '_____________________________________________________________________________________________________ Public Function stringIsBytes(s As String, b() As Byte) As Long 'функция будет возвращать адрес данных массива до подмены '(или ноль, если массив уже привязан к строке) Dim pSa As Long GetMem4 ArrPtr(b), VarPtr(pSa) 'в pSa получить адрес на SAFEARRAY массива If pSa = 0 Then 'массив ещё не проредимлен ReDim b(0) As Byte b(0) = 98 'чтобы перед уничтожением проверить, а вернулись ли данные на место. Потом закомментирую GetMem4 ArrPtr(b), VarPtr(pSa) Dim pOld As Long GetMem4 (pSa + 12), VarPtr(pOld) 'запоминаем данные по этому адресу (потому что мы их сейчас перепишем) stringIsBytes = pOld 'функция возвращает адрес старых данных End If Dim pStr As Long pStr = StrPtr(s) ' адрес данных строки GetMem4 VarPtr(pStr), (pSa + 12) 'подмена данных массива данными строки PutMem4 pSa + 16, LenB(s) 'записать длинну массива в SAFEARRAY.rgsabound.cElements End Function '_____________________________________________________________________________________________________ Public Sub DestroySAFEARRAY(b() As Byte, pOld As Long) Dim pSa As Long GetMem4 ArrPtr(b), VarPtr(pSa) 'получаем адрес SAFEARRAY If pSa Then GetMem4 VarPtr(pOld), (pSa + 12) ' записываем старые данные на место PutMem4 pSa + 16, 1 ' записываем старую длинну (у нас был там всего один меченый байт) Debug.Print b(0) 'проверяем, вернулись ли старые данные на место (должно быть 98) Erase b End If End Sub Специально пометил неприкаянный байт перед подменой, чтобы проверить, вернётся ли он на место перед уничтожением. Вернулся. Вроде бы, вопрос решён? |
|
Сообщ.
#25
,
|
|
|
|
Ну, если ты это называешь решением... то да.
|
|
Сообщ.
#26
,
|
|
|
|
B.V.
А по какому поводу сарказм? как было бы лучше? |
|
Сообщ.
#27
,
|
|
|
|
Цитата B.V. @ Надо объявить структуру SAFEARRAY, выделить под неё память, присвоить указатель, ручками проинициализировать И ничего не придётся запоминать, возвращать... |
|
Сообщ.
#28
,
|
|
|
|
Тогда по поводу чего было
Цитата Ну разумеется. Мой вариант может и корявый, но мне понятный. А вариант с объявлением SAFEARRAY я представляю довольно смутно, и он мне не то чтобы сильно понятен. Если разберусь, то сделаю... но пока не разобрался. допустим, я в функции объявляю sf as SAFEARRAY, заполняю поля и пристёгиваю её к массиву. Но после выхода из функции sf ведь умрёт и память, выделенная под неё, освободится? |
|
Сообщ.
#29
,
|
|
|
|
Цитата Артур @ допустим, я в функции объявляю sf as SAFEARRAY, заполняю поля и пристёгиваю её к массиву Эм. А ты правда читал те статьи, ну к которым тебя направили и с которых у тебя мысль появилась? Или ты дальше заголовка их не читал? http://www.vbstreets.ru/VB/Articles/65977.aspx, обрати внимание на функцию CreateSAFEARRAY. |
|
Сообщ.
#30
,
|
|
|
|
B.V.
Ты переоцениваешь мою способность понимать такие статьи. Читал, и уже не один раз (и даже, наверное, не десять). И с каждым разом всё больше понятно... Сейчас уже понимаю процентов 5. В основном, понятным становится то, что получается использовать. Как использовать CreateSAFEARRAY, мне не понятно. Ну тупой я. Кстати, массив всё рано нужно будет уничтожать самому - ну и какая тогда разница: так создавать и уничтожать или как у меня? Только в том, что с каждой строкой я храню один лонг? Ну ещё в том, что мой вариант не так изящен. Зато я сам до него додумался. Цитата В таком случае, к чему был этот совет? у Сергея структура не объявлена (просто описана), а заполняет он её теми же гетмем и путмем. Да ещё наводящим на меня ужас SafeArrayAllocDescriptorНадо объявить структуру SAFEARRAY Кстати, сейчас ещё раз перечитал эту CreateSAFEARRAY - она, оказывается, уже почти понятна Через годик уже смогу использовать. Добавлено Ну вот - и года не прошло... ![]() ![]() Option Explicit Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (ByRef pArr() As Any) As Long Private Declare Sub GetMem4 Lib "msvbvm60" (ByVal pSrc As Long, ByVal pDst As Long) Public Declare Function PutMem4 Lib "msvbvm60" (ByVal pDst As Long, ByVal NewValue As Long) As 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) Public Function stringIsBytes(s As String, b() As Byte) As Long Dim psa As Long, pAr As Long pAr = ArrPtr(b)'адрес массива GetMem4 pAr, VarPtr(psa) 'в pSa получить адрес на SAFEARRAY массива If psa = 0 Then 'массив ещё не проредимлен SafeArrayAllocDescriptor 1, ByVal pAr 'выделяем новый дескриптор массива и память под него GetMem4 pAr, VarPtr(psa) 'получаем дескриптор SAFEARRAY PutMem4 psa + 4, 1 'записываем размер элемента массива (массив байтовый, так что 1) End If Dim pStr As Long pStr = StrPtr(s) ' адрес данных строки GetMem4 VarPtr(pStr), (psa + 12) 'подмена данных массива данными строки PutMem4 psa + 16, LenB(s) 'записать длинну массива в SAFEARRAY.rgsabound.cElements End Function Public Sub DestroySAFEARRAY(b() As Byte) Dim psa As Long, pAr As Long pAr = ArrPtr(b) GetMem4 pAr, VarPtr(psa) If psa Then SafeArrayDestroyDescriptor ByVal psa PutMem4 pAr, 0 End If End Sub |
|
Сообщ.
#31
,
|
|
|
|
Цитата Артур @ В таком случае, к чему был этот совет? Ой, сам не знаю В общем, первые два пункта -- SafeArrayAllocDescriptor. |
|
Сообщ.
#32
,
|
|
|
|
В смысле SafeArrayAllocDescriptor это и объявление структцры и выделение памяти? Ну да, я уже догадался
![]() А то, как я переписал - правильно? Вроде, работает. |
|
Сообщ.
#33
,
|
|
|
|
Цитата Артур @ А то, как я переписал - правильно? Вроде как да. Только ты оставил непроинициализированными cDims и lLbound. |
|
Сообщ.
#34
,
|
|
|
|
B.V.
Ну так lLbound ведь ноль - какой смысл инициировать? А cDims, вроде бы, заполняется самой SafeArrayAllocDescriptor - у меня размерность одна, так что я сразу 1 и записал |
|
Сообщ.
#35
,
|
|
|
|
Цитата Артур @ какой смысл инициировать? Что бы было совсем красиво. Это не обязательно. |
|
Сообщ.
#36
,
|
|
|
|
Я так полагаю, что теперь-то точно - вопрос решён? Жму кнопку
Добавлено Кстати, в комментариях к статье предложен как раз подход без функций SafeArrayЛяляля - там предлагается рядом с объявлением массива объявлять и его SAFEARRAY, а размерности (которые могут быть разными) как-то хитро хранить в строках (я недопонял). Так мой способ с сохранением адреса старых данных, мне кажется, тоже не так уж и плох. Тем более, что заморачиваться по поводу полей стуктуры вообще не пришлось бы, а один лонг, лежащий рядом с массивом - это даже меньше, чем одна SAFEARRAY, лежащая там же. |