Об указателях в VB6

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

Модератор: Хакер

uni
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 105
Зарегистрирован: 05.05.2006 (Пт) 15:24
Откуда: Екатеринбург

Re: Об указателях в VB6

Сообщение uni » 03.01.2011 (Пн) 14:56

Ха, это работает! Вот как это теперь делаю я. Про то, что Optional можно не использовать я догадался и сам, но представив код со стороны пользователя... передумал это использовать. До функции-посредника я не дотумкал:

Код: Выделить всё
Private Sub func_ShowPropertyTableForFill(frm As FormMain, _
    ByVal Stub As Long, _
    ByRef RecordTitle As TYPE_WPC_TITLE, _
    ByRef RecordFill As TYPE_WPC_FILL)

    Dim I, J, row As Integer
    Dim ParamStr, s As String
    Dim StepPointer As Long

    With frm
        ' Формируем столбик с названиями параметров
        ParamStr = ";Параметр|"
       
' /* Вырезано */
       
        ' Эта строка автоматически изменяет количество строк
        .PropertyTable.FormatString = ParamStr
       
        StepPointer = .Manager.DataPointer + .Manager.ProgramIndex * PROGRAM_SIZE_IN_BYTES
        'CopyMemory RecordTitle, ByVal StepPointer, HEADER_SIZE_IN_BYTES
        PutMem4 VarPtr(Stub) + 4, ByVal StepPointer
       
        StepPointer = .Manager.DataPointer + _
            .Manager.ProgramIndex * PROGRAM_SIZE_IN_BYTES + _
            HEADER_SIZE_IN_BYTES + _
            .Manager.StepIndex * STEP_SIZE_IN_BYTES
       
        'CopyMemory RecordFill, ByVal StepPointer, STEP_SIZE_IN_BYTES
        PutMem4 VarPtr(Stub) + 8, ByVal StepPointer
       
' /* Вырезано */

    End With
End Sub

Public Sub ShowPropertyTableForFill(frm As FormMain)
    Dim RecordTitle As TYPE_WPC_TITLE
    Dim RecordFill As TYPE_WPC_FILL
   
    func_ShowPropertyTableForFill frm, 0&, RecordTitle, RecordFill
End Sub


Переделываю теперь весь остальной код.
Россия навсегда!
Сетрификаты

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 03.01.2011 (Пн) 15:08

Dim I, J, row As Integer
Dim ParamStr, s As String
Dim StepPointer As Long


Есть группа идиотов (это я не о тебе) из мира С/С++ и паскаля, которая освоила и VB и перенесла на него стиль из вышеупомянутых языков, не обременив себя чтением документации (как делают нормальные люди) по VB. Из-за них, ошибка, которая есть в этом коде, стала популярна.


Dim i, j, k as long — это НЕ то же самое, что и DIm i as long, j as long, k as long, а совсем другое. i и j при этом, скорее всего, будут иметь совсем другой тип, а не Long.

Что касается твоего кода: я всё-таки рекомендую (как советовал keks-n) делать пару аргументов: один для «разыменованного» указателя, другой для значения указателя. Намного удобнее.


Код: Выделить всё
ByVal begin_of_pointers As Long, _
ByRef pointer_to_DATA As Long, _
ByRef value_of_DATA As Long

PutMem4 ByVal VarPtr(begin_of_pointers) + 4, _
        ByVal VarPtr(begin_of_pointers) + 8

pointer_to_DATA = A
MsgBox value_of_DATA ' Выводим dword, лежащий по адресу A

pointer_to_DATA = B
MsgBox value_of_DATA ' Выводим dword, лежащий по адресу B

pointer_to_DATA = C
value_of_DATA = D ' Записываем по адресу C значение D
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

uni
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 105
Зарегистрирован: 05.05.2006 (Пт) 15:24
Откуда: Екатеринбург

Re: Об указателях в VB6

Сообщение uni » 03.01.2011 (Пн) 15:18

Use a separate As type clause for each variable you declare

Неужели Variant будет? Это для меня откровение :) Я вообще-то не только на C/C++ и паскаль, но и на Java (J2ME), С#, C++/CLI и прочее и прочее, за всем не уследишь.

Кстати, пишу для прикола интерпретатор команд методом КА. Седня/завтра выложу в соотв-щей теме в виде специализированного калькулятора с поддержкой функций. Сел писать только для того, чтобы как раз сильнее освоить VB6. Пока только наваял парсер, который плюс/минус понимает. Надесь после этого кода :) всякие нападки по поводу идиотизма в сторону тех самых товарищей пропадут ;)
Россия навсегда!
Сетрификаты

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 03.01.2011 (Пн) 15:22

uni писал(а):Неужели Variant будет?

