Розробка DLL для CTD з використанням Delphi, Різне, Програмування, статті

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, все вищевикладене взагалі не повинно бути проблемою для вас.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*