Что нового

Как правильно передать массив в dll?

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Garrett [?]
потому как структура удобнее в использовании.
Можно пример AutoIt-скрипта, где структура удобнее массива (без DllCall()).
 

Prog

Продвинутый
Сообщения
593
Репутация
73
В структуре можно дать полям понятные имена и код будет лучше читаем чем в случае с массивом где бывает сложно понять что в по какому индексу хранится.
 

alex33

Скриптер
Сообщения
1,457
Репутация
186
Лучше уж тогда в скрипте использовать
Код:
ObjCreate('Scripting.Dictionary')

Там и поля можно называть как хочешь...
 

Prog

Продвинутый
Сообщения
593
Репутация
73
Пример можно найти здесь.
Код:
; Создаем структуру MEMORYSTATUSEX
; http://msdn.microsoft.com/en-us/library/aa366770%28VS.85%29.aspx
$tMem = DllStructCreate( _
        "dword  dwLength;" & _
        "dword  dwMemoryLoad;" & _
        "uint64 ullTotalPhys;" & _
        "uint64 ullAvailPhys;" & _
        "uint64 ullTotalPageFile;" & _
        "uint64 ullAvailPageFile;" & _
        "uint64 ullTotalVirtual;" & _
        "uint64 ullAvailVirtual;" & _
        "uint64 ullAvailExtendedVirtual;")

; До вызова Функции GlobalMemoryStatusEx() необходимо в поле "dwLength" записать размер структуры в байтах

; Получаем размер структуры $tMem
$iSize = DllStructGetSize($tMem)

; Записываем в поле "dwLength" значение из $iSize (размер структуры)
DllStructSetData($tMem, "dwLength", $iSize)

; Получаем адрес структуры $tMem
$pMem = DllStructGetPtr($tMem)

; Вызываем API функцию GlobalMemoryStatusEx(), которая заполнит необходимые поля в структуре $tMem
; http://msdn.microsoft.com/en-us/library/aa366589%28VS.85%29.aspx
DllCall("kernel32.dll", "bool", "GlobalMemoryStatusEx", "ptr", $pMem)

; Читаем значения из полей "ullTotalPhys" и "ullAvailPhys" (количество установленной и доступной памяти соответственно, в байтах)
$iTotalPhys = DllStructGetData($tMem, "ullTotalPhys")
$iAvailPhys = DllStructGetData($tMem, "ullAvailPhys")

; Освобождаем занимаемую структурой $tMem память (можно пропустить, т.к. программа на этом заканчивается)
$tMem = 0

; Выводим результат в консоль
ConsoleWrite("Всего памяти: " & StringFormat("%.2f МБ", $iTotalPhys / 1024 / 1024) & @CR)
ConsoleWrite("Доступно:     " & StringFormat("%.2f МБ", $iAvailPhys / 1024 / 1024) & @CR)


Вообще, по моему мнению, структуры в AutoIt слабо развиты и с ними не очень удобно работать. Вроде даже вложенные структуры не поддерживаются (таких примеров не видел). Возможно по этому кажется что структуры не нужны.

Давайте я покажу код на PB, в котором лучше обстоят дела со структурами.
Это код показанный выше и переписанный на PB.
Код:
pMem.MEMORYSTATUSEX
pMem\dwLength = SizeOf(pMem)
GlobalMemoryStatusEx_(@pMem)
Debug "Всего памяти: "+Str(pMem\ullTotalPhys / 1024 / 1024)
Debug "Доступно:     "+Str(pMem\ullAvailPhys / 1024 / 1024)
Код короче и ИМХО со структурой работать проще.

Еще один пример, по сложнее. Допустим нужно найти все файлы и папки в заданной папке и сохранить их параметры, т. е. имена всех папок и файлов, размеры файлов их атрибуты и т. д. Т. к. папки имеют древовидную структуру (в папке может быть несколько папок, в каждой их которых еще несколько, плюс файлы), обычный массив не очень подходит для этого. Нужен тип данных "дерево", который можно сделать из структуры.
Пример который сканирует папку с AutoIt и сохраняет ее структуру в JSON файле.
Код:
; Для компиляции нужен PureBasic 5.30 и выше.
Structure FileDate         ; Информация о дате папки или файла.
  Created.l                ; Дата создания.
  Accessed.l               ; Дата предыдущего доступа.
  Modified.l               ; Дата предыдущей модификации.