Если нет вещей (установки типа по умолчанию) вроде DefLng A-z или DefStr x-y — будет Variant, да.

uni писал(а):Надесь после этого кода :) всякие нападки по поводу идиотизма в сторону тех самых товарищей пропадут ;)

А какая связь между твоим кодом и теми, кто популяризировал ошибку?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

uni
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 105
Зарегистрирован: 05.05.2006 (Пт) 15:24
Откуда: Екатеринбург

Re: Об указателях в VB6

Сообщение uni » 03.01.2011 (Пн) 15:50

Да очень просто, С/С++ товарищи - труженники. Да, я не читал доку по этому поводу, но она (ошибка) рано или поздно о себе напомнит. Код, что я использовал - это моё второе в жизни серьёзное приложение на VB6 и, признаюсь, имея опыт из других языков и применяя его в VB6 я был сильно удивлён возможностям этой IDE и языка. Воистину в умелых руках он может многое. Увы, жаль только, что сама среда не способна поддерживать чуть более сложный код (хук на клаву вышибает IDE, к примеру - MSFlexGrid не работает с курсорами, пришлось добывать из инета обходной путь).

Код интерпретатора (парсера) достаточно не тривиален, он взят из J2ME, где я написал аналогичный. Из-за своей не тривиальности он ускоряет изучение языка многократно и возможностям отладки IDE тоже.
А какая связь между твоим кодом и теми, кто популяризировал ошибку?
Я из той группы товарищей, которые переносят свои навыки между языками и это, вообще говоря, не есть зло, т.к. практически все PCшные ЯВУ устроены одинаково (машина Тьюринга и архитектура фон Неймана). Мой код покажет, что надо давать тем товарищам право на ошибки, т.к. они вполне способны постепенно обнаружить их самостоятельно. Ну и кроме того, этот парсер может мне понадобиться в расширении возможностей текущего проектика и мысли гуру в этом языке мне бы не помешали. Меня больше результат интересует.
Россия навсегда!
Сетрификаты

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 03.01.2011 (Пн) 15:55

uni писал(а):Да очень просто, С/С++ товарищи - труженники.

Да кто бы спорил, я сам сишник и пишу почти всегда на Си.

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

uni писал(а): что сама среда не способна поддерживать чуть более сложный код (хук на клаву вышибает IDE

Это всё ерунда. Ничего не вышибается в нормальном безошибочном коде.

Но если сделать хук на создание окна, в коде сделать ошибку, то при возникновении ошибки создастся окно, опять сработает хук, опять в нём возникнет ошибка, опять создастся окно, и так, пока не кончится стек.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

uni
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 105
Зарегистрирован: 05.05.2006 (Пт) 15:24
Откуда: Екатеринбург

Re: Об указателях в VB6

Сообщение uni » 03.01.2011 (Пн) 16:08

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

Ну, можно согласиться. Я дам тебе повод ещё поиздеваться над моим кодом. Мне простого калькулятора мало :) Хочется переменных и оператора присвоения ;) Пришлось ваять кучу вспомогательного код, в т.ч. делать реализацию класса Vector, а вот как выглядит черновой пример класса поддержки переменных (он хоть и черновой, но в работе уже проверен - пашет) ОбразПеременных.cls:
Код: Выделить всё
Option Explicit

Public Имена As Vector
Public Значения As Vector
Public Свойства As Vector

' Конструктор
Private Sub Class_Initialize()
    Set Имена = New Vector
    Set Значения = New Vector
    Set Свойства = New Vector
End Sub

' Деструктор
Private Sub Class_Terminate()
  Set Имена = Nothing
  Set Значения = Nothing
  Set Свойства = Nothing
End Sub

Public Sub ДобавитьЭлемент(имя As String, свойство As Integer, Значение As ОбразРезультата)
    Dim str As New CString
    Dim num As New CInteger
   
    str.value = имя
    num.value = свойство
   
    Имена.addElement (CVar(str))
    Свойства.addElement (CVar(num))
    Значения.addElement (CVar(Значение))
End Sub

Public Function ИмяВТаблице(имя As String) As Boolean
    Dim Found As Boolean
    Dim i As Integer
    Dim строка As CString
   
    Found = False
   
    For i = 1 To Имена.Size
        Set строка = Имена.elementAt(i)
       
        If (строка.equals(имя)) Then
            Found = True
            Exit For
        End If
    Next i

    ИмяВТаблице = Found
End Function

Public Function СвойствоЭлемента(имя As String) As Integer
    Dim i As Integer
    Dim свойство As CInteger
    Dim строка As CString

    For i = 1 To Имена.Size
        Set строка = Имена.elementAt(i)
       
        If (строка.equals(имя)) Then
            Set свойство = Свойства.elementAt(i)
            СвойствоЭлемента = свойство.value
            Exit Function
        End If
    Next i

    СвойствоЭлемента = ТИП_НЕИЗВЕСТЕН
