Навигация
Главная »  Delphi 

Разработка DLL для CTD с использованием Delphi


Джо Мейер (Joe Meyer), Ice Tea Group
Centura Team Developer - великолепный инструмент, который включает множество готовых функций, и почти все, что вам может понадобиться, здесь уже есть. Хорошо, почти все...

Время от времени, функциональность CDT приходится расширять. Может потому, что требуется что-то, что не так легко сделать с помощью языка SAL или потому, что вам нужна скорость компилированного кода. Конечно, вы можете начать писать COM-объект, импортировать интерфейс с помощью ActiveX-проводника CTD, затем пройти через весь этот ад вариантного программирования, которое нужно для использования COM в CTD.

COM - это не совсем то, чему вас учили? Слышали об этом, но не знаете, как разрабатывать COM-объекты? Хорошо, тогда вы, наверное, предпочтете использовать Visual C++ для написания DLL. Если это вызывает улыбку на вашем лице, и вы чувствуете, как ваши пальцы тянутся запустить Visual Studio, то, возможно, вы не захотите читать эту статью.

Если, кроме всего прочего, вы имели дело с Delphi, известной инструментальной средой программирования на языке Pascal компании, тогда вы можете спросить себя, а нельзя ли написать DLL с помощью Delphi и затем использовать эту DLL в CTD?

Хорошо, у нас есть две новости: хорошая и плохая. Хорошая новость - Delphi действительно хороша для написания DLL, которые прекрасно интегрируются с CTD. Плохая новость - на этом пути есть несколько ловушек и вы должны знать несколько моментов, чтобы успешно выполнить ваш проект разработки такой DLL.

Я начну свою статью со сравнения строковых типов данных, а затем перейду и к другим типам данных.

Эти вещи не похожи друг на друга.

Строки в Delphi

В отличие от почти всех других языков программирования, в Pascal есть специальный тип для строк (тоскливый вздох...).

Давным-давно, когда Pascal уже появился, но фирма Borland еще не выпустила первую 32-битную версию Delphi для Windows, длина строки в Pascal была ограничена 255-ю символами. Даже хуже - первый байт строки отводился под ее длину.

Новый тип строк появился в первой 32-битной версии Delphi, которая сделала ограничение в 255 символов анахронизмом. Сегодняшняя текущая версия Delphi 6 не ограничивает длину строки (однако это ограничение есть в Windows). К счастью, в Delphi есть и другой тип данных для строк: PChar. PChar - это указатель на символьный массив, совсем как char* в C. В Delphi есть и процедуры для операций с этим типом данных.

Строки в C

В языке C нет встроенного типа данных, такого как String. Вместо этого строки представляются массивами символов. Завершением строки служит двоичный ноль, следующий за последним символом массива. Библиотеки C включают довольно мало функций для работы с этими специальными массивами символов.

Строки в CDT

В CDT есть два типа строк: String и Long String.

В действительности тип Long String это String , и ведет себя точно так же как String, так что для чего же он хорош? Для ответа на этот вопрос мы должны сделать небольшой экскурс в программирование баз данных. Точнее, в то, как получаются и передаются данные через программный интерфейс базы данных.

Как вы, конечно, знаете, базы данных на основе SQL обычно имеют различные типы данных, такие как char и blob.

Тип данных char используется для хранения строк определенной длины и может использоваться в выражении WHERE и для объединения таблиц. С помощью полей типа blob вы этого делать не можете. Но что делать, если вы хотите хранить строки длиной более 255-ти символов (ограничение на длину строки зависит от типа SQL базы данных)? Отлично, используйте поле blob и храните столько символов, сколько хотите. Но, так как ничего бесплатного не бывает, вы потеряете возможность использовать эти данные в качестве критерия выбора и не сможете использовать их для индексирования.

Почему я вам об этом говорю? Чтобы вам стало ясно, что поля char отличаются от полей blob . Даже хуже, они обрабатываются совершенно по-разному при обмене между клиентом и сервером. Поля blob извлекаются и передаются с использованием специальных внутренних процедур.

Для того чтобы сообщить SQL-серверу к какому типу данных вы действительно обращаетесь, вы должны использовать подходящий тип, чтобы дать знать SQL-серверу принадлежит ли эта символьная строка типу char или blob. Именно так вы должны использовать тип String в CTD для char и varchar любой длины, вплоть до максимально поддерживаемой, и использовать Long String для строк, хранящихся в полях blob . Это относится только к выражениям SQL. В чистом языке SAL вы можете использовать любой из типов, это безразлично. Вы можете даже присваивать значения типа Long String переменным типа String.