EndStructure

Structure FileInfo         ; Информация о файле.
  Name.s                   ; Имя файла.
  Size.q                   ; Размер файла.
  Date.FileDate            ; Даты, создания, предыдущего доступа и модификации файла.
  Attributes.i             ; Атрибуты файла.
EndStructure

Structure DirInfo          ; Информация о папке.
  Name.s                   ; Имя папки.
  Date.FileDate            ; Даты, создания, предыдущего доступа и модификации папки.
  Attributes.i             ; Атрибуты папки.
  List DirList.DirInfo()   ; Список папок в текущей папке.
  List FileList.FileInfo() ; Список файлов в текущей папке.
EndStructure


Procedure ScanDir(Path.s, *p.DirInfo)
  Protected ID, Info.FileInfo
  
  ID = ExamineDirectory(#PB_Any, Path, "*.*")
  If ID
    
    While NextDirectoryEntry(ID)
      
      Info\Name = DirectoryEntryName(ID)
      
      If Info\Name = "." Or Info\Name = ".."
       Continue
      EndIf
      
      Info\Attributes = DirectoryEntryAttributes(ID)
      Info\Date\Created = DirectoryEntryDate(ID, #PB_Date_Created)
      Info\Date\Accessed = DirectoryEntryDate(ID, #PB_Date_Accessed)
      Info\Date\Modified = DirectoryEntryDate(ID, #PB_Date_Modified)
      
      If DirectoryEntryType(ID) = #PB_DirectoryEntry_Directory ; Это папка.
        If AddElement(*p\DirList())
          *p\DirList()\Name = Info\Name
          *p\DirList()\Attributes = Info\Attributes
          *p\DirList()\Date = Info\Date
          ScanDir(Path+Info\Name+"\", *p\DirList())
        EndIf
      Else                                                      ; Это файл.
        Info\Size = DirectoryEntrySize(ID)
        If AddElement(*p\FileList())
          *p\FileList() = Info
        EndIf
      EndIf
      
    Wend
    
    FinishDirectory(ID)
  EndIf
  
EndProcedure


Dir.DirInfo
ScanDir("C:\Program Files\AutoIt3\", @Dir)

If CreateJSON(0) ; Создание JSON-объекта.
  InsertJSONStructure(JSONValue(0), @Dir, DirInfo)    ; Вставка данных из структуры в JSON.
  SaveJSON(0, "D:\DirInfo.txt", #PB_JSON_PrettyPrint) ; Сохранение JSON на диске.
  FreeJSON(0)
EndIf
Дерево получается за счет того что в строке
Код:
List DirList.DirInfo()
каждый элемент двусвязного списка это структура DirInfo в которой находится этот список. Получается что таким образом можно получить множество вложенных экземпляров структур, которые в свою очередь также могут иметь множество вложенных экземпляров структур. Ограничивается вложенность только размером доступной памяти.
 

Garrett

Модератор
Локальный модератор
Сообщения
3,999
Репутация
967
madmasles [?]
ИМХО, в AutoIt использовать структуры нужно только для вызова API функций
Garrett [?]
ИМХО, это как раз таки досадно


madmasles [?]
Почему удобно?
Прошу.
Код:
// объявления структуры (фактически это тот же самый массив)
struct name {
  char name;
  int age;
};

// объявляем структуру как обычную переменную
struct name User;

// получаем доступ к полям
User.name = "Игорь";
User.age = 20;

//...
printf( "Меня зовут %s мне %d лет\n", User.var,  User.age);
//...

В Autoit Вы такого не увидите, а жаль.

P.S. Кстати, работать со структурами в Autoit`е тоже не очень удобно.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Prog [?]

madmasles [?]
Можно пример скрипта, где структура удобнее массива (без DllCall()).



Добавлено:
Сообщение автоматически объединено:

Garrett,
То, что в других языках удобнее использовать стуктуры я не оспариваю. Меня интересует пример (я поправил свой вопрос) на AutoIt.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Друзья, мы здесь начали говорить о структурах в рамках AutoIt. Давайте не переходить на другие ЯП. В AutoIt структуры были введены преимущественно для работы с DLL и WM. Для хранения данных разработчики не рекомендуют использовать структуры, т.к. на этот счет нет никакой документации. Естественно, речь здесь идет о нативном менеджере памяти. Дабы немного расширить функционал использования выделяемой памяти, я написал альтернативный менеджер памяти, который на данный момент реализован в WinAPISys.au3.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Garrett [?]
В Autoit Вы такого не увидите, а жаль.
Можно по-извращаться :smile:
Код:
oNames = ObjCreate('Scripting.Dictionary')
$oNames.Add('madmasles', ObjCreate('Scripting.Dictionary'))
$oNames.Item('madmasles').Item('name') = 'Александр'
$oNames.Item('madmasles').Item('age') = 55

For $sTmp In $oNames
	ConsoleWrite($sTmp & @LF)
	$oTmp = $oNames.Item($sTmp)
	For $sTxt In $oTmp
		ConsoleWrite($sTxt & @TAB & $oTmp.Item($sTxt) & @LF)
	Next
	ConsoleWrite('---' & @LF)
Next

ConsoleWrite('madmasles --> name: ' & $oNames.Item('madmasles').Item('name') & ', age: ' & $oNames.Item('madmasles').Item('age') & @LF)
 

Prog

Продвинутый
Сообщения
593
Репутация
73
madmasles, речь о том что структуры для некоторых задач, достаточно удобны, но к сожалению в AutoIt они не достаточно функциональны, что ставит под вопрос целесообразность их использования. Неужели разработчики не могут сделать нормальную поддержку структур?
 
Автор
iamOmg

iamOmg

Новичок
Сообщения
97
Репутация
2
Yashied, Спасибо, почитал про структуры, теперь вроде понятнее стало.

Кстати, почему у меня X и Y в структуре показывают одинаковое значение? :scratch: Знаю, тут форум не по PureBasic`у, но многие уже в теме просто.
Код:
Global $massiv1[5][2] = [[4,0], _
						 [-4900,1], _
						 [1400,2], _
						 [1500,3], _
						 [1600,4]]

_Obrabotka_Massiva_Koordinat_DLL($massiv1)
Func _Obrabotka_Massiva_Koordinat_DLL(ByRef $Massiv_vseh_koordinat)
	$razmer_Massiva = UBound($Massiv_vseh_koordinat)
	$tStruct = DllStructCreate("int X["&$razmer_Massiva&"]; int Y["&$razmer_Massiva&"]")
	For $s1 = 0 To $razmer_Massiva - 1
		DllStructSetData($tStruct, "X", $Massiv_vseh_koordinat[$s1][0], $s1+1)
		DllStructSetData($tStruct, "Y", $Massiv_vseh_koordinat[$s1][1], $s1+1)
	Next

	If DllOpen(@ScriptDir & '\Obrabotka_Massiva_Koordinat.dll') = -1 Then Exit 1
	$a_Res = DllCall('Obrabotka_Massiva_Koordinat.dll', 'int', '_TestArray', 'struct*', $tStruct, 'word', $razmer_Massiva)
    If @error Then Exit 2
    ConsoleWrite('$a_Res[0]: ' & $a_Res[0] & @LF)

EndFunc


PB
Код:
Structure ArrA
  X.i[0]
  Y.i[0]
EndStructure

ProcedureDLL _TestArray(*p_StructStart.ArrA, i_Item.u)
  Protected i, s_Str$, Index=0
  
  For i = 1 To i_Item
    s_Str$ + Str(*p_StructStart\X[Index]) + " | "
    s_Str$ + Str(*p_StructStart\Y[Index])
    Index+1
    s_Str$ + #CRLF$
  Next
  MessageRequester("Array", s_Str$)
  ProcedureReturn 1
EndProcedure
 

qqww22

Новичок
Сообщения
115
Репутация
4
Если мне нужно было передать массив или большой размер информации я выделял памяти в длл и возвращал в Autoit начальный адресс.

И банальным MemoryWrite мог создать массив. Доступный для чтения внутри длл без особых танцев с бубном.
 

Prog

Продвинутый
Сообщения
593
Репутация
73
iamOmg [?]
стати, почему у меня X и Y в структуре показывают одинаковое значение?
Потому что структура пустая, т. е. имеет размер 0 байт, а ее поля фиктивные (массивы содержат 0 элементов).
Код:
Structure ArrA
  X.i[0]
  Y.i[0]
EndStructure

Debug "Размер структуры "+SizeOf(ArrA)+" байт"
Debug "Смещение поля 'X' = "+OffsetOf(ArrA\X)
Debug "Смещение поля 'Y' = "+OffsetOf(ArrA\Y)
Нужно явно задать число элементов массива.
Но в данном случае, т. к. заранее неизвестно число элементов массива, можно использовать два экземпляра структуры, присвоив второй адрес массива Y.
Код:
Structure ArrA
  Arr.i[0]
EndStructure

ProcedureDLL _TestArray(*p_StructX.ArrA, i_Item.u)
  Protected i, s_Str$, Index=0
  Protected *p_StructY.ArrA = *p_StructX + i_Item * SizeOf(Integer)
  
  For i = 1 To i_Item
    s_Str$ + Str(*p_StructX\Arr[Index]) + " | "
    s_Str$ + Str(*p_StructY\Arr[Index])
    Index+1
    s_Str$ + #CRLF$
  Next
  MessageRequester("Array", s_Str$)
  ProcedureReturn 1
EndProcedure
 
Автор
iamOmg

iamOmg

Новичок
Сообщения
97
Репутация
2
Prog [?]
Нужно явно задать число элементов массива.Но в данном случае, т. к. заранее неизвестно число элементов массива, можно использовать два экземпляра структуры, присвоив второй адрес массива Y.
Благодарю, теперь работает как надо, хотя я и не очень понял как это происходит
Код:
Protected *p_StructY.ArrA = *p_StructX + i_Item * SizeOf(Integer)
 

Prog

Продвинутый
Сообщения
593
Репутация
73
Массивы в памяти располагаются друг за другом. Зная число элементов и количество байт занимаемых каждым элементом, можно вычислить где заканчивается первый массив и начинается второй. :smile:
В массиве элементы типа Integer (это машинно-зависимый тип и его размер зависит в т. ч. от разрядности программы). Количество байт требуемых для типа Integer вычисляется макросом компилятора SizeOf (выполняется во время компиляции). Далее размер умножается на количество элементов и таким образом получаем размер массива. Прибавив его к адресу структуры получим адрес второго массива.
Допустим что адрес структуры (и в нашем случае также адрес первого массива) равен 1000, и в массиве 10 элементов. Для x86 программы, тип Integer потребует 4 байта памяти, которые умножив на 10 (размер массива) получим число 40. Это и есть смещение от начала структуры где находится второй массив и прибавив его в адресу структуры, получим указатель на второй массив. В этом примере адрес будет 1040.
Как-то так. :smile:
 
Автор
iamOmg

iamOmg

Новичок
Сообщения
97
Репутация
2
Update :search: А обратно как информацию в autoit лучше возвращать? В виде структуры созданной непосредственно в PB или в самом начале давать ссылку на уже готовую структуру и уже её заполнять?
 

Prog

Продвинутый
Сообщения
593
Репутация
73
Можно первым и вторым способом. Но в первом случае больше вероятности "наступить на грабли".
 
Автор
iamOmg

iamOmg

Новичок
Сообщения
97
Репутация
2
А есть возможность увеличивать количество элементов в структуре (созданной в autoit) в PB
 

Prog

Продвинутый
Сообщения
593
Репутация
73
Нет. Если структура создана в AutoIt, то попытка что-то изменить в dll может привести к вылету программы. В таком случае, структуру лучше создавать в dll. Но чтобы не наступить на грабли, нужно запомнить пару простых правил. Структура должна быть глобальной, статической или с динамически выделенной памятью под нее. Если она будет локальной, то велика вероятность ошибки, т. к. локальные структуры уничтожаются при завершении работы функции/процедуры. Если память выделяется динамически, то ее нужно не забывать освобождать, в dll, т. е. там где она была создана.
 
Верх