End Function

Public Function ЗначениеЭлемента(имя As String) As ОбразРезультата
    Dim i As Integer
    Dim строка As CString

    For i = 1 To Имена.Size
        Set строка = Имена.elementAt(i)
       
        If (строка.equals(имя)) Then
            Set ЗначениеЭлемента = Значения.elementAt(i)
            Exit Function
        End If
    Next i

    ЗначениеЭлемента = Nothing
End Function

Public Sub ИзменитьЭлемент(имя As String, свойство As Integer, новоезначение As ОбразРезультата)
    Dim i As Integer
    Dim строка As CString
   
    For i = 1 To Имена.elementAt(i)
        Set строка = Имена.elementAt(i)
       
        If (строка.equals(имя)) Then
            ' Удаляем старую запись
            Имена.removeElementAt (i)
            Свойства.removeElementAt (i)
            Значения.removeElementAt (i)
           
            ' Добавляем новую запись
            ' Имена.addElement( имя )
            ' Свойства.addElement( new Integer( свойство ) )
            ' Значения.addElement( новоезначение )
           
            Exit Sub
        End If
    Next i
End Sub

Россия навсегда!
Сетрификаты

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 03.01.2011 (Пн) 16:09

Просто код здесь не обсуждается. Кириллические идентификаторы в коде меня убивают.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

uni
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 105
Зарегистрирован: 05.05.2006 (Пт) 15:24
Откуда: Екатеринбург

Re: Об указателях в VB6

Сообщение uni » 03.01.2011 (Пн) 16:19

Ты не понял сути. Это пример. Учитывая сложность самой идеи парсинга по всем правилам в виде КА, стоит ли в качестве обучающей демострации для понятия самой идеи использовать русский текст? На J2ME у меня всё приложение написано на русском, а не только интерпретатор. Я потом выложу оригинал и портированную на бейсик версию, сможете обсудить в отдельной теме.

Да, кстати, если кто сомневается :) вот тут в мануале в конце приведены листинги текстовых файлов, которые будет в принципе способен интерпретировать указанный интерпретатор, только в виду ограниченных возможностей отладчика среды VB6, увы, я не смогу включить работу с матрицами (это будет сложно понять).
http://школьныйзвонок.рф/user_manual_0_5_8_a6.pdf
Россия навсегда!
Сетрификаты

tych
Начинающий
Начинающий
Аватара пользователя
 
Сообщения: 13
Зарегистрирован: 03.12.2013 (Вт) 0:16
Откуда: Russia, Kaliningrad

Re: Об указателях в VB6

Сообщение tych » 26.01.2014 (Вс) 13:10

Прошу прощения за недопонимание
' Определить реальный адрес MyPointer с помощью VarPtr - нельзя.
' Но его можно вычислить как VarPtr(Stub) + 4.

Почему данная схема перестает работать, когда мы выносим двойной указатель за пределы функции(процедуры) ?
Код: Выделить всё
Sub Main()
    dim PtrAddr as Long, p1 as Long, p2 as Long
    ptrAddr=tst(0,p1,p2)
    PutMem4 ptrAddr, VarPtr(p2)
    '...
End Sub

Function tst(ByVal stuff As Long, ByRef p1 As Long, ByRef p2 As Long)
    tst=VarPtr(stuff) + 4
End Function

получается чушь (

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 26.01.2014 (Вс) 13:22

Потому что в таком виде код бессмысленен.

Значение, которое возвращает функция tst, перестаёт иметь всякий смысл после того, как происходит возврат из этой функции.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 09.03.2017 (Чт) 17:11

Хакер писал(а):Хотим пример? Пожалуйста. Первое, что мне пришло в голову — быстрая замена символов в строке

А у меня не работает:(
Я только начинаю разбираться с указателями, так что не судите строго. Я сделал так:
Код: Выделить всё
Private Declare Function GetMem4 Lib "msvbvm60" _
   (ByVal pSrc As Long, ByVal pDst As Long) As Long
Private Declare Function PutMem4 Lib "msvbvm60" _
   (pDst As Any, ByVal NewValue As Long) As Long

Sub Main()
   Repl "мама", AscW("м"), AscW("п")
End Sub

Public Sub Repl(ByVal sString As String, _
                ByVal Search As Integer, _
                ByVal Replacement As Integer, _
                Optional ByVal Stub As Long, _
                Optional ByRef Replacer As Integer)

        Dim CurPos As Long
        Dim EndPos As Long
        Dim ptrAddress As Long ' Здесь будем хранить VarPtr(Stub)+4
   
100     CurPos = StrPtr(sString)
102     EndPos = CurPos + LenB(sString)
104     ptrAddress = VarPtr(Stub) + 4
   
        Do
106         PutMem4 ptrAddress, CurPos
108         If Replacer = Search Then Replacer = Replacement
110         CurPos = CurPos + 2     ' Переходим к след. сим.
112     Loop Until CurPos = EndPos
114     MsgBox sString
End Sub

Никакой замены символов в итоге не происходит. MsgBox возвращает "мама". В строке 108 Replacer всегда равен 0. Мне вообще не понятно, как эта процедура должна работать?
Я так понимаю, что в строке 106 мы записываем в Replacer адрес текущего символа. Отсюда два вопроса: 1) Почему Replacer = 0; 2) Почему записываем адрес символа, а не сам символ?
Мне также не понятно в каком месте должно происходить изменение собственно строки sString, если мы производим единственное присваивание в цикле: Replacer = Replacement?
Короче, Help!

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 09.03.2017 (Чт) 20:15