Но есть другая проблема в CTD: это внутренние обработчики для собственного представления строк. Эти обработчики не совместимы ни с какими другими компиляторами. На самом деле, это не проблема, так как при объявлении внешней функции, которая в качестве параметра принимает строку, вы можете просто передавать стандартную строку CTD, и она будет преобразована к типу LPSTR "на лету".

Для того чтобы разрешить доступ CTD к библиотекам DLL и передавать и получать строки, вы можете объявить параметр функции в DLL следующим образом:

String: LPSTR

Это укажет CTD, что нужно конвертировать внутренний дескриптор в стандартный символьный массив C и передать его функции в DLL.

Все становится несколько сложнее, когда функция в DLL возвращает строку, и мы рассмотрим этот случай в следующей главе.

Дескриптор или указатель? Вот в чем вопрос.

В основном, есть два способа объявления строковых параметров, совместимых с CTD: PChar и Handle. Использовать PChar проще всего. CTD будет конвертировать дескриптор в строку C и передавать в DLL параметр типа char*. Так как в Delphi есть совместимый тип, вы можете объявить параметр как PChar в Delphi, и это будет работать достаточно хорошо.

Ниже приведен маленький пример, для иллюстрации принципа кодирования:

Не так уж и сложно, правда? Но что если вы хотите иметь параметр для возвращаемого значения?

Вы можете поступить следующим образом:

Delphi:
Procedure DoNothing (AString:PChar); stdcall;
Begin
StrCopy (AString, 'Return value');
End;
Внешнее объявление в CTD:
Function: DoNothing
Parameters
Receive String: LPSTR

Не так много различий, не правда ли? Да, но только на первый взгляд.

При вызове DoNothing в языке SAL вам нужно инициализировать строку, которую вы передаете в DLL. Обычно вы используете SalStrSetBufferLength() для распределения памяти достаточного объема для хранения содержимого, которое будет возвращаться из DLL.

Хотя в целом это работает, мне кажется несколько неуклюжей необходимость вызова SalStrSetBufferLength() при каждом моем вызове функции в DLL. Не будет ли изящнее, если Delphi будет принимать внутренний обработчик строки CTD и напрямую его модифицировать? Без необходимости предварительного вызова SalStrSetBufferLength()? Спорю, вы ответите - "да"!

Теперь расслабьтесь, или налейте себе стаканчик вина, потому что вам не нужно самому искать решение. Как вы уже знаете, CTD может вызывать функции, размещенные во внешней библиотеке DLL. Но с другой стороны, внешние функции могут вызывать функции из DLL CTD. Круто, правда?

Внутреннее представление строк в CTD - это 4-х байтный дескриптор, и мы можем объявить его в Delphi примерно так:

Type
THString = DWORD;

DLL, экспортирующая функции CTD называется CDLLI20.DLL. Число в имени DLL просто отражает версию CTD. Для версии 1.5 DLL будет называться CDLLI15.DLL. Эта DLL экспортирует несколько функций, используемых для обработки внутреннего представления строк в CTD:

BOOL CBEXPAPI SWinInitLPHSTRINGParam(LPHSTRING, LONG);
LPSTR CBEXPAPI SWinStringGetBuffer(HSTRING, LPLONG);

С точки зрения программиста на Delphi это выглядит уродливо, так что давайте посмотрим, сможем ли мы лучше объявить интерфейс к DLL на языке Pascal:

const
DLLName = 'cdlli20.dll'; // cdlli15.dll when using CTD1.5

function SWinInitLPHSTRINGParam (var StringHandle:THString; Len:DWORD) : BOOL; stdcall
xternal DLLName;
function SWinStringGetBuffer (StringHandle:THString; var Len:DWORD) : PChar;
stdcall external DLLName;

Гораздо красивее, не правда ли? Хорошо, хорошо, мы сделали это красиво, но как теперь с этим работать? SWinInitLPHSTRINGParam - ключ к созданию строк CTD. Эта функция принимает 2 параметра: обработчик и длину. Обработчик будет создан интерпретатором CTD , если вы инициализируете его значением 0 перед вызовом функции. Второй параметр сообщит интерпретатору, сколько байт распределить для реальной строки, и вот как будет выглядеть вызов функции:

Var
Hdl : THString;
begin
Hdl := 0;
SWinInitLPHSTRINGParam (Hdl, 200);
end;

Приведенный пример создает обработчик строки CTD, который указывает на буфер для строки из 200 байт. Отлично, но как поместить текст в этот буфер? Нет ничего проще.

