Выбрать главу

Строку, на которую указывает PChar, можно использовать везде, где требуется string — компилятор сам выполнит необходимые преобразования. Обратное неверно. Фактически, string — это указатель на начало строки, завершающейся нулем, т.е. тот самый указатель, который требуется при работе с PChar. Однако, как уже отмечалось, некорректные манипуляции с этим указателем могут привести к нежелательным эффектам, поэтому компилятор требует явного приведения переменных и выражений типа string к PChar. В свою очередь, программист должен ясно представлять, к каким последствиям это может привести.

Если посмотреть описание функций API, имеющих строковые параметры, в справке, можно заметить, что в некоторых случаях строковые параметры имеют тип LPCTSTR (как, например, у функции SetWindowText), а в некоторых — LPTSTR (GetWindowText). Ранее мы говорили, что появление префикса C после LP указывает на то, что это указатель на константу, т.е. то, на что указывает такой указатель, не может быть изменено. Тип LPCTSTR имеют те строковые параметры, содержимое которых функция только читает, но не модифицирует. С такими параметрами работать проще всего. Рассмотрим на примере функции SetWindowText, как можно работать с такими параметрами (листинг 1.20).

Листинг 1.20. Вызов функции с параметром типа LPCTSTR

{ Вариант 1 }

SetWindowText(Handle, 'Строка');

{ Вариант 2

 S -переменная типа string }

SetWindowText(PChar(S));

{ Вариант 3

 X - переменная типа Integer }

 SetWindowText(PChar('Выполнено ' + IntToStr(X) + '%'));

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

Во втором варианте функции передается указатель, хранящийся в переменной S. Такое приведение string к PChar безопасно, т.к. строка, на которую ссылается переменная S, не будет модифицироваться. Но здесь существует одна тонкость: конструкция PChar(S) — это не просто приведение типов, при ее использовании неявно вызывается функция _LStrToPChar. Как мы уже говорили, когда string хранит пустую строку, указатель просто имеет значение nil. Функция _LStrToPChar проверяет, пустая ли строка хранится в переменной, и, если не пустая, возвращает этот указатель, а если пустая, то возвращает не nil, а указатель на символ #0, который специально для этого размещен в сегменте кода. Поэтому даже если содержит пустую строку, в функцию будет передан ненулевой указатель.

Вычисление строковых выражений требует перераспределения памяти, а это компилятор делает только с выражениями типа string. Поэтому результат выражения, приведенного в третьем варианте, также имеет тип string. Но его можно привести к PChar. Память для хранения результата выражения выделяется динамически, как и для обычных переменных типа string. Чтобы передать указатель на но выражение в функцию, следует привести его к PChar. В эпилог процедуры, вызывающей функцию SetWindowText или иную функцию с подобным аргументом, добавляется код, который освобождает динамически сформированную строку, поэтому утечек памяти не происходит. Разумеется, существуют и другие способы формирования параметра типа LPCTSTR, кроме предложенных здесь. Можно, например, выделить память для нуль-терминированной строки с помощью StrNew или родственной ей функции из модуля SysUtils. Можно использовать массив типа Char. Можно выделять память какими-либо другими способами. Но предложенные здесь три варианта в большинстве случаев наиболее удобны.

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

Таким образом, перед программой встает задача узнать, какой объем памяти следует выделить под возвращаемую строку. Здесь API не предлагает универсального решения, разные функции по-разному решают эту проблему. Например, при получении заголовка окна с помощью GetWindowText размер этого заголовка можно узнать, вызвав предварительно GetWindowTextLength. Функции типа GetCurrentDirectory возвращают длину строки. Если при первом вызове этой функции памяти выделено недостаточно, можно увеличить буфер и вызвать функцию еще раз. И наконец, есть функции типа SHGetSpecialFolderPath, в описании которых написано, каков минимальный размер буфера, необходимый для гарантированной передачи полной строки этой функцией (это, разумеется, возможно только в том случае, когда размер возвращаемой строки имеет какое-то естественное ограничение). Следует также отметить, что большинство API-функций, возвращающих строки, в качестве одного из параметров принимают размер буфера, чтобы не скопировать больше байтов, чем буфер может принять.