nouyana писал(а):Никакой замены символов в итоге не происходит.

Неправильно объявлена PutMem4.

Если поменять вызов на PutMem4 ByVal ptrAddress, CurPos или в объявлении аргумент на ByVal pDst As Long, то всё будет заменяться.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 09.03.2017 (Чт) 20:23

nouyana писал(а):Я так понимаю, что в строке 106 мы записываем в Replacer адрес текущего символа.

Мы перевешиваем указатель (который скрывается «под капотом» за ByRef-аргументом) так, чтобы он указывал на символ, который мы собираемся проверять и, при случае, изменять.

nouyana писал(а):Отсюда два вопроса: 1) Почему Replacer = 0;

Потому что из-за неправильного вызова PutMem4 указатель так и продолжал указывать на 0, который был передан при вызове Repl.

nouyana писал(а):Почему записываем адрес символа, а не сам символ?

Replacement содержит сам символ, а не адрес.

nouyana писал(а):Мне также не понятно в каком месте должно происходить изменение собственно строки sString, если мы производим единственное присваивание в цикле: Replacer = Replacement?

Вот это присвоение посимвольно и изменяет входную строку.

И вообще, описанный хак больше для выработки понимания того, как всё устроено, а не для практического использования. Для примитивных типов обычно гораздо эффективнее использовать другой трюк. Для структур — трюк с ремаппингом SafeArray-дескриптора. Но можно и этот, в принципе.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 09.03.2017 (Чт) 21:29

Хакер писал(а):Мы перевешиваем указатель (который скрывается «под капотом» за ByRef-аргументом) так, чтобы он указывал на символ, который мы собираемся проверять и, при случае, изменять.

Спасибо! Кажется тайна за семью печатями начинает рассеиваться.
Правильно ли я понял, что и в результате использования PutMem4, и в результате использования GetMem4 переменные меняются своими значениями. Только в случае с PutMem4 это происходит через замену ссылок (указателей), а в случае с GetMem4 меняются сами данные (4 байта)?

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 09.03.2017 (Чт) 21:54

Блин, всё равно не пойму. Зачем нам указатель именно на ByRef? Я так понимаю, что ptrAddress в принципе изначально может быть любым, лишь бы в памяти не нагадить, так? Тогда почему мы не можем сделать указатель на какую-нибудь обычную переменную? Например, так:
Код: Выделить всё
Public Sub Repl(ByVal sString As String, _
                ByVal Search As Integer, _
                ByVal Replacement As Integer) ' Replacer и заглушку убрал.
               
       Dim CurPos     As Long
       Dim EndPos     As Long
       Dim Replacer   As Long 'То, что раньше было ByRef
       Dim ptrAddress As Long
 
100    CurPos = StrPtr(sString)
102    EndPos = CurPos + LenB(sString)
   
104    ptrAddress = VarPtr(Replacer) 'А здесь было VarPtr(Stub) + 4

       Do
106       PutMem4 ptrAddress, CurPos
108       If Replacer = Search Then Replacer = Replacement
110       CurPos = CurPos + 2
112    Loop Until CurPos = EndPos
114    MsgBox sString
End Sub

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 10.03.2017 (Пт) 8:27

Во общем, чем дальше, тем больше вопросов. PutMem4 "перевешивает указатель" или записывает новое значение по адресу? В вышеописанной процедуре Repl для меня было очевидно, что PutMem4 перевешивает указатель. Но, в этом примере происходит запись значения:
Код: Выделить всё
Declare Sub PutMem4 Lib "msvbvm60" _
   (ByVal Addr As Long, ByVal NewVal As Long)
   
Sub Main()
100    Dim val&: val = 10
102    PutMem4 VarPtr(val), 1000000
104    Debug.Print val            ' Вернёт 1000000
End Sub

