Juri Shutenko Personal Homepage. Visual FoxPro.

Использование структур в функциях WinAPI.
Ниже представлена в моем переводе статья Кристофа Ланге и Марка Вильдена, которая посвящена описанию класса Struct, позволяющего необыкновенно просто работать со структурами, требующимися для функций API. Оригинал статьи вместе классом иожно взять здесь.

Struct

Written 1999 by Christof Lange and Mark Wilden

Введение
Типы данных языка C
Таблица соответствия типов данных Windows и языка C
Начинаем работать
Использование указателей, субструктур, массивов и буферов
Модификации класса Struct под собственные нужды
Обзор разработки

Введение

В этом документе Christof Lange and Mark Wilden объясняют использование класса Struct в ваших собственных приложениях. Документ охватывает понимание C-структур, перевод их в класс структур VFP и использование объекта struct. Вторая часть этого документа охватывает возможности субклассирования класса struct, что выходит за рамки рассмотрения его простого использования. Это особенно полезно при включении его в каркас (framework) ваших приложений. Третья часть предназначена для разработчиков, которые хорошо понимают структуры и рассматривает предназначение класса struct.

C data types

Концепция типов данных является, по моим наблюдениям, тем, что заставляет многих VFP-разработчиков думать, что программирование с использованием API является трудным. Но это не так! Подобно функциям VFP, API функции ожидают определенного типа данных при их вызове. В VFP мы имеем дело со следующими типами данных: Character, Currency, Date, DateTime, Logical, Numeric. В дополнение к этому мы имеем некоторые типы полей, такие как Memo, General, Double, Float и Integer, которые преобразуются в типы Character или Numeric, когда они считываются в переменные памяти.

Это же применимо и к С; единственное различие заключается в том, что С использует отличные от привычных нам типы данных. Для того, чтобы использовать их, вы должны знать как транслировать типы данных С в типы данных VFP. В основном С использует следующие типы данных:

Integers представляют собой целочисленные величины, которые не имеют долей, точно также, как поля типа Integer в Visual FoxPro. Integers различаются памятью, в которой они размещаются. Размер хранилища также определяет максимальный диапазон значений, который может хранить такой тип данных.  16-bit величина называется short, word и, иногда, short int. 32-bit величина называется integer,long или dword (для double word). Редко используемыми являются 64-bit величины, которые называются longlong или long64.

Кроме того, integers могут объявляться в двух вариантах: со знаком или без - signed и unsigned. Целое число со знаком может быть негативным, но диапазон возможных положительных значений, в таком случае, сокращается вдвое. Например, signed char может содержать величины от -128 до +127, а unsigned char лежать в диапазоне от +0 до +255. Большинство параметров в API функцяих и большинство членов в структурах представляют собой именно этот вид типа данных - integer.

Strings не существуют в С. Это может звучать странно, но на самом деле все strings представляют собой массивы символов. Имеется в основном два типа strings: с фиксированной длиной - fixed length strings, и с переменной длиной - flexible length strings. Фиксированная длина определяется числом символов и обычно заполняется CHR(0). Типичным объявлением является char variable[32], которое определяет строку 32-символьной длины. Что касается второго типа strings, то не имеется ничего такого, чтобы указывало бы на длину строки. По этой причине C имеет страндартное определение. Chr(0) или '\0' в C-программах отмечает конец строки. Когда C-программа хочет определить длину строки, она просматривает все символы в строке и ищет величину CHR(0). Ее позиция, за вычетом 2 представляет собой длину строки.

Strings состоят из символов. Символы могут встречаться в 2 различных форматах: в ANSI кодах или в UNICODE. Код символа ANSI имеет длину в один байт и представляют символ, возвращаемый функцией CHR(). UNICODE-символы требуют двух байтов для каждого символа и для западных шрифтов второй байт всегда равен CHR(0). Поэтому строка Unicode использует CHR(0)+Chr(0) как знак окончания строки.

Floating-point величины представляют собой то, что FoxPro программисты называют numeric-величиной. Она может хранить большие числа м доли. Эти типы данных существуют в двух вариантах: float или single является короткой версий, которая требует 32 bit памяти, точно также, как и величина integer. Она имеет ограниченную точность и может иметь только 6 знаков после запятой без потери точности.  Double требуется 64 bit памяти и она в точности соответсвует тому, что Visual FoxPro использует для numeric величин. Оба типа используются только тогда, когда они действительно нужны, например для вычислений и тому подобного. Они обслуживаются цифровым сопроцессором и требуют некоторого специального обращения. Класс struct поддерживает только double, так как float или single почти не используются в 32-bit системах.

И это все!. C предлагает некоторый особый тип данных, такой как enumeration, но, в основном, перечисленные три типа данных покрывают 99% всех типов данных используемых в программировании API. Это звучит слишком просто, не так ли? Как же обслуживаются объекты, как насчет дат или логических величин? И почему описания функций API содержат все эти типы - HWND, HINSTANCE, POINT и им подобные?

Причиной тому является то, что C имеет особенность, называемую strict-typing.Однажды появившись, переменная одного типа может принимать только те значения, которые соответствуют ее типу. Вы не можете сначала сохранить в переменной строку, а затем присвоить ей численное значение. В Visual FoxPro мы обычно стараемся симулировать подобное путем соглашения о наименованиях. Например, cName представляет собой переменную character-типа, тогда как nCount - численную. Strict-typing предполагает вторую особенность. Вы можете описать новый тип данных, присвоив существующему типу данных новое имя. В Visual FoxPro вы можете сравнить это с alias для таблицы. Вы можете открыть одну и ту же таблицу несколько раз, используя каждый раз различные alias. Каждый alias может имет свой собственный активный индекс, свой собственный фильтр  и свой собственный  указатель записи, но все они ссылаются на одну и ту же таблицу. Тоже и при описании новых типов: каждый тип представляется отличным от других типов, несмотря на то, что внутренне они хранят одно и то же.

Windows усложняет использование этой концепци. К примеру, LONG в действительности представляет собой long signed int. UINT - long unsigned int. Оба являются 32-bit integers. Эти имена определены в нескольких include-файлах. Если вы приобрели Visual Studio с VC++ compiler, вы можете найти эти файлы в каталоге \VC98\Include. В довершение всего, вы можете использовать такие user-defined типы в объявлениях типа повторно.

Определение того, какой из базовых типов данных действительно представляет тип данных используемый в функции API, является одной из тяжелейших частей в использовании API функций. Правило большого пальца – если вы не можете найти double, char или str где-либо в имени, тогда это обычно 32-bit integer. Потребуется некоторое время для овладения и выработки навыка, но после вы будете запросто преобразовывать типы данных. Следуюшая таблица должна помочь вам в начале этого процесса:

 
Типы данных Windows Основные типы данных языка C
BOOL 32-bit signed integer. 0 означает false, все остальное означает true.
BOOLEAN тоже самое, что и BOOL.
BYTE 8-bit integer
CHAR 8-bit integer. Вы можете использовать CHR().
COLORREF 32-bit unsigned integer. Подобную величину возвращает функция RGB().
DWORD 32-bit unsigned integer
FLOAT float
HANDLE 32-bit unsigned integer
HBITMAP 32-bit unsigned integer
HBRUSH 32-bit unsigned integer
HDC 32-bit unsigned integer
HFONT 32-bit unsigned integer
HGDIOBJ 32-bit unsigned integer
HICON 32-bit unsigned integer
HINSTANCE 32-bit unsigned integer
HPEN 32-bit unsigned integer
HWND 32-bit unsigned integer
LONG 32-bit signed integer
SHORT 16-bit signed integer
TCHAR ANSI code character for ANSI functions, UNICODE character for UNICODE functions.
UCHAR ANSI code character
UINT 32-bit unsigned integer
ULONG 32-bit unsigned integer
USHORT 16-bit unsigned integer
WCHAR UNICODE character
WORD 16-bit unsigned integer

