// Если 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. "Неправильный" порядок вычисления операндов при делении
procedure TForm1.Button2Click(Sender: TObject);
Читать дальше
Конец ознакомительного отрывка
Купить книгу