Каким образом VB6 в строке 102 понял, что 1000000 - это не указатель, а значение?
Кстати, в этой статье на говорится, что PutMem принимает-таки значение, а не указатель.
Хакер писал(а):Если поменять вызов на PutMem4 ByVal ptrAddress, CurPos или в объявлении аргумент на ByVal pDst As Long, то всё будет заменяться.

Я так понимаю, что собака зарыта именно здесь, но пока я её не нашёл:)

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 10.03.2017 (Пт) 9:32

nouyana писал(а):Правильно ли я понял, что и в результате использования PutMem4, и в результате использования GetMem4 переменные меняются своими значениями. Только в случае с PutMem4 это происходит через замену ссылок (указателей), а в случае с GetMem4 меняются сами данные (4 байта)?


PutMem4 принимает два 32-битных значения: первое это адрес, куда нужно записать, а второе — это само число, которое нужно записать. И записывает.
GetMem4 принимает два 32 битных значения: первое это адрес, откуда нужно прочитать, а второе — это адрес, куда нужно записать прочитанное. Читает и записывает.

Эти функции сделанны именно такими, какими сделанны, чтобы соответствовать семантике вызовов, которые применяются в COM (вообще) и в VB (в частности) для get/put-свойств. Их только не обернули в Get/Put-свойства, но я это недоразумение исправил — пришёл и обернул их в свойство «At», о чём целый топик и написан. В нём они работают именно как обёрнутые свойства.

nouyana писал(а):Зачем нам указатель именно на ByRef?

Постановка вопроса показывает полное напонимание сути происходящих процессов.
Нет никакого указателя «на ByRef». Если конечнно предлогом «на» ты пытаешься сказать, кто на кого указывает, а не что на чём базируется.

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

nouyana писал(а):Я так понимаю, что ptrAddress в принципе изначально может быть любым, лишь бы в памяти не нагадить, так?

ptrAddress — это обычная плоская переменная, которая нам нужна, чтобы в ней хранить адрес самого указателя. Адрес указателя нам нужен для того, чтобы записывая 32-битные значения (которые по факту являются адресами) по адресу указателя менять (то есть «перевешивать») указатель. Иными словами, ptrAddress хранит адрес ячейки памяти, куда записывается другой адрес, по которому уже потом можно читать и писать 16-битные (в данном примере) значения, используя аргумент Replacer.

nouyana писал(а): Тогда почему мы не можем сделать указатель на какую-нибудь обычную переменную?

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

Код: Выделить всё
a& = 1:  b& = 2:  c& = 3:  d& = 4:  e& = 5: f& = 6
proc_xxx a&, b&, c&, d&, e&, f&

Sub proc_xxx(byval arg1 as long, byval arg2 as long, byval arg3 as long, byref arg4 as long, byval arg5 as long, byref arg6 as long)
   ...
End Sub

При таком вызове в процедуру будут переданы: единица, двойка, тройка, какой-то адрес, пятёрка, какой-то адрес.
Обращаясь изнутри процедуры (proc_xxx) к arg3 мы «видим» тройку, а обращаясь к arg4 видим не «какой-то адрес», а то, что по нему находится (а по нему находится 4-ка).

Иными словами, arg4 (и arg6) в отличие от остальных аргументов, содержат не значения, а адреса, то есть являются указателями. Но при попытке читать или изменять эти аргументы мы читаем/меняем не сам адрес, а то, что по нему находится. Причём это происходит для VB-программиста скрытно. Сами адреса (которые фактически хрантся в arg4 и arg6) VB программист напрямую не может ни прочитать (получить), ни изменить. Тем не менее, узнать, на что указывает ByRef-аргумент можно с помощью VarPtr, а вот заставить ByRef-аргумент указывать не на то, на что он должен указывать (на что указатель установлен в момент вызова процедуры), а на какое-то вообще левое произвольное место памяти мы не можем.

Но раз у нас есть PutMem4/GetMem4/CopyMemory, которые дают нам возможность произвольных модификаций памяти в произвольных местах, мы можем вычислить адрес самого указателя и поменять его.

VarPtr(arg4) даст нам не адрес arg4, а адрес Long-значения, на которое ссылается arg4. Чтобы получить реальный адрес arg4, мы получаем адрес arg3 (который мы можем получить с помощью VarPtr(arg3)), а затем к адресу arg3 прибавляем 4, чтобы получить адрес arg4.

Т.е., зная тот факт, что аргументы 4-байтовые и в стеке размещаются компактно подряд (в прямо порядке), мы адрес arg4 можем вычислить как:
VarPtr(arg3) + 4 или
VarPtr(arg2) + 8 или
VarPtr(arg1) + 12 или
VarPtr(arg5) - 4.