В приведенном выше списке вы можете видеть, что логические величины трактуются как numeric величины. По определению  0 означает .F., все остальное - .T. В Visual FoxPro вы используете нечто подобное

lReturnValue = ( nReturnValue # 0 )

для преобразования численной величины в логическую переменную Visual FoxPro.

Другой концепцией, широко используемой в С, являются указатели (pointers). Указатель представляет собой переменную, которая содержит адрес величины, на которую он указывает. Тип указателя всегда определяется типом величины, на которую он указывает; его размер всегда равен 4-м байтам. Например указатель на SHORT представляет собой 32-bit integer величину, как и указатель на любой другой тип данных. Величиной указателя является адрес памяти, где хранится 16-bit short integer. При передаче указателя в другую функцию последняя, вызываемая, функция может изменить значение величины в указанном адресе. Указатели могут представлять большие трудности для C программистов. Одна причина состоит в том, что указатель может указывать на другие указатели, которые указывают на действительную величину. Похоже звучит слишком сложно? В действительности, Visual FoxPro поддерживает сходную систему. Взгляните на следующий кусок кода:

C code Visual FoxPro code
LONG nValue=25;
LONG* lpPointer = &nValue;
// access nValue via the pointer
*lpPointer;
nValue = 25
lpPointer = "nValue"
* access nValue via the pointer
EVAL(lpPointer)

В обоих случаях последняя линия кода возвращает 25 - содержимое переменной nValue. Оба кода не явлются одинаковыми; evaluation не то же самое, что переадресовка (de-referencing), просто делает это чисто. Но этот код позволит вам думать об указателях в терминах кода Visual FoxPro. Когда вы вызываете API функцию она принимает указатель на величину, вы присваиваете величину переменной Visual FoxPro и посылаете переменную по ссылке. Visual FoxPro достаточно умен,  чтобы послать в этом случае адрес к переменной. Например:

DECLARE APIFunction in Win32API INTEGER @lpPointer

nValue = 24
APIFunction(@nValue )

Я использовал одинаковые имена в коде, приведенном выше, чтобы связь была очевидной. APIFunction() может теперь обращаться к переменной Visual FoxPro variable и менять ее значение. nValue будет иметь уже другое значение при ее возврате функцией. Этот метод широко используется для возврата разных величин в одном вызове API.

Функции API обращаются со строками немного по другому. Как уже упоминалось, строки в действительность представляют собой массив символов. В Visual FoxPro массив не может быть передан в функцию по величине. Если вы вызываете функцию Visual FoxPro нижеприведенным образом:

Local laArray[10]
Function(laArray )

то функция получит только первое значение. Вместо этого вы можете послать в функцию массив по ссылке используя синтаксис:

Function( @laArray)

Это справедливо также и для программирования API. API может получить значения массива только в том случае, если массив передан по ссылке. Так как строка представляет собой массив, то она всегда пересылается по ссылке, причем не имеет значения - как вызывается функция API. Передача по ссылке означает, что вызывающая функция может изменить значение переданного параметра, однако этого не произойдет, если в объявлении функции командой DECLARE вы опустите символ @ перед переменной. Как это происходит? Когда вы посылаете строку по величине в функцию API, Visual FoxPro копирует строку и посылает копию по ссылке. В противном случае он посылает первоначальную строку. Даже если API функция изменит значение строки, это не скажется на значении строки Visual FoxPro. В кодах программирования API в С используется объявление CONST, если строка посылается по величине.

Остается одна проблема. Как вы узнаете, когда API функция получает параметр как указатель, что равнозначно передаче по ссылке для Visual FoxPro. В чистом С коде, символ “*” перед типом указывает, что мы имеем дело с указателем, как это показано в коде приведенном выше, в таблице. Однако такое соглашение почти не используется в программировании API. Обычно вы находите в описаниях функций объявление в таком виде:

DWORD GetShortPathName(
   LPCTSTR lpszLongPath,
   // pointer to a null-terminated path string
   LPTSTR lpszShortPath,
   // pointer to a buffer to receive the 
   // null-terminated short form of the path
   DWORD cchBuffer
   // specifies the size of the buffer pointed 
   // to by lpszShortPath
);

Первый параметр получает тип LPCTSTR. Этот тип данных состоит из трех частей: LP означает Long Pointer, другими словами, просто указатель. C сокращение для Constant, то есть API функция не изменяет этот параметр при вызове. TSTR представляет собой действительный тип. В списке типов данных вы можете найти похожий тип TCHAR. TSTR почти тоже, но подразумевает не единичный символ, но строку, которая может быть ANSI или UNICODE в зависимости от того, вызываете-ли вы функцию ANSI или UNICODE.

Второй параметр очень похож на первый, но не содержит С в середине. Другими словами, API функция желает изменить этот параметр. Хотя он остается указателем. Третий параметр представляет собой DWORD. В списке типов данных вы можете найти, что он представляет собой 32-х битное целое число без знака (unsigned 32-bit integer). Полное объявление этой функции в Visual FoxPro будет выглядеть следующим образом:

DECLARE GetShortPathName in Win32API ;
   STRING lpszLongPath, ;
   STRING @lpszShortPath, ;
   INTEGER cchBuffer

К счастью, API строго следует этому соглашению по наименованиям. Если тип данных начинается с LP, P или оканчивается на _PTR, то обычно это указатель и вы должны при его объявлении в Visual FoxPro использовать "@" в команде DECLARE. Когда параметр строковый и в объявлении типа используется LPC, вы можете опустить @, но это справедливо только для строк!

К этому моменту мы рассмотрели типы данных и указатели, но как преобразовывать их в код Visual FoxPro? А как насчет структур? Все то, о чем я буду рассказывать в дальнейшем может быть использовано в обычном коде Visual FoxPro code, особенно при использовании команд DECLARE-DLL. Концепция структур очень схожа с концепцией записей, концепцией, с которой вы хорошо знакомы. И действительно, в некоторых языках структуры называются записями. Запись определяется структурой таблицы. Каждое поле получает имя и тип данных. В структурах то же самое. Они похожи на определения. Структура имеет многочисленных членов с именем, типом данных и позицией внутри структуры. Структура используется в случае, когда одной величины недостаточно. Например, точка на экране требует двух единиц информации, координаты x и y. В таких случаях, Windows API использует структуру POINT, которая определена как:

typedef struct tagPOINT {
   LONG x;
   LONG y;
}
POINT;

Когда вы вызываете API функцию, которая требует экранных координат, вы можете заполнить ее члены x и y значениями соответствующими позиции и передать ссылку на структуру функции API. Если вы сравните это с курсором, вы найдете много общего:

CREATE CURSOR POINT ( ;
   x I(4), ;
   y I(4) ;
)

Для того, чтобы передать запись в такой курсор, вы можете использовать команду SCATTER NAME loRecord и передать соответствующую ссылку на объект в функцию. Тоже справедливо и для структуры, за исключением того, что структуры более гибки. Например, структура может содержать другую структуру, или указатель на другую структуру, или величину.

Проблема заключается вопросе - как послать структуру в API функцию? Вы не можете послать вместо структуры курсор, хотя концепции схожи. Ответ на этот вопрос кроется в способе, каким структура заносится в память. Как я упоминал ранее, каждый тип данных определяет размер переменной. 32-bit integer величина требует 32-bit или 4 байба памяти. В памяти эти величины хранятся в специальном формате, который используется процессором. Месторасположение в памяти идентифицируется адресом, точно также, как запись идентифицируеся ее номером.

Структура хранится в памяти так же, как и простой тип данных. Предположим, что структура POINT хранится по адресу adr. Тогда adr+0 доджен содержать члена структуры X. adr плюс размер члена структуры X должен быть адресом, по которому расположен член структуры Y, другими словами, adr+4 хранит значение Y. Полная структура занимает 4 байба + 4 байта = 8 байтам. Это означает, что структура занимает блок памяти длиной в 8 байт. В таком блоке памяти, индивидуальные члены структуры хранятся в специальном формате.

Как хранит строку Visual FoxPro? Он отводит для нее то количество памяти, которое необходимо, чтобы вместить строку. Если у вас строка имеет длину в 8 байт, Visual FoxPro отводит для нее 8 байт памяти. В обоих случаях, физически мы имеем 8 байт выделенной памяти. Это и есть причина, по которой структура посылается из Visual FoxPro как строка, как это и показано в "Solution.App", которая поставляется вместе с Visual FoxPro. Действительно трудной частью является преобразование всех этих величин в формат, который ожидает получить функция API. А это уже работа для класса struct.

Скажем, вы не понимаете всех деталей, но уже держите в уме, что структура состоит из нескольких величин, каждая из которых имеет тип данных, который определяет ее размер и все величины имеет фиксированную позицию в структуре. Функция API не адресуется к членам структуры по именам, но адресуется по позиции внутри структуры.

Начинаем работать

В предыдущей части я объяснил, что представляет из себя структура. Если вы сравните ее с объектами, то отметите, что оба во многом схожи и используют много общего. C-объявление:

typedef struct tagPOINT {
   LONG x;
   LONG y;
}
POINT;

смотрится почти одинаково с определение класса VFP в программном коде:

DEFINE CLASS POINT AS Struct
   x = 0
   y = 0
ENDDEFINE

В этом и заключено то, как класс struct осуществляет структуру. В Struct.Vcx вы найдете класс Struct, который является родительским классом для всех структур, которые вы пожелаете определить. Каждая новая структура представляет собой его субкласс. Ранее я подчеркивал важность двух аспектов структуры, которые не охватываются определением класса, показанном выше. Это не совсем точное определение, что тип данных представляет индивидуальное свойство и, хотя это менее очевидно, порядок этих свойств не совсем ясен. Поэтому для определения обеих неясностей используется дополнительное свойство cMembers. Это свойство содержит comma-separated список с определением типа. Порядок, в котором эти свойства перечислены определяет порядок, в котором они добавляются в структуру. Полное определение выглядит так:

DEFINE CLASS POINT AS Struct
   x = 0
   y = 0
cMembers = "l:x, l:y"
ENDDEFINE

Определение каждого члена содержит определение типа, разделяемое двоеточием, за которым следует имя члена, используемого в классе. "l" код для 32-bit integer; как сокращение для long. Реально, полное определение позволяет вам разместить комментарий перед определением типа с разделением пробелом; это может быть использовано, например, для указания C типа даных. Свойство cMembers должно выглядеть так:

cMembers = "LONG l:x, LONG l:y"

Определение структуры является поэтому вовсе не трудным, когда вы определили базовый тип данных для каждого члена структуры. Общий синтаксис для cMembers выглядит так:

cMembers = "[text], type:property [, [text] type:property...]"

Типы зависят от базовых C типов данных, которые я объяснял выше: numeric, strings и structures. Следующий раздел объясняет синтаксис для каждого индвидуального типа данных. В диаграмме синтаксиса "[]" указывают на необязательные элементы. "|" разделяет альтернативные коды. Для численных величин тип определяется как:

[p] [s|u] l|w|b|d

Код Описание
p Указатель на численную величину. В C-определениях структуры вы найдете такой тип, начинающийся с символов LP, например, LPDWORD.
s Величина со знаком. Это код по умолчанию, если вы опустите s и u. Величина со знаком может быть, разумеется, отрицательной и в то же самое время она уменьшает диапазон положительных величин вдвое. Большинство типов данных представляют собой величину со знаком, если С определение не начинается с символа unsigned, например, UINT. Вы можете использовать этот символ единственно в том случае, когда хотите четко представлять, что величина является величиной со знаком.
u Величина без знака и поэтому не может быть отрицательной. Эти типы редко используются в программировании Windows API.
l (Это маленькая "L"!). Величина представляет собой 32-bit integer. Большинство типов данных, используемых в программировании Windows API представляют собой 32-bit integer величины. Список наиболее общих типов данных можно найти в предыдущем разделе.
w Это 16-bit integer. Этот тип данных используется реже и соответствует ключевому слову SHORT команды DECLARE-DLL.
b Это 8-bit integer, или byte. Этот тип данных почти никогда не используется в простых членах, но обычно используется в массиве байт.
d Это число с плавающей запятой. Этот тип также редко используется. "d" означает "double".

Как я упоминал ранее, существует два типа строк. Одна из них имеет фиксированную длину. В определении языка C, она обычно выглядит как массив, потому что длина декларирована в квадратных скобках, следом за именем, то есть:

TCHAR FileName[260];

которая в данном случае определяет строку с фиксированной длиной в 260 символов. Другой тип представляет собой строку с переменной длиной, где для обозначения конца строки используется символ CHR(0). Этот тип почти никогда не включается непосредственно в структуру в явном виде, а только как указатель. Например,

LPTSTR lpszFileName;

что означает, что эта строка представляет собой null-terminated строку, где только адрес действительно строки включен в структуру. Причиной этому является то, что функция API получает доступ к индивидуальным членам структуры по их позиции в структуре. Так как строки переменной длины по определению имеют разные длины, все подобные члены не могут иметь фиксированной позиции. Указатель, с другой стороны, всегда имеет длину в 4 байта, независимо от того, на строку какой длины он указывает. Синтаксис типа для строкового члена структуры выглядит так:

[p] [x|2] ([0]c)|z [length]

Код Описание
p Представляет собой указатель на строку. Он обычно используется для строк переменной длины, причем реже, чем для строк с фиксированной длиной. Вы можете определить является-ли член структуры указателем посмотрев на тип данных. Обычно объявление этого типа данных в структуре начинается с "LP" или "P". Менее общим типом данных является указатели, чьи обявления заканчиваются на "_PTR" или они следуют за символом "*". Таким образом,указатели на строку выглядят как LPTSTR, LPCHAR, LPWSTR… Этот код обычно используется вместе с кодом "z".
x В зависимости от операционной системы, на которой запущено ваше приложение используется либо UNICODE, либо ANSI. Символ UNICODE занимает в длину 2 байта, тогда как символ ANSI только 1 байт. Когда вы используете такой символ, вы должны быть уверены, что вы определили функцию API соответствующую операционной системе. ANSI версия функции API обычно заканчивается на символ "A", тогда как UNICODE версия – на "W". Если вы не укажете версию, VFP автоматически добавит "A" к имени функции. Документация каждой функцию содержит информацию о том – имеются или нет разные версии функции для UNICODE и ANSI. Если вы вызываете ANSI версию на Windows NT, Windows NT автоматически преобразует параметры в UNICODE и внутренне вызовет UNICODE версию. Таким образом, обычно вам не нужно заботиться об этом, если только вы специально не пожелаете разрабатывать приложение, которое не использует не Western шрифты, а, к примеру, приложение для Asian стран.
2 Эта строка должна всегда использовать UNICODE, вне зависимости от того, какая операционная система используется. Некоторые функции Windows 9x API всегда ожидают параметров в Unicode. В таком случае этот код необходим. Вы можете использовать этот код, когда вы определеяете структуру для Windows NT или Windows 9x
0 Обычно в случаях, когда вы определяете строку фиксированной длины она завершается символом CHR(0), но вас самих этот символ не интересует, вас интересует только строка перед ним. Этот код может быть использовать вместе с кодом "c" и означает, что символы CHR(0) будут удалены из строки, перед тем, как она будет сохранена в свойствах.
c Строка фиксированной длины. Обычно вы задаете еще ее длину, однако если вы не сделали этого, будет использовано текущее значение. Класс struct заполняет строку символами CHR(0) перед тем, как передать строковый параметр функции API. Если строка в объекте struct больше, чем определено стрктурой, она будет обрезана.
z Null-terminated строка. Класс struct добавляет необходимый код терминатора, до того, как передать строку в  структуру.

Некоторые структуры содержат другие структуры - либо полностью, либо указатель на них. С помощью класса struct вы определяете вложенную структуру во втором классе и затем присваиваете ссылку на объект внутренней (вложенной) структуры свойству внешней (основной) структуры. Таким образом вы можете связать столько структур, сколько пожелаете. Такое, например, требуется для многих структур содержащих информацию о файде. Отметка времени (time stamp) хранится в отдельной структуре FILETIME, которая в свою очередь является часть информационной структуры. Для объявления свойства в качестве субструктуры, вы можете использовать следующий синтаксис:

[p]o

Код> Описание
p Указатель на субструктуру. В структуру включается только адрес субструктуры. Обычно объявления такого типа данных начинается с символов LP, например LPFILETIME.
o Указывает на то, что вы желаете вставить структуру. Это свойство должно содержать ссылку на объект другой структуры и не может быть THIS, так как это приведет к неопределенной рекурсии.

Конечно, это все привлекательная абстракция. Следующая таблица содержит коды для большинства наиболее общих типов данных Windows API.  xxx указывает на то, что вы должны вставить длину строки.

Тип данных Код Тип данных Код Тип данных Код
BOOL l BOOLEAN l BYTE b
CHAR cxxx COLORREF ul DWORD ul
DOUBLE d HANDLE ul или l HBITMAP ul или l
HBRUSH ul или l HDC ul или l HFONT ul или l
HGDIOBJ ul или l HICON ul или l HINSTANCE ul или l
HPEN ul или l HWND ul или l LONG l или sl
SHORT w или sw TCHAR x0cxxx или 0xxx или 20cxxx UCHAR 0cxxx
UINT ul ULONG ul USHORT uw
WCHAR 20cxxx WORD uw LPBOOL pl
LPBYTE pb LPCOLORREF pul LPCSTR pz
LPCTSTR pz или pxz или p2z LPCVOID pz LPCWSTR p2z
LPDWORD pul LPHANDLE pul или pl LPINT psl или pl
LPLONG psl или pl LPSTR pz LPTSTR pz или pxz или p2z
LPVOID pz LPWORD puw LPWSTR p2z

С помощью этой таблицы вы будете способны описать субкласс класса Struct, добавляя членов структуры и устанавливая свойство cMembers так, как это требуется. Вы можете описать структуру либо в коде, либо в дизайнере классов. Класс Struct основан на классе Label, так как это позволяет вам установить в caption что-нибудь значимое, ну скажем имя структуры. Когда вы добавите несколько структуру в форму вы сможете легко их различать. WinStruct.Vcx содержит несколько предопределенных структур, которые вы сможете использовать по ссылке.

После того, как вы опрелили структуру, ее использование столь же просто, как и использование других Visual FoxPro классов. Концепция этого класса проста. Из приложения Visual FoxPro, вы имеете доступ только к объекту struct со всеми его свойствами. Когда вы вызываете функцию API, вы запрашиваете строку, которая представляет структуру с ее текущими величинами. Эта строка представляет собой копию вашей структуры в формате, который понятен функции API. Функция API может изменить эту строку, но конечно же не имеет доступа к объекту FoxPro. Далее, после возврата строки API функцией, вы посылаете иодифицированную строку объекту структуры и «говорите» ему, чтобы он переписал свои свойства новыми значениями.

Для того, чтобы получить такую строку, вы вызываете метод GetString(). Обратная функция для перезаписи свойств объекта - SetString(). Если функция API ожидает структуру и не изменяет ее, код может выглядеть следующим образом:

Local loStruct
Declare APIFunction in Win32API String lpStructure
loStruct = CreateObject( "Structure" )
loStruct.Property1 = SomeValue
loStruct.Property2 = SomeValue
APIFunction(loStruct.GetString() )

Когда функция API изменяет строку, вы посылаете ее по ссылке. В таком случае вы не можете вызвать метод GetString() с вызовом API, но вы присваиваете ее переменной Visual FoxPro:

Local loStruct, lcStruct
Declare APIFunction in Win32API String @lpStructure
loStruct= CreateObject( "Structure" )
lcStruct = loStruct.GetString()
APIFunction(@lcStruct )
loStruct.SetString(m.lcStruct )
? loStruct.Property1
? loStruct.Property2

В основном, вы вызываете GetString() до вызова функции API, и SetString() после. Когда структура содержит указатели – любой из членов требует код "p" в определении типа – строка, возвращаемая GetString() становится invalid как только вы вызовете GetString() во второй раз. Другими словами, в таком случае вы не должны сохранять строку для использования в дальнейшем, так безопаснее вызывать GetString() каждый каз, когда вам необходима строка. Если структура не содержит указателей, вы можете без опаски сохранять строку и использовать повторно объект структуры в дальнейшем. Это показано в примере ResChange.Scx.

Кроме этих двух методов часто требуется метод SizeOf(). Он возвращает размер структуры в байтах, или, в терминах Visual FoxPro, длину возвращенной строки. Для функции API структура лишь блок памяти, который содержит информацию о том, насколько велика структура, которую вы передаете. Часто структуры изменяются с различными версиями Windows, добавляются новые члены, как только они становятся необходимыми. Поэтому первый член многих структур содержит размер структуры. Функция API использует это значение для того, чтобы определить в какую версию функции вы посылаете структуру. Поэтому размер структуры должен точно совпадать с тем значением, которого ожидает функция. В C коде  вы обычно находите выражение типа: «dwSize должна быть инициализирована с sizeof(STRUCTURE) перед вызовом этой функции». В C sizeof() представляет собой макрос, который возвращает размер структур определенного типа. Для того, чтобы сделать транслятор понятным для использования вами, метод получил тоже самое имя. Типичный вызов с такой структурой выглядит так:

Local loStruct
loStruct = CreateObject( "STRUCTURE" )
loStruct.dwSize = loStruct.SizeOf()
APIFunction(loStruct.GetString() )

Конечно, мы можете инициализировать размер этого члена структуры также и в событии Init() вашего класса структуры, если вам нравится. Эти три метода, свойство cMembers и субклассирование структуры вполне достаточно для многих структур, которые вы найдете в Windows API.

Использование указателей, субструктур, массивов и буферов

Простые структуры имеют только простые типы данных или указатели к простым типам данных, подобно тем, которые я описывал выше. За исключением указателей, большинство этих структур достаточно легко сконструировать в Visual FoxPro. Имеется несколько более или менее полезных инструментов, способных помочь в этом процессе. Сложные структуры содержат субструктуры, массивы, указатели на другие структуры и хэндлы буфферов. В этой области класс просто сияет, потому что не требуется ничего такого особенного для их описания.

Некотрые структуры требуют массива элементов. Например. структура CHARSET включает в себя массив из трех 32-bit integers

typedef struct tagCHARSET { 
   DWORD aflBlock[3]; 
   DWORD flLang; 
}
CHARSET;

Массивы обслуживаются прозрачно компонентом класса Struct. Все, что вам нужно сделать, так это описать массив. Класс Struct автоматически определяет, что свойство представляет собой массив и обрабатывает его соответствующим образом. Объявление такой структуры в Visual FoxPro выглядит так:

DEFINE CLASS CHARSET AS Struct
   DIMENSION aflBlock[3]
   flLang = 0
   cMember = "ul:aflBlock, ul:flLang"
ENDDEFINE

Вы можете обращаться к aflBlock также, как и к любому свойству простого массива. GetString() возвращает строку в 16 символов длиной, как вы и ожидали. Однако часто вместо массива простых типов данных, таких как DWORD, вы имеете дело с массивом других структур. Структура AXESLIST представляет собой хороший пример подобного рода структур.

typedef struct tagAXESLIST {
   DWORD axlReserved;

DWORD axlNumAxes;

AXISINFO axlAxisInfo[MM_MAX_NUMAXES];

} AXESLIST

Последний член axlAxisInfo представляет собой массив структур AXISINFO. Для внедрения ее, в первую очередь вы должны знать структуру AXISINFO:

typedef struct tagAXISINFO {
   LONG axMinValue;
   LONG axMaxValue;
   TCHAR axAxisName[MM_MAX_AXES_NAMELEN];
}
AXISINFO

Ниже приведена простая структура, которую вы иожете объявить в Visual FoxPro:

DEFINE CLASS AXISINFO AS Struct
   axMinValue = 0
   axMaxValue = 0
   axAxisName = ""
   cMembers = "l:axMinValue,l:axMaxValue, 0c16:axAxisName"
ENDDEFINE

Размер этой структуры равен 24 байтам. Если вы хотите вызывать функцию UNICODE, измените тип последнего параметра на «20c16». Для класса Struct массив структур, в действительности, представляет собой массив объектов. Поэтому объявление структуры AXESLIST выглядит следующим образом:

DEFINE CLASS AXESLIST AS Struct
   axlReserved = STAMP_AXESLIST
   axlNumAxes = 0
   DIMENSION axlAxisInfo[MM_MAX_NUMAXES]
   cMembers = "ul:axlReserved,ul:axlNumAxes, o:axlAxisInfo"
ENDDEFINE

Код "o" последнего члена означает, что свойство axlAxisInfo содержит другую структуру. И поскольку это массив, класс Struct автоматически просматривает массив и преобразует все объекты структуры. STAMP_AXESLIST и MM_MAX_NUMAXES представляют собой константы, которые вы найдете в WinGDI.H file. Это файл поставляется с Microsoft Visual C++. Когда вы субклассируете структуру, которая содержит другие структуры, то сначала вы описываете внутреннюю структуру и затем  затем присваиваете субструктуру ссылке или массиву, как показано в примере выше. Код в VFP выглядит как:

Local loAxesList, lnAxis
loAxesList= CreateObject( "AXESLIST" )
For lnAxis = 1 to MM_MAX_NUMAXES
   loAxesList.axlAxisInfo[lnAxis] = CreateObject( "AXISINFO" )
Endfor

Это не выглядит слишком сложным, не так-ли? Таким-ли образом вы опишите субструктуру после описания внешней структуры, или поместите цикл FOR в событие Init() класса AXESLIST, ваше дело. Класс Struct может обслуживать оба. После того. как опишите объект подобный этому, вы можете обращаться к каждому свойству структуры, например:

loAxesList.axlNumAxes = 3
loAxesList.axlAxisInfo[3].axAxisName = "third axis"

GetString() возвратит строку, которая содержит структуру AXESLIST и все шестнадцать структур AXISINFO. Использование сложной структуры , наподобие этой, столько же просто,как и использование простых структур. Одну только вещь необходимо соблюдать, когда вы работаете с массивом в структуре. Всегда, когда вы изменяете размер массива, должен быть вызван метод Requery() объекта структуры. Это необходимо потому, что изменение размера массива кроме того изменяет размер структуры и, что более важно, позиции окружающих членов.

Субструктуры не всегда используются в связке с массивом. Часто структура может содержать несколько субструктур. Структура WINDOWPLACEMENT, например, содержит информаицю о позиции окна на рабочем столе в различных режимах:

typedef struct _WINDOWPLACEMENT { 
   UINT length; 
   UINT flags; 
   UINT showCmd; 
   POINT ptMinPosition; 
   POINT ptMaxPosition; 
   RECT rcNormalPosition; 
}
WINDOWPLACEMENT;

POINT представляет собой структуру, содержащую координаты X и Y. RECT содержит 4 координаты - left, top, right и bottom. Для начала просто посмотрим на них обеих. Я перейду непосредственно в Visual FoxPro код, так как они представляют действительно простые структуры:

DEFINE CLASS POINT AS Struct
   x = 0
   y = 0
   cMembers = "l:x, l:y"
ENDDEFINE

DEFINE CLASS RECT AS Struct
   left = 0
   top = 0
   right = 0
   bottom = 0
   cMembers = "l:left, l:top, l:right,l:bottom"

ENDDEFINE

Но WINDOWPLACEMENT содержит 2 инстанции структуры POINT и одну инстанцию структуры RECT . В этот раз я опишу субструктуры прямо в событии Init(), просто для того, чтобы показать как это может быть сделано:

DEFINE CLASS WINDOWPLACEMENT AS Struct
   length = 0
   flags = 0
   showCmd = 0
   ptMinPosition = NULL
   ptMaxPosition = NULL
   rcNormalPosition = NULL
   cMembers = "ul:length, ul:flags,ul:showCmd, o:ptMinPosition, " + ;
              "o:ptMaxPosition,o:rcNormalPosition"

PROCEDURE Init
   This.ptMinPosition = CreateObject("POINT" )
   This.ptMaxPosition = CreateObject("POINT" )
   This.rcNormalPosition = CreateObject("RECT" )
   DODEFAULT()
ENDPROC
ENDDEFINE

Предположим у вас имеется хэндл окна lnHWNDи теперь вы можете вызвать функцию GetWindowPlacement() следующим образом:

Local loWindowPlacement, lcStruct
loWindowPlacement = CreateObject( "WINDOWPLACEMENT" )
lcStruct= loWindowPlacement.GetString()
GetWindowPlacement(lnHWND, @lcStruct )
loWindowPlacement.SetString(lcStruct )

Все свойства, включая свойства в субструктурах обновлены величинами позиции указанного окна. Например, вы можете использовать следующее выражение для получения позиции «left» окна, если оно отображается в восстановленной позиции:

loWindowPlacement.rcNormalPosition.Left

Некоторые API функции не желают получить в качестве параметра единичную структуру, они иногда требует массива структур. Например, функция Polygon():

BOOL Polygon(
   HDC hdc,  // handle to device context
   CONST POINT *lpPoints,  // pointer to polygon's vertices
   int nCount  // count of polygon's vertices
);

В функции второй параметр выглядит как просто указатель на единичную структуру POINT, но из описания следует, что это предполагает массив:

lpPoints: Указатель на массив структур POINT, которые определяют вершины полигона.

Как внедрить такое? Вы не можете послать массив Visual FoxPro в функцию API, вам необходима строка, содержащая все элементы массива. Структура POINT дает вам строку для одной структуры. Можно было бы описать массив Visual FoxPro и присвоить каждому элементу объект структуры POINT. Когда вы вызовете функцию API, вы могли бы просмотреть массив, вызвать метод GetString() для каждого элемента массива и собрать все строки. Но это неудобное решение. Вместо этого вы определяете структуру, которая содержит только массив:

DEFINE CLASS ArrayPOINT AS Struct
   DIMENSION Points[3]
   cMembers = "o:Points"
ENDDEFINE

Вы найдете этот класс в WinStruct.Vcx. Он используется в примере GDI2.Scx с функцией упомянутой выше. Теперь вы можете использовать цикл для того, чтобы присвоить объект POINT каждому элементу массива:

Local loArray, lnPoint
loArray = CreateObject( "ArrayPOINT" )
For lnPoint = 1 to 3
   loArray.Points[lnPoint] = CreateObject("POINT" )
Endfor

Для вызова функциии API Polygon(), вы теперь можете послать строку, возвращаемую объектом ArrayPOINT:

Polygon( nHDC, loArray.GetString(), 3 )

Если функция не желает указатель на массив структур, как это произошло в разобранном случае, но желает получить указатель на массив указателей структур, все что вам нужно сделать, так это заменить "o:Points" на "po:Points". Указатели внутри структуры использовать несложно. Все что нужно сделать, так это вставить "p" впереди кода в классе. Например, структура DOCINFO содержит указатель на строку:

typedef struct DOCINFO { 
   int cbSize; 
   LPCTSTR lpszDocName;
   LPCTSTR lpszOutput;
   LPCTSTR lpszDatatype;
   DWORD   fwType;  // Windows 95 only; ignored on Windows NT 
}
DOCINFO; 

Эта структура используется Windows API для печати. «PrtDemo.Prg» представлет собой пример, который использует эту структуру. Кроме того, вы найдете его в библиотеке класса WinStruct.Vcx. Объявление Visual FoxPro выглядит так:

DEFINE CLASS DOCINO AS Struct
   cbSize = 0
   lpszDocName = ""
   lpszOutput = ""
   lpszDatatype = ""
   fwType = 0
   cMembers = "l:cbSize, pz:lpszDocName,pz:lpszOutput," + ;
              "pz:lpszDatatype, ul:fwType"
ENDDEFINE

Строки не имеют фиксированной длины. Поэтому структуры содержит только их адреса, а реальные строки размещаются в блоках памяти. Вам не нужно беспокоиться о них, так как класс struct сделает это за вас. Все что вам нужно сделать,так это присвоить строки для loDocInfo.lpszDocName. Когда вы вызовете GetString(), это свойство скопируется в память, адрес будет преобразован и встален в структуру. Если Windows изменит строки, будет затребована новая строка, когда вы вызовете SetString() и соответственно обновится свойство.

Тем не менее имеется одна вещь, о которой надо позаботиться, когда вы имеете дело с указателями в любых вариантах. Блок памяти выделяется, когда вы описываете структуру и освобождается, когда вы ее уничтожаете. По умолчанию это 8 KB, который используется всеми указателями структуры. Если требуется меньше, это без проблем, а если больше, вам необходимо увеличить соответсвующее значение в классе структуры. nMemorySize определяет количество памяти в байтах, которые выделяется под указатели. Если вы не выделите достаточного объема памяти GetString() вернет пустую строку, вместо структуры.

К этому моменту мы рассмотрели довольно-таки много, но остается еще одна вещь: буфферы. Для понимания необходимости буфферов вы должны знать, что Windows никогда не выделяет память по собственному почину. Всегда, когда вы запрашиваете информацию через Windows API, вам необходимо предоставить столько памяти, сколько необходимо. С некоторыми структурами это сделать сложно. Структура PRINTER_INFO_1,к примеру, содержит три указателя на строки:

typedef struct _PRINTER_INFO_1 { // pri1 
   DWORD  Flags; 
   LPTSTR pDescription;
   LPTSTR pName; 
   LPTSTR pComment;
}
PRINTER_INFO_1;

Эти строки предоставляются функцией API GetPrinter(). Полная структура имеет длину в 16 байт, 4 байта для DWORD, и 4 байта для каждого указателя. Это означает, что если вы послали строку, которую вернул метод GetString (), вы отправляете только 16 байт памяти и здесь не имеется места, где бы Windows сохранил строку. Поэтому GetPrinter() получает указатель на буффер:

BOOL GetPrinter(
   HANDLE hPrinter,    // handle to printer of interest
   DWORD Level,        // version of printer info data structure
   LPBYTE pPrinter,    // pointer to array of bytes that receives
                       // printer info structure
   DWORD cbBuf,        // size, in bytes, of the pPrinter buffer
   LPDWORD pcbNeeded   // pointer to variable with count of bytes 

// retrieved (or required) );

Буффер, или как он называется в описании - массив байт – представляет собой просто блок памяти. Первая часть этого буффера содержит структуру PRINTER_INFO_1, остаток выделен и Windows может быть уверен, что это память доступна для использования. Функция API GetPrinter() хранит три строки в области памяти, сразу за структурой. Указатели соответственно указывают на эти области. Например, буффер начинается с адреса 1000, первая строка хранится по адресу 1016 и pDescription будет указывать на этот адрес.

Я упоминал несколько раз, что строки Visual FoxPro не имеют фиксированного адреса в памяти, так как Visual FoxPro перемещает их для оптимизации использования памяти. Поэтому вы не можете просто вызвать GetString() и добавить, скажем, строку в 1000 символов длиной, для формирования буффера. После вызова API, строка может располагаться по другому адресу, но указатель внутри структуры будет по-прежнему указывать на старый и уже не действительный адрес строки.

Компонент Struct предлагает решение и для этой проблемы. Для буфферов вы можете использовать методы GetPointer() и SetPointer(). Они соответствуют GetString() и SetString(). Разница в том, что эти методы копируют полную структуру в память Windows и возвращают адрес вместо структуры. Так как класс Struct обслуживает такие блоки памяти, вы можете быть уверены, что адрес не изменится до тех пор, пока вы не высвободите блок памяти или структуру.

GetPointer() может получать дополнительный параметр, который определяет размер выделяемого блока памяти. Например, если вы вызываете GetPointer(1000) на структуре PRINTER_INFO_1, вы получите адрес блока памяти в 1000 байт, где первые 16 байт содержат структуру. Так как теперь вы посылаете адрес вместо строки, объявление этой функции выглядит несколько по-иному. В примере «ApiDef.Prg» вы можете найти объеявление для GetPrinter():

Declare Integer GetPrinter in WinSpool.Drv as WS_GetPrinter ;
   Integer hPrinter, ;
   Integer Level, ;
   Integer pPrinter, ;
   Integer cbBuf, ;
   Integer @pcbNeeded

Это объявление использует алиас для функции API, так как GETPRINTER() имеется среди собственных Visual FoxPro функций и вы уже не сможете вызвать ее, так как функция API зарегистрирована под тем же именем. Важный параметр pPrinter теперь определен как Integer, который представляет собой  32-bit величину и может хранить адрес памяти. Отметим, что передача параметра определена по величине, а не по ссылке. Это важно, так как указатель, как я упоминал об этом раньше, представляет собой переменную, хранящую адрес. Так как велична уже представляет собой адрес, мы не можем послать ее по ссылке. Проделанное таким образом действие вынуждает Visual FoxPro послать ссылку на нашу ссылочную переменную, то есть у нас будет, в терминах С, указатель на указатель на буффер.

Так как буффер не содержит никакой информации о своем размере, вы также посылаете параметр chBuf, который содержит размер буффера. Другими словами, chBuf представляет собой ту жу самую величину, котору. вы послали в GetPointer(). Если этот буффер слишком мал, Windows может распознать это и установить параметр pcbNeeded равным числу байт, которые необходимы и вернуть код ошибки. В таком случае вы можете вызвать GetPointer() повторно, указав на этот раз «правильное» количество выделяемой памяти и вызвать API функцию заново.

Когда вы имеете дело с такими буфферами вы должны иметь в виду, что компонент struct не высвобождает блок выделенной памяти до тех пор, пока вы не затребовали этого. После того, как вы поработали с буффером вы должны вызвать FreePointer() и послать ему адрес буффера. Есть простое объяснение тому, почему SetPointer() не делает этого автоматически. Некоторые API функции хранят адрес буффера для последующего использования. Если компонент класса Struct высвободит буффер автоматически, адрес станет недействительным и в результате получим «возражение» системы, когда функция API попытается использовать этот буфер позднее. Полная последовательность использования буфферов может выглядеть так (вы найдете этот код в примере «PrinterInfo.PRG»):

lnNeeded = 0
lnSize = 1000
lnBuffer = loPrtInfo.GetPointer( m.lnSize )
lnOK = WS_GetPrinter( ;
   m.lnHandle, ;
   1, ;
   m.lnBuffer, ;
   m.lnSize, ;
   @lnNeeded ;
)
loPrtInfo.SetPointer(m.lnBuffer )
loPrtInfo.FreePointer(m.lnBuffer )

lnSize определяет размер буффера и используется для GetPointer() также, как и для функции API. Использование переменной помогает вам проще обращаться с буфферами, так как если вы увеличиваете размер буффера, то это можно сделать в одном месте. Кроме того, это уменьшает возможность посылки разных величин в GetPointer() и в функцию API результатом чего может быть GPF (General Protection Fault). GetPointer() копирет структуру в память и возвращает адрес. Функция API заполняет этот блок памти информацией принтера. SetPointer() читает измененный блок памяти и обновляет свойства. Так как вы более не нуждаетесь в буффере, FreePointer() освобождает блок памяти. Свойства объекта loPrtInfo теперь содержат величины, возвращенные функцией API.

Иногда функция API желает получить только буффер без какого-либо содержания, даже без структуры. В таком случае вы можете вызвать метод GetBlock() без передачи ему размера буффера. Этот метод возвратит адрес буффера, который вы можете использовать, как параметр для функции API, или присвоить его свойству объекта структуры. Когда у вас отпадет нужда в этом буфере вы можете вызвать метод FreePointer() для высвобождения буффера.

Это все, что вам нужно для использования класса struct в ваших приложениях. Конечно, он содержит несколько больше, чем описано в последующих двух частях. Таблица, приведенная ниже, содержит короткое резюме о всех методах и структурах, которые доступны в классе Struct для использования вами.

Метод/Свойство Описание
cMembers Содержит объявление структуры в виде comma-separated списка. Каждый член указан с его типом. Порядок, в котором перечислены свойства, определяют порядок в котором они добавлются в структуру.
GetString() Возвращает структуру, как строку, используя текущие соответствующие величины. В случае ошибки возвращается пустая строка.
SetString() Обновляет свойства, используя строку переданную этой функции в качестве параметра. Строка обычно изменяется в результате вызова функции API непосредственно перед передачей в функцию.
GetPointer() Возвращает адрес буффера, содержащего структуру. Дополнительно вы можете передать размер буффера, если требуется больше памяти, чем установленный размер структуры. В случае ошибки возвращается 0.
SetPointer() Получает адрес буффера памяти и обновляет свойства в объекте структуры величинами, хранящимися в буффере. Обычно эта функция вызывается после того, как функция API изменила содержимое буффера.
FreePointer() Высвобождает память, которая была выделена вызовом GetPointer() или GetBlock().
GetBlock() Возвращает адрес блока памяти, размер которого устанавливается в первом параметре.
Requery() Пересматривает свойство cMembers. Вы вызываете этот метод после того, как вы либо изменили свойство cMembers, либо когда вы изменили размер массива свойств. Вызов этой функции высвобождает все выделенные блоки памяти, использовавшиеся под указатели или буфферы.
nMemorySize Устанавливает размер памяти, которая требуется для всех указателей и буфферов. Размер по умолчанию равен 8192 байтам (или 8 KB). Эта величина не может быть изменена в runtime, любые изменения должны быть сделаны в классе в design time, или в событии Init() до исполнения команды DODEFAULT().

Модификации класса Struct под собственные нужды

Способ, которым некоторые вещи обслуживаются в этом классе может не отвечать вашему каркасу приложения (framework). С этой целью для модификации поведения по умолчанию могут быть использованы несколько методов и свойств. В этой части я разъясню эти возможности.

Класс struct требует Convert.FLL. Эта библиотека содержит некоторые конверсионные функции необходимые для преобразования числовых величин в бинарные строки, как это требуется для функций API. Она содержит также некоторые функции для копирования в память и из строк. Вы можете использовать эти функции, если вы не желаете играться с классом Struct. Эта библиотека должна быть загружена. Класс загружает эту библиотеку во время события Init, если класс еще не был загружен. Он остается в памяти до тех пор, пока вы либо не выпустите команду SET CLASSLIB TO без каких-либо параметров, либо не выпустите команду RELEASE ALL, либо он будет выгружен, если библиотека не была загружена до описания объектов. Это подразумевает, что библиотека должна быть в текущем пути.

Иногда отмечается неверное поведение. Например, вы можете захотеть скопировать FLL в каталог Windows\System, или желаете выгрузить библиотеку, когда она больше не нужна. Имеется два места, где вы это можете обслуживать. Свойство cLibConvert содержит имя и путь к Convert.Fll. По умолчанию он установлен в "CONVERT.FLL". Если вам нужно просто указать другое имя или другой путь, вы можете изменить это свойство. Если вы желаете изменить процесс загрузки, вы можете переписать код события класса LoadLibrary(). Он вызывается без параметра и возвращает .T., когда библиотека может быть загружена, или .F. в случае ошибки.

Для обслуживания памяти класс Struct использует отдельный класс, названный как StructMemory. Если вы хотите субклассировать StructMemory для того, чтобы изменить его поведение, вы можете поместить имя своего собственного класса в свойство cClassMemory. Величина по умолчанию - "StructMemory". Init() вызывает метод CreateMemory() для инстанциации этого объекта. CreateMemory() загружает библиотеку класса (ту самую, в которой находится ваш класс struct), создает объект и хранит ссылку в свойстве oMemory. Если вы хотите изменить это поведение, вы можете без опаски переписать метод CreateMemory(), например, когда библиотека, в которой хранится StructMemory, не загружена и тому подобное. Класс StructMemory ожидает один параметр в событии Init. Это размер блока памяти, который он должен обслуживать. По умолчанию туда передается значение свойства nMemorySize. Изменением этого свойства вы можте определять, какое количество помяти доступно структурам для использования указателями и буфферами.

Некоторые структуры могут быть очень большими. Когда вы определяете ваши структуры визуально, вы отмечаете, что Visual FoxPro имеет ограничение в 254 символа для свойств в окошке Properties. Поэтому класс struct не обращается к свойству cMembers напрямую. Вместо этого он использует метод GetCMember(), который возвращает значение свойства cMembers. Переписью этого метода вы можете описывать действительно огромные структуры. Кроме того, так как вы можете вставить комментарии в метод, это, чаще всего, проще и удобнее собирать и находить bugs в средних и колоссальных структурах, когда они определены в методе GetCMember(). Несколько классов в WinStruct.VCX переписывают этот метод именно по этой причине.

Метод GetOS() возвращает "9x" когда приложение запущено на Windows 9x и "NT" когда оно запущено на Windows NT. Эта величина используется для "x" кода свойства cMembers для того, чтобы определить, что должно быть использовано - UNICODE или ANSI. Вы можете переписать этот метод, если ни одна из версий не работает, или, если имеется причина, чтобы код "x" был бы независимым от операционной системы, например, на которой объявлена функция API.

Для каждого члена, определяемого вами в структуре, создается объект, базирующийся на классе StructMember. Для каждого возможного типа, таких как 32-bit integer, double, или т.д. имеется отдельный субкласс StructMember. Метод CreateMember() представляет собой факторный метод, который создает объект, основанный на типе символа, переданного ему в качестве параметра. Если вы добавляете новый тип данных в структуру, или, если вы хотите субклассировать существующие типы, вы можете переписать этот метод для описания ваших собственных объектов. Метод получает два параметра. Первый (tcType) идентичен последнему символу в типа кода, используемого в свойстве cMembers. Второй - tcSpec - должен быть передан в класс StructMember. Этот класс получает два параметра в событии Init. Первый представляет собой ссылку на объект Struct, второй является параметром tcSpec. CreateMember() возвращает ссулку на объект объекту StructMember, или NULL, если произошла ошибка.

Обзор разработки

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

С целью облегчения создания структур настолько, насколько это возможно, разработка этого кмопонента должна допускать гибкость при расширении класса. Поэтому я не положил ничего в один класс, но описал несколько классов, которые используют за сценой классStruct . Ни один из этих классов не загружается напрямую, зато всегда используют факторный метод для инстанциации объекта (я упоминал об этом в предыдущей части). Кроме того, очищающая все вещь управляется несколькими методами. Оба, Release() и Destroy() вызывают метод CleanUp(). Это удостоверит, что задача очистки четко выполнена, если вы не вызывали открыто метод Release(). Cleanup() сначала убеждается, что он не вызван вторично. Затем он вызывает метод ReleaseMembers() для удаления все объектов членов, метод ReleaseLibrary() в конечном счете для выгрузки библиотеки и ReleaseMemory() для освобождения памяти и возврата высвобожденной памяти Windows.

Задачей объекта Struct явлется разбор свойства cMembers и управление объектами StructMember. Но сам по себе он не производит никаких преобразований. Это работа для класса StructMember. Для каждого возможного типа имеются субклассы. Вдобавок, сходные типы используют совместно общий промежуточный класс. Иерархия класса описана в приведенной ниже таблице. Заголовки на сером являются абстрактыми классами, которые не используются непосредственно.

Класс Ответственнен за:
StructMember базовый абстрактный классa
StructCharMember все строковые члены
StructCharBufMember type "C"
StructCharStringMember type "Z"
StructNumberMember все численные члены
StructByteMember type "B"
StructDoubleMember type "D"
StructLongMember type "L"
StructWordMember type "W"
StructStructMember type "O"

Все классы StructMember используют совместно 3 общих метода: GetString(), SetString() и SizeOf(). Они ведут себя схожим образом с теми, что предлагает класс Struct, но обслуживают только единичное свойство. Каждый StructMember привязан к свойству в классе Struct. Для избежания ссылочных проблем, этот объект не содержит ссылку на объект Struct или на объект памяти. Вместо этого, ссылки пересылаются всегда, когда объект Struct вызывает один из этих трех методов.

Внутренне класс StructMember разбирает спецификацию типа, проверяет - находится-ли свойство в массиве, обслуживает память, которую выделяет и преобразует строку в указатель, если был использован символ кода "p". Настолько, насколько это возиожно, он перемещен в класс StructMember и не обслуживается в субклассах. Поэтому этот класс использует некоторые абстрактные методы: ConvertDataToString и ConvertStringToData, так как это часть различная для всех типов. Оставшееся может быть обобщено, когда известны некоторые параметры. DataSize() возвращает размер действительного элемента, ElementSize возвращает размер такого члена, который требуется в структуре. nElementSize хранит размер индивидуального элемента, когда используются комбинированные элементы, например, strings. cSetStringConversion и cGetStringConversion содержат имена функций, которые они используют для преобразования элементов. cProperty является свойством, к которому привязан объект члена. lIsPointer и lIsArray определяют - является-ли свойство указателем или массивом.

Класс StructMemory обслуживает память. Это просто упаковщик API функций для Heap memory management. Он предоставляет методы для выделения и высвобождения памяти, определения размера блока памяти и копирования в память и в строку.

И это все!

Cелектор для быстрого перехода на сайты, связанные с Visual FoxPro.