Повысить среднюю производительность можно, применяя комбинированный метод получения буфера. Программа создает массив в стеке такого размера, чтобы в большинстве случаев возвращаемая строка вмещалась в нем. Этот размер определяется в каждом конкретном случае, исходя из особенностей функции и условий ее вызова. А на тот случай, если она все-таки там не поместилась, предусмотрен запасной вариант с выделением буфера в динамической памяти. Этот подход иллюстрирует листинг 3.45.
Листинг 3.45. Быстрый (в среднем) способ получения строки через буфер
const
StatBufSize = ...; // Размер, подходящий для данного случая
var
StatBuf: array[0..StatBufSize - 1] of Char;
Buf: PChar;
RealLen: Integer;
begin
//
Пытаемся разместить строку в буфере StatBuf
RealLen := GetString(StatBuf, StatBufSize);
if RealLen > StatBufSize then
begin
// Если StatBuf оказался слишком мал, динамически выделяем буфер
// нужного размера и вызываем функции еще раз
Buf := StrAlloc(RealLen);
GetString(Buf, RealLen);
end
else
// Размера статического буфера хватило. Пусть Buf указывает
// на StatBuf, чтобы нижеследующий код мог в любом случае
// обращаться к буферу через переменную Buf
Buf := StatBuf;
// Что-то делаем с содержимым буфера
...
// Если выделяли память, ее следует очистить
if Buf <> StatBuf then StrDispose(Buf);
end;
Следует также упомянуть о еще одной альтернативе передачи строк в DLL — типе
WideString
, который хранит строку в кодировке Unicode и является, по сути, оберткой над системным типом
BSTR
. Работать с
WideString
так же просто, как и с
AnsiString
, перекодирование из ANSI в Unicode и обратно выполняется автоматически при присваивании значения одного типа переменной другого. В целях совместимости с СОМ и OLE при работе с памятью дли строк
WideString
используется специальный системный менеджер памяти (через API-функции
SysAllocString
,
SysFreeString
и т.п.), поэтому передавать эти строки из DLL в главный модуль и обратно можно совершенно безопасно даже без
ShareMem
. Правда, при этом не стоит забывать о расходовании процессорного времени на перекодировку, если основная работа идет не с Unicode, а с ANSI.
Отметим одну ошибку, которую делают новички, прочитавшие комментарий про
ShareMem
, но не умеющие работать с
PChar
. Они пишут, например, такой код для функции, находящейся в DLL и возвращающей строку (листинг 3.46).
Листинг 3.46. Неправильный способ возврата строки из DLL
function SomeFunction(...): PChar;
var
S: string;
begin
// Здесь присваивается значение S
Result := PChar(S);
end;
Такой код компилируется и даже, за редким исключением, дает ожидаемый результат. Но тем не менее, в этом коде грубая ошибка. Указатель, возвращаемый функцией, указывает на область памяти, которая считается свободной, поскольку после выхода переменной
S
за пределы области видимости память, которую
занимала эта строка, освободилась. Менеджер памяти может в любой момент вернуть эту память системе (тогда обращение к ней вызовет Access violation) или задействовать для других целей (тогда новая информация уничтожит содержащуюся там строку). Проблема маскируется тем, что обычно результат используется немедленно, до того как менеджер памяти что-то сделает с этим блоком. Тем не менее полагаться на это и писать такой код не следует.
3.4. Прочие "подводные камни"
В этом разделе собрана небольшая коллекция не связанных между собой "подводных камней", с которыми пришлось столкнуться автору книги.
3.4.1. Порядок вычисления операндов
Эта проблема связана с тем, что у человека есть определенные интуитивные представления о порядке выполнения действий программой, однако компилятор не всегда им соответствует. Рассмотрим следующий код (листинг 3.47, пример OpOrder на компакт-диске).
Листинг 3.47. "Неправильный" порядок вычисления операндов
var
X: Integer;
function GetValueAndModifyX: Integer;
begin
X := 1;
Result := 2;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
A1, A2: Integer;
begin
X := 2;
A1 := X + GetValueAndModifyX;
X := 2;
А2 := GetValueAndModifyX + X;
Label1.Caption := IntToStr(A1);
Label2.Caption := IntToStr(A2);
end;
Суть этого примера заключается в том, что функция
GetValueAndModifyX
имеет побочный эффект — изменяет значение глобальной переменной
X
. И эту же переменную мы используем при вычислении выражения, в которое входит также вызов
GetValueAndModifyX
. При вычислении
A1
в выражении сначала упоминается
X
, а потом
GetValueAndModifyX
, при вычислении
А2
— наоборот. Логично было бы предположить, что
A1
получит значение 4,
А2
— 3, т.к. вычисление первого операнда должно выполняться раньше второго. В действительности же обе переменные получат значение 3, поскольку компилятор сам выбирает порядок вычисления операндов независимо от того, в каком порядке они упоминаются в выражении. То же самое касается любых коммутативных операций: умножения, арифметических
and
,
or
и
xor
. Посмотрим, что будет для некоммутативных операций, например, для деления (листинг 3.48).
Листинг 3.48. "Неправильный" порядок вычисления операндов при делении