Всё, с тех пор как мы знаем реальный адрес arg4, мы можем менять реальное содержимое arg4, а реальным содержимым является адрес. После того, как мы поменяем реальное содержимое arg4 на какое-то свой (какой-то свой адрес), обратившись к arg4 обычным образом, например так:
arg4 = arg4 + 1
будет изменено значение, которое в памяти лежит по тому адресу, который мы предварительно в arg4 загнали.

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

А вот само содержимое arg4 (реальное, фактическое, «низкоуровневое») мы можем поменять только так:
PutMem4 real_address_of_byref_argument(arg4), новый_адрес

Функции real_address_of_byref_argument в VB6 нет и быть не может, но заместо неё реальный адрес ByRef-аргумента, как я уже сказал, можно вычислить с помощью трюка, например как VarPtr(arg3) + 4 или VarPtr(arg5) - 4.

nouyana писал(а): PutMem4 "перевешивает указатель" или записывает новое значение по адресу?

Монета круглая или металлическая? Какого ответа ты ожидаешь и почему надо выбирать либо то, либо другое? Монета и круглая, и металлическая — одно другому не мешает. Точно так же и PutMem4 записывает новое значение по указанному адресу, чем осуществляет (в рамках того примера, о котором мы говорим) перевешивание указателя.

nouyana писал(а):Каким образом VB6 в строке 102 понял, что 1000000 - это не указатель, а значение?

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

Дальше этот код выполняется процессором. Процессору тоже глубоко безразлично, что делает GetMem4 и как интерпретирует переданные параметры. Процессор слепо и бездумно выполняет череду инструкций: одну за другой.

В этом коде в PutMem4 был передан адрес переменной val (полученный вызовом VarPtr) и значение 1000000. PutMem4 слепо по переданному адресу записала число 1000000. Вот и всё.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 10.03.2017 (Пт) 9:38

nouyana писал(а):Кстати, в этой статье на говорится, что PutMem принимает-таки значение, а не указатель.

Сама постановка вопроса неправильная.

Указатель и значение — не две взаимоисключающих идеи.

Указатель — это просто поэтичное название переменной, содержащей (фактически) адрес чего-то. Обычно язык программирования, поддерживающий указатели, позволяет модифицировать как сам указатель, так и то, на что он указывает.

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

Поэтому есть «значение указателя», а есть «значение по указателю» (или «значение по адресу»). Есть просно «значение», если мы говорим о простых переменных.

Самой функции PutMem4 глубоко фиолетово, что ей передают: (1) адрес самой указательной переменной, (2) значение указательной переменной или (3) значение, на которое указывает указатель.

Она тупо съест то, что ей передали, и по переданному адресу запишет новое число. В первом случае произойдёт «перевешивание» указателя. Во втором случае изменится та ячейка памяти, на которую указывал указатель. В третьем случае та ячейка памяти, на которую указывал указатель, будет сама расценена как некий адрес, и по этому адресу будет осуществлена попытка записи. Если это некорректный адрес — произойдёт исключение, которое, если не будет должным образом обработана, вызовет крах процесса.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 10.03.2017 (Пт) 11:43

Хакер писал(а):Всё, с тех пор как мы знаем реальный адрес arg4, мы можем менять реальное содержимое arg4, а реальным содержимым является адрес.

Вот теперь я понял! :D
Спасибо!

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Об указателях в VB6

Сообщение Хакер » 10.03.2017 (Пт) 11:56

nouyana писал(а):то почему Replacer принимает значение 1084, а не 139969156

Replacer фактически принимает значение 139969156, но при попытке прочитать значение Replacer прочитается не его фактическое значение (являющееся адресом-ссылкой), а то, на что ссылается ByRef-аргумент. А ссылается он, после того, как мы туда загнали значение 139969156, как раз на 16-битное значение 1084, потому что именно такое 16-битное (Integer) значение и лежит по адресу 139969156.

nouyana писал(а):Получилось так, будто это не PutMem4, а GetMem4, только аргументы в другом порядке: значение (код символа) из адреса CurPos записалось в переменную по адресу ptrAddress.

Если бы было так, то попытка сделать Replacer = Replacer + 1 после вызова PutMem4 (которая по твоему GetMem4) привела бы к тому, что исходная строка не поменялась. Потому что по твоему мнению код символа из оригинальной исходной строки скопировался в Replacer.

А на самом деле Replacer теперь ссылается на 16-битный элемент (символ) строки и любая попытка прочитать (штатными средствами языка) значение из переменной Replacer приведёт к чтению символа из строки, а любая попытка записать (штатные средствами языка) что-то Replacer приведёт к изменению символа внутри оригинальной строки.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 10.03.2017 (Пт) 15:26

Хакер писал(а):Replacer фактически принимает значение 139969156, но при попытке прочитать значение Replacer прочитается не его фактическое значение (являющееся адресом-ссылкой), а то, на что ссылается ByRef-аргумент.

Да, понял уже. Просто изначально я думал, что операция ptrAddress = VarPtr(Stub) + 4 присваивает переменной ptrAddress именно адрес конечного значения, а не адрес ссылки по аналогии с тем как ты писал:
Хакер писал(а):Не совсем понятно? Давайте рассмотрим пример:
Код: Выделить всё
Function Main()
        Dim zipa as long
        zipa = 123456789
        AAA zipa
    End Function
    Function AAA(ByRef MyArg As Long)
          MsgBox CStr(MyArg)
          BBB MyArg
    End Function
    Function BBB(ByRef MyBBBArg As Long)
          MsgBox CStr(MyBBBArg)
    End Function

Здесь в функцию AAA передаётся не само значение переменной zipa, а ссылка (указатель) на неё. Однако, в AAA при вызове BBB в качестве аргумента MyBBBArg передаётся уже не указатель на AAA->MyArg, а указатель на Main->zipa.

Мне казалось, что это логично и для функции VarPtr: зачем возвращать ссылку на ссылку, если можно вернуть ссылку на значение? Я и сейчас не понимаю, зачем сделали именно так. Наверное, когда разберусь со свойствами о которых ты писал в этой теме, мне станет всё понятно.

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 10.03.2017 (Пт) 20:58

Я не знаю, как писать TLB-шки и, вероятно, не скоро с этим разберусь, а пока написал свойство At для обычного модуля. Может, кому пригодится:
Код: Выделить всё
Private Declare Function GetMem4 Lib "msvbvm60" _
   (ByVal pSrc As Long, ByVal pDst As Long) As Long
Private Declare Function PutMem4 Lib "msvbvm60" _
   (ByVal pDst As Long, ByVal NewValue As Long) As Long

Public Property Get At(ByVal addr As Long) As Long
   Dim tmpValue&
   GetMem4 addr, VarPtr(tmpValue)
   At = tmpValue
End Property
Public Property Let At(ByVal addr As Long, _
                       ByVal letValue As Long)
   PutMem4 addr, letValue
End Property

Использовать так:
Код: Выделить всё
Sub Main()
   Dim addr As Long
   Dim val_1 As Long
   Dim val_2 As Long
   
   val_1 = 5
   addr = VarPtr(val_1) 'получаем адрес переменной val_1
   val_2 = At(addr)     'извлекаем значение по адресу
   Debug.Print val_2    ' = 5
   At(addr) = 10        'меняем знач. переменной val_1
   Debug.Print val_1    ' = 10
End Sub

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 11.03.2017 (Сб) 6:46

Решил сделать что-то более полезное - написать свойство AtV, которое бы возвращало любую переменную (через тип Variant) по её указателю. Может, кто подскажет, почему не получилось?
Код: Выделить всё
Sub Main()
   Dim s1   As String
   Dim s2   As String
   Dim addr As Long
   
   s1 = "Hello, world!"
   addr = VarPtr(s1)
   s2 = AtV(addr)
   Debug.Print s2
End Sub

Public Property Get AtV(ByVal addr As Long) As Variant
   AtV = fnAtV(addr)
End Property
Private Function fnAtV(ByVal addr As Long, _
              Optional ByRef refVar As Variant) As Variant
   Dim ptrAddress As Long
   ptrAddress = VarPtr(addr) + 4
   PutMem4 ptrAddress, addr
   
   ' Дальше идёт ошибка 458: Переменная использует
   ' тип, не поддерживаемый в Visual Basic
   fnAtV = refVar
End Function

Предполагаю, это потому, что тип Variant тоже содержит ссылку. Получается, что ptrAddress - это уже ссылка на ссылку на ссылку. Есть предложения, как решить задачу?

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 11.03.2017 (Сб) 16:06

Экономистам (как я) довольно часто приходится работать с массивами пользовательских типов (таблицы, по сути). Я только что понял, что такой массив нельзя присвоить вариантной переменной, и к обозначенной выше задаче добавилась ещё одна. Я недавно научился компилировать DLL-ки при помощи PowerBasic, который обычно работает быстрее, и хочу использовать это для ускорения обработки таких массивов. Но для этого мне нужно как-то передать ссылку на этот массив в DLL-ку. Хакер, можешь подсказать как получить такую ссылку? Или я уже слишком много напостил вопросов в твоём блоге? :)
Проблемы, как минимум, две:
1. Массив пользовательского типа нельзя присвоить вариантной переменной.
2. VarPtr не работает с массивами.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4147
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Об указателях в VB6

Сообщение Mikle » 11.03.2017 (Сб) 16:33

nouyana писал(а):PowerBasic, который обычно работает быстрее