Посмотрите на функцию SwinStringGetBuffer. Она принимает 2 параметра. Первый - обработчик, который мы только что создали. Второй возвращает длину буфера, адресуемого этим обработчиком. Результатом функции является то, что нам нужно: указатель PChar прямо ссылающийся на буфер. Этот указатель можно использовать для манипуляций с содержимым строки.

Теперь у нас есть все необходимое для создания собственных строк CTD и заполнения их текстом, но что если DLL, написанная на Delphi, получает обработчик и вызывающего ее приложения CTD? Ответ: просто забудьте о функции SWinInitLPHSTRINGParam , так как обработчик уже создан, и, скорее всего, уже содержит текст. Все что вам нужно - это вызвать SWinStringGetBuffer , и вы получите длину строки и, в придачу, указатель на ее содержимое.

Хотя все вышеизложенное уже достаточно хорошо работает, я все еще не удовлетворен. Я люблю, чтобы мои функции на Pascal были как можно более гибкими и легко сопровождаемыми, так что я написал несколько инкапсулирующих функций, интуитивно более понятных:

interface

function SWinCreateString (StringValue:PChar) : THString; overload;
function SWinCreateString (StringValue:String) : THString; overload;
function SWinCreateString (StringLength:DWORD) : THString; overload;

procedure SWinSetString (StringHandle:THString; Value:PChar); overload;
procedure SWinSetString (StringHandle:THString; const Value:string); overload;

function SWinGetString (StringHandle:THString) : string; overload;
procedure SWinGetString (StringHandle:THString; var Value:PChar); overload;

implementation

function SWinCreateString (StringValue:PChar) : THString;
var
Len : DWORD;
begin
result := 0;
if SWinInitLPHSTRINGParam (result, StrLen (StringValue) + 1) then
StrCopy (SWinStringGetBuffer (result, Len), StringValue);
end;

function SWinCreateString (StringValue:String) : THString;
begin
result := SWinCreateString (PChar (StringValue));
end;

function SWinCreateString (StringLength:DWORD) : THString;
begin
SWinInitLPHSTRINGParam (result, StringLength);
end;

procedure SWinSetString (StringHandle:THString; Value:PChar);
begin
SWinSetString (StringHandle, string (Value));
end;

procedure SWinSetString (StringHandle:THString; const Value:string);
var
Len : DWORD;
begin
Len := Length (Value);
if SWinInitLPHSTRINGParam (StringHandle, Len + 1) then
StrPCopy (SWinStringGetBuffer (StringHandle, Len), Value);
end;

function SWinGetString (StringHandle:THString) : string;
var
Len : DWORD;
begin
result := StrPas (SWinStringGetBuffer (StringHandle, Len));
end;

procedure SWinGetString (StringHandle:THString; var Value:PChar);
var
Len : DWORD;
begin
Value := SWinStringGetBuffer (StringHandle, Len);
end;

Приведенные функции привносят смысл в операции создания, присвоения значений и получения строк в различных контекстах. Можно создавать строки путем передачи как PChar , так и собственного типа строк Delphi. Присвоение значения и получение строки тоже можно сделать с использованием как PChar , так и собственного типа строк Delphi.
Вы могли уже заметить, что здесь есть функции с одинаковыми именами, но разными параметрами. Они помечены как перегружаемые, так что компилятор на основе списка параметров будет решать, какая функция должна быть вызвана.

Сами по себе функции не делают ничего особенного, они просто используют SWinInitLPHSTRINGParam и SWinStringGetBuffer для доступа к строкам.

Вооружившись приведенными выше функциями, нетрудно обрабатывать строки CTD в Delphi, так что давайте их использовать. Я больше не хочу видеть SalStrSetBufferLength в ваших CTD-приложениях.

Число, которое есть число, которое является числом.

В CTD есть ровно один тип данных для чисел. В не зависимости от того, есть ли в числе десятичная точка или нет, все равно - это число. К слову, даже логический тип Boolean имеет внутреннее числовое представление. В Delphi для чисел существует несколько различных типов:

Тип  Пределы   Размер 
Integer -2147483648..2147483647 32 бита, со знаком
Cardinal 0..4294967295 32 бита, со знаком
Shortint -128..127 8 бит, со знаком
Smallint -32768..32767 16 бит, со знаком
Longint -2147483648..2147483647 32 бита, со знаком
Int64 -263..263 -1 64 бита, со знаком
Byte 0..255 8 бит, со знаком
Word 0..65535 16 бита, со знаком
Longword 0..4294967295 32 бита, со знаком
Опять же, CTD предоставляет функции и для преобразования внутреннего типа Number во внешние представления числовых типов и из них в Number:

BOOL CBEXPAPI SWinCvtIntToNumber(INT, LPNUMBER);
BOOL CBEXPAPI SWinCvtWordToNumber(WORD, LPNUMBER);
BOOL CBEXPAPI SWinCvtLongToNumber(LONG, LPNUMBER);
BOOL CBEXPAPI SWinCvtULongToNumber(ULONG, LPNUMBER);
BOOL CBEXPAPI SWinCvtDoubleToNumber(double, LPNUMBER);

BOOL CBEXPAPI SWinCvtNumberToInt(LPNUMBER, LPINT);
BOOL CBEXPAPI SWinCvtNumberToWord(LPNUMBER, LPWORD);
BOOL CBEXPAPI SWinCvtNumberToLong(LPNUMBER, LPLONG);
BOOL CBEXPAPI SWinCvtNumberToULong(LPNUMBER, LPDWORD);
BOOL CBEXPAPI SWinCvtNumberToDouble(LPNUMBER, double FAR *);

Так как программисты на языке Pascal ненавидят код, написанный на C, они, возможно, захотят преобразовать эти функции во что-то, вроде этого:

function SWinCvtIntToNumber(Value:integer; var Num:TNumber) : BOOL;
stdcall external DLLName;
function SWinCvtWordToNumber(Value:word; var Num:TNumber) : BOOL;
stdcall external DLLName;
function SWinCvtLongToNumber(Value:integer; var Num:TNumber) : BOOL;
stdcall external DLLName;
function SWinCvtULongToNumber(Value:DWORD; var Num:TNumber) : BOOL;
stdcall external DLLName;
function SWinCvtDoubleToNumber(Value:double; var Num:TNumber) : BOOL;
stdcall external DLLName;

function SWinCvtNumberToInt(Num:PNumber; var Value:integer) : BOOL;
stdcall external DLLName;
function SWinCvtNumberToWord(Num:PNumber; var Value:word) : BOOL;
stdcall external DLLName;
function SWinCvtNumberToLong(Num:PNumber; var Value:integer) : BOOL;
stdcall external DLLName;
function SWinCvtNumberToULong(Num:PNumber; var Value:DWORD) : BOOL;
stdcall external DLLName;
function SWinCvtNumberToDouble(Num:PNumber; var Value:double) : BOOL;
stdcall external DLLName;

Как вы могли заметить, эти функции используют тип TNumber . Этот тип используется для внутреннего формата CTD для представления чисел - Number , и объявлен так:

Type
TNumber = packed record
numLength : byte;
numValue : array [0..23] of byte;
end;
PNumber = ^TNumber;

Как видите, TNumber - это запись длиной в 24 байта, предваренная байтом длины. PNumber - просто указатель на тип TNumber. Запись должна быть объявлена с директивой packed. Это гарантирует, что компилятор не будет увеличивать размер байта numLength для выравнивания на границу слова, то есть, не даст компилятору вставить 3 дополнительных байта между numLength и numValue.

Байт длины очень важен, так как он может сообщить нам, что число имеет значение NULL. Если число имеет значение NULL , то его длина равна 0. Для удобной проверки этого, я написал следующую исключительно сложную функцию:

function NumberIsNull (const Num:TNumber) : boolean;
begin
result := Num.numLength = 0;
end;

Функция возвращает TRUE (истина) когда число имеет значение NULL , просто проверяя numLength на равенство 0.

В действительности процедуры преобразования чисел достаточно просты, но в целях достижения большей ясности я написал две маленькие функции, демонстрирующие их использование:

function ShiftLeft (Value:integer) : TNumber; stdcall;
var
num : TNumber;
begin
SWinCvtIntToNumber (Value shl 1, Num);
result := num;
end;

function ShiftRight (Value:integer) : TNumber; stdcall;
var
num : TNumber;
begin
SWinCvtIntToNumber (Value shr 1, Num);
result := num;
end;

Функции принимаю целый тип данных, побитно сдвигают полученное значение и возвращают результат в виде типа Number .

На сегодня это все. Мы узнали, как избежать встреч с чудовищем Visual C++ и приручить красавицу Delphi. Если вы опытный программист на Pascal, все вышеизложенное вообще не должно быть проблемой для вас.



 

 Flash в Delphi.
 Быстрая обработка данных Excel в Delphi.
 Хороший выбор плохой архитектуры.
 DelphiX: Загрузка и вывод спрайтов.
 Медиаплеер своими руками.


Главная »  Delphi 

© 2018 Team.Furia.Ru.
Частичное копирование материалов разрешено.