Это заблуждение.
nouyana писал(а):VarPtr не работает с массивами.

VarPtr нулевого элемента массива и будет указателем на данные массива, на сам массив можно получить указатель с помощью такой функции:
Код: Выделить всё
Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (Arr() As Any) As Long

Только поддерживает ли PowerBasic массивы VB6?

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 11.03.2017 (Сб) 18:22

Mikle писал(а):Только поддерживает ли PowerBasic массивы VB6?

Судя по твоему вопросу - не поддерживает, но я даже в VB6 не смог протестировать работу со ссылкой на массив:
Код: Выделить всё
Private Type type_Test
   strVar As String
   lonVar As Long
End Type

Sub Main()
   Dim MyArr(1 To 10) As type_Test
   Dim sAddr          As Long
   sAddr = ArrPtr(MyArr) 'Получаем адрес массива
   subTest sAddr         'Вызываем процедуру заполнения массива
End Sub

Public Sub subTest(ByVal sAddr As Long, _
          optional ByRef refArr() As type_Test) 'ОШИБКА!!!
'   ...
End Sub

Ошибка постигла меня уже на этапе объявления Public Sub subTest, потому что optional-переменная не может быть ни массивом, ни пользовательского типа (у меня и то, и то). Правильно ли я понимаю, что пользоваться ссылками на массив в VB просто негде, и передать их в DLL не получится? Или есть какой-то способ хотя бы в VB получить массив по ссылке (не знаю пока, зачем мне это)?

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 11.03.2017 (Сб) 19:00

2Mikle.
PowerBasic поддерживает массивы пользовательских типов. Единственное, что он мне не дал сделать - это объявить внутри пользовательского типа строку переменной длины - можно только фиксированной. А так, всё работает:
Код: Выделить всё
#COMPILE EXE
#DIM ALL

TYPE ttt
    a AS STRING*3
    b AS LONG
END TYPE

FUNCTION PBMAIN () AS LONG
    DIM MyArr(1 TO 10) AS ttt
    MyArr(1).a = "ggg"
    MSGBOX MyArr(1).a
END FUNCTION   

The trick
Постоялец
Постоялец
 
Сообщения: 774
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Об указателях в VB6

Сообщение The trick » 11.03.2017 (Сб) 19:59

nouyana писал(а):1. Массив пользовательского типа нельзя присвоить вариантной переменной.

Можно, для этого тип должен быть объявлен в библиотеке типов. viewtopic.php?f=1&t=43928
Изображение
nouyana писал(а):2. VarPtr не работает с массивами.

Майл уже ответил, еще можно использовать багофичу с Not Not Arr.
UA6527P

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 104
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Об указателях в VB6

Сообщение nouyana » 11.03.2017 (Сб) 23:16

The trick писал(а):Можно, для этого тип должен быть объявлен в библиотеке типов. viewtopic.php?f=1&t=43928

По-моему, пора новую книгу писать. Собрать посты за 2008-2017 год и написать второй том. Он мог бы быть гораздо более интересным, чем первый. Хакер красавчик, хоть и вредничает немного :)
Итак, в результате я могу написать процедуру, которая получает ссылку на вариантный массив пользовательского типа и заполняет его. Для этого я:
  1. создал проект "ActiveX EXE"
  2. в свойствах проекта изменил на вкладке Component: Start mode = Standalone
  3. Создал модуль класса и изменил его свойство Instancing на «5 – MultiUse»
  4. Объявил в этом классе Public Type, который стал доступен во всём проекте, и который можно использовать внутри типа Variant (в т.ч. массивом):
  5. Код: Выделить всё
    Public Type type_Test
       strVar As String
       lonVar As Long
    End Type

  6. Ну, и написал процедуру subTest:
Код: Выделить всё
Sub Main()
   Dim MyArr(1 To 10) As type_Test
   Dim MyVar          As Variant
   Dim sAddr          As Long
   
   MyVar = MyArr         'Присваиваем UDT-массив варианту
   sAddr = VarPtr(MyVar) 'Получаем адрес вариантного массива
   subTest sAddr         'Вызываем процедуру заполнения массива
End Sub

Public Sub subTest(ByVal sAddr As Long, _
          Optional ByRef refArr As Variant)
   Dim ptrAddr As Long
   ptrAddr = VarPtr(sAddr) + 4  'Получаем адрес refArr
   PutMem4 ptrAddr, sAddr       'Пишем в него ссылку на MyVar
   refArr(1).lonVar = 5         'Заполняем массив MyVar
   refArr(1).strVar = "test"
End Sub

Осталось проверить, как это работает с DLL-кой из PowerBasic, но у меня уже 3:20 ночи, так что я пошёл спать, отпишусь в следующем посте.

Пред.След.

Вернуться в МануAll

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

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

    TopList