Delphi выделить память под pchar
Преобразование строк из одного типа в другой
Здесь, все как обычно, и просто и сложно.
Преобразование между "настоящими" строковыми типами String[n], ShortString, и AnsiString выполняются легко, и прозрачно. Никаких явных действий делать не надо, Delphi все сделает за Вас. Надо лишь понимать, что в маленькое большое не влезает. Например:
В результате выполнения этого кода, в переменной s3 окажется строка 'abc', а не 'abcdef'. С преобразованием из pChar в String[n], ShortString, и AnsiString, тоже всё очень не плохо. Просто присваивайте, и все будет нормально.
Все они, синтаксически правильны. И кажется, что все три указателя (p1, p2 и p3) будут в результате иметь одно и то же значение. Но это не так. Всё зависит от того, что находится в s. Если быть более точным, равно ли значение s пустой строке, или нет:
Чтобы Вы понимали причину такого явления, я опишу, как Delphi выполняет каждое из этих преобразований. В начале напомню, что переменные AnsiString представляющие пустые строки, реально имеют значение Nil. Так вот:
Для выполнения преобразования pChar(s), компилятор генерит вызов специальной внутренней функции @LstrToPChar. Эта функция проверяет – если строковая переменная имеет значение Nil, то вместо него, она возвращает указатель на реально размещенную в памяти пустую строку. Т.е. pChar(s) никогда не вернет указатель равный Nil.
Тут все просто, такое преобразование просто возвращает содержимое строковой переменной. Т.е. если она при пустой строке содержит Nil, то и результатом преобразования будет Nil. Если же строка не пуста, то результатом будет адрес экземпляра строки.
Здесь, все совсем по другому. Перед выполнением такого преобразования, компилятор вставляет код, обеспечивающий ситуацию, когда указатель, хранящийся в s, будет единственным указателем на экземпляр строки в памяти. В нашем примере, если строка не пуста, то будет создана копия исходной строки. Вот ее-то адрес и будет возвращен как результат такого преобразования. Но, если строка пуста, то результатом будет Nil, как и во втором случае.
Теперь, интересно отметить, что если в приведенном примере, преобразование p3 := @(s[1]) выполнить первым, то при не пустой строке в s, все указатели (p1, p2, и p3), будут равны. И содержать они будут адрес "персонального" экземпляра строки.
Вот, теперь, зная как выполняются те или иные преобразования, Вы сможете всегда выбрать подходящий способ. Для тех, кто ещё не "проникся" до конца этой проблемой, приведу пример. В нем, преобразование, рекомендуемое разработчиками, приводит к "странному" поведению программы:
вызывает ошибку доступа к памяти при выполнении строки, помеченной 1. Вот и приходится применять эту функцию.
Здесь, поскольку параметр передаётся по значению, при вызове будет создана локальная копия переменной (указателя) Msg. Об этом я ещё расскажу ниже. Эта переменная, при завершении процедуры будет уничтожаться, что приведёт к освобождению "персональной" копии экземпляра переданной и преобразованной строки.
Функции этой группы используются для сравнения строк. Результатом будет одно из значений:
>0 если S1 > S2
255 символов приходится платить.
Если же тебе достаточно и 255 символов, то используй ShortString, или String[n].
Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга?
В Delphi 1.0 существовал лишь единственный строковый тип String, полностью эквивалентный одноименному типу в Turbo Pascal и Borland Pascal. Однако, этот тип имеет существенные ограничения, о которых я расскажу позднее. Для обхода этих ограничений, в Delphi 2, разработчики из Borland устроили небольшую революцию. Теперь, начиная с Delphi 2, имеются три фундаментальных строковых типа: ShortString, AnsiString, и WideString. Кроме того, тип String теперь стал логическим. Т.е., в зависимости от настройки соответствующего режима компилятора (режим больших строк), он приравнивается либо к типу ShortString (для совместимости со старыми программами), либо к типу AnsiString (по умолчанию). Управлять режимом, можно используя директиву компиляции (короткая форма ) или из окна настроек проекта – вкладка "Compiler" -> галочка "Huge strings". Если режим включен, то String приравнивается к AnsiString, иначе String приравнивается ShortString. Из этого правила есть исключение: если в определении типа String указан максимальный размер строки, например String[25], то, вне зависимости от режима компилятора, этот тип будет приравнен к ShortString соответствующего размера.
Поскольку, как вы узнаете в дальнейшем, типы ShortString и AnsiString имеют принципиальное отличие в реализации, то я вообще не рекомендую пользоваться логическим типом String без указания размера, если Вы, конечно, не пишете программ под Delphi 1. Если же Вы все-таки используете тип String, то я настоятельно рекомендую прямо в коде Вашего модуля указывать директиву компиляции, устанавливающую подразумеваемый Вами режим работы компилятора. Особенно если Вы используете особенности реализации соответствующего строкового типа. Если этого не сделать, то однажды, когда Ваш код попадет в руки другого программиста, не будет никакой гарантии того, что его компилятор будет настроен, так же как и Ваш.
Поскольку по умолчанию, после установки Delphi, режим больших строк включен, большинство молодых программистов даже и не подозревают, что String может представлять что-то отличное от AnsiString. Поэтому, дальше в этой статье, любое упоминание типа String без указания размера, подразумевает, что он равен типу AnsiString, если не будет явно указано иное. Т.е., считается что настройка компилятора соответствует настройке по умолчанию.
Сразу же упомяну о различии между типами AnsiString и WideString. Эти типы имеют практически одинаковую реализацию, и отличаются лишь тем, что WideString используется для представления строк в кодировке UNICODE использующей 16-ти битное представление каждого символа (WideChar). Эта кодировка используется в тех случаях когда необходима возможность одновременного присутствия в одной строке символов из двух и более языков (помимо английского). Например, строк содержащих одновременно символы английского, русского и европейских языков. За эту возможность приходится платить – размер памяти, занимаемый такими строками в два раза больше размера, занимаемого обычными строками. Использование WideString встречается не часто, поэтому, я буду в основном рассказывать о строках типа AnsiString. Но, поскольку они имеют одинаковую реализацию, почти все сказанное относительно AnsiString будет действительно и для WideString, естественно с учетом разницы в размере каждого символа.
Тоже самое касается и разницы между pChar и pWideChar.
Строковый тип AnsiString, обычно используется для представления строк в кодировке ANSI, или других (например OEM) в которых для кодирования одного символа используется один байт (8 бит). Такой способ кодирования называется single-byte character set, или SBCS. Но, очень многие не знают о существовании еще одного способа кодирования многоязычных строк (помимо UNICODE) используемого в системах Windows и Linux. Этот способ называется multibyte character sets, или MBCS. При этом способе, некоторые символы представляются одним байтом, а некоторые, двумя и более. В отличие от UNICODE, строки, закодированные таким способом, требуют меньше памяти для своего хранения, но требуют более сложной обработки. Так вот, строковый тип AnsiString может использоваться для хранения таких строк. Я не буду подробно останавливаться на этом способе кодирования, поскольку он применяется крайне редко. Лично я, ни разу не встречал программ использующих данный способ кодирования.
Знатоки Delphi вероятно мне сразу напомнят еще и о типах pChar (pWideChar) и array [. ] of Char. Однако, я считаю, что это не совсем строковые типы, но я расскажу и о них, поскольку они очень часто используются в сочетании со строковыми типами.
Итак, приведу основные характеристики строковых типов:
Еще, этот счётчик используется и для разрешения проблем, связанных со следующей ситуацией:
Здесь, как мы уже знаем, после выполнения оператора s2 := s1, обе переменные указывают на один и тот же экземпляр строки 'abc123'. Однако, что же произойдёт когда выполниться оператор s2[1] := 'X'? Казалось бы, в единственном имеющимся в нашем распоряжении экземпляре строки первая буква будет заменена на 'X'. И как следствие, обе строки станут равными 'Xbc123'. s1 то за что "страдает"? Но, к счастью это не так. Здесь на помощь Delphi вновь приходит счетчик ссылок. Delphi, при выполнении этого оператора понимает, что строка на которую указывает s2 будет изменена, а это может повлиять на других. Поэтому, перед изменением строки, проверяется ее счётчик ссылок. Обнаружив, что на нее ссылается более одной строковой переменной, делается следующее: создается копия этой строки со счётчиком ссылок равным 1, и адрес этой копии, присваивается s2; У исходного экземпляра строки, счетчик ссылок уменьшается на 1 – ведь s2 на неё теперь не ссылается. И лишь после этого, происходит изменение первой буквы, теперь уже собственного экземпляра строки. Т.е., по окончанию выполнения этого оператора, в памяти будут находиться две строки: 'abc123' и 'Xbc123'. Причем, s1 будет ссылаться на первую, а s2 на вторую.
При работе со строками определенными как константы, алгоритм работы несколько отличается. Приведу пример:
Казалось бы, при завершении работы процедуры, экземпляр строки 'Вася' должен быть уничтожен. Но в данном случае это не так. Ведь, при следующем входе в процедуру, для выполнения присваивания нужно будет вновь где-то взять строку 'Вася'. Для этого, ещё при компиляции, Delphi размещает экземпляр строки 'Вася' в области констант программы, где её даже невозможно изменить, по крайней мере, простыми методами. Но как же при завершении процедуры определить что строка 'Вася' – константная строка, и ее нельзя уничтожать? Все очень просто. Для константных строк, счётчик ссылок устанавливается равным -1. Это значение, "выключает" нормальный алгоритм работы со "счётчиком ссылок". Он не увеличивается при присваивании, и не уменьшается при уничтожении переменной. Однако, при попытке изменения переменной (помните s2[1]:='X'), значение счётчика равное -1 будет всегда считаться признаком того, что на строку ссылается более одной переменной (ведь он не равен 1). Поэтому, в такой ситуации всегда будет создаваться уникальный экземпляр строки, естественно, без декремента счётчика ссылок старой. Это защитит от изменений экземпляр строки-константы.
К сожалению, этот алгоритм срабатывает не всегда. Но об этом, мы поговорим позже, при рассмотрении вопросов преобразования строковых типов.
Где же Delphi хранит "счётчик ссылок"? Причем, для каждой строки свой! Естественно, вместе с самой строкой. Вот что представляет собой эта область памяти, хранящая экземпляр строки 'abc':
Для удобства работы с такой структурой, когда строковой переменной присваивается ссылка на эту строку, в переменную заносится адрес не начала этой структуры, а адрес её девятого байта. Т.е. адрес начала реальной строки (прямо как pChar). Для того, что бы приблизиться к реальной жизни, перепишем приведённую структуру:
С полем по смещению -8, нам уже должно быть все ясно. Это значение, хранящееся в двойном слове (4 байта), тот самый счетчик, который позволяет оптимизировать хранение одинаковых строк. Значение этого счетчика имеет тип Integer, т.е. может быть отрицательным. На самом деле, используется лишь одно отрицательное значение – "-1", и положительные значения. 0 не используется.
Теперь, обратите внимание на поле, лежащее по смещению -4. Это, четырёхбайтовое значение длинны строки (почти как в ShortString). Думаю, Вы заметили, что размер памяти выделенной под эту строку не имеет избыточности. Т.е. компилятор выделяет под строку минимально необходимое число байт памяти. Это конечно хорошо, но, при попытке "нарастить" строку: s1 := s1 + 'd', компилятору, точнее библиотеке времени исполнения (RTL) придется перераспределить память. Ведь теперь строке требуется больше памяти, аж на целый байт. Для перераспределения памяти нужно знать текущий размер строки. Вероятно, именно для того, что бы не приходилось каждый раз сканировать строку, определяя её размер, разработчики Delphi и включили поле длины, строки в эту структуру. Длина строки, хранится как значение Integer, отсюда и ограничение на максимальный размер таких строк – 2 Гбайт. Надеюсь, мы не скоро упрёмся в это ограничение. Кстати, именно потому, что память под эти строки выделяется динамически, они и получили ещё одно свое название: динамические строки.
Осталось рассказать ещё о нескольких особенностях переменных AnsiString. Важнейшей особенностью значений этого типа является возможность приведения их к типу Pointer. Это впрочем, естественно, ведь в "душе" они и есть указатели, как бы они этого не скрывали. Например, если описаны переменные: s :AnsiString и p :Pointer. То выполнение оператора p := Pointer(s) приведет к тому, что переменная p станет указывать на экземпляр строки. Однако, при этом, очень важно знать: счетчик ссылок этой строки не будет увеличен. Но об этом, мы поговорим чуть позднее.
Поскольку, переменные этого типа реально являются указателями, то для них и реально такое значение как Nil – указатель в "никуда". Это значение в переменной типа AnsiString по смыслу приравнивается пустой строке. Более того, чтобы не тратить память и время на ведение счётчика ссылок, и поля размера строки всегда равного 0, при присваивании пустой строке переменной этого типа, реально, присваивается значение Nil. Это не очевидно, поскольку обычно не заметно, но как мы увидим позже, очень важная особенность.
Вот теперь, кажется, Вы знаете о строках все. Настала пора переходить к более интересной части статьи – как с этим всем жить?
Использование строк в качестве параметров и результатов функций размещенных в DLL.
Многие, наверное, пытались хоть раз написать собственную Dll. Если при этом Вы использовали соответствующего мастера Delphi, то наверняка видели комментарий который он вставляет в начало файла библиотеки:
Общий смысл этого эпоса в том, что если Ваша Dll экспортирует хотя бы одну процедуру или функцию с типом параметра соответствующим любой динамической строке (AnsiString например), или функцию, возвращающую результат такого типа. Вы должны обязательно и в Dll, и в использующей ее программе, первым модулем в списке импорта (uses) указать модуль ShareMem. И как следствие, поставлять со своей программой и Dll еще одну стандартную библиотеку BORLNDMM.DLL.
Вы не задумывались над вопросами: "Зачем все эти сложности?"; "Что будет если этого не сделать?" и "Можно ли этого избежать?"; "Если да, то как?" Если не задумывались, то самое время сделать это.
Попробуем разобраться что будет происходить с экземплярами динамических строк в следующем примере:
Сначала, при выполнении процедуры X, функция IntToStr(100) создаст экземпляр динамической строки '100', и ее адрес будет помещен в переменную Str. Затем, адрес этой переменной будет передан процедуре Y. В ней, при выполнении оператора s := s+'$', будет создан экземпляр новый строки '100$', Экземпляр старой строки '100' станет не нужным и память, выделенная для него при создании, будет освобождена. Кроме того, при завершении процедуры X, будет освобождена и память, выделенная для строки '100$', так как перестанет существовать единственная ссылка на нее - переменная Str.
Всё вроде бы хорошо. Но до тех пор, пока обе процедуры располагаются в одном исполняемом модуле (EXE-файле). Если например поместить процедуру Y в Dll, а процедуру X оставить в EXE, то будет беда.
Дело в том, что выделением и освобождением памяти для экземпляров динамических строк занимается внутренний менеджер памяти Delphi-приложения. Использовать стандартный менеджер Windows очень накладно. Он слишком универсален, и потому медленный, а строки очень часто требуют перераспределения памяти. Вот разработчики Delphi и создали свой. Он ведет списки распределенной и свободной памяти своего приложения. Так вот, вся беда в том, что Dll будет использоваться свой менеджер памяти, а EXE свой. Друг о друге они ничего не знают. Поэтому, попытка освобождения блока памяти выделенного не своим менеджером приведёт к серьезному нарушению в его работе. Причем, это нарушение может проявиться далеко не сразу, и довольно необычным образом.
В нашем случае, память под строку '100' будет выделена менеджером EXE-файла, а освобождаться она будет менеджером DLL. То же произойдет и с памятью под строку '100$', только наоборот.
Для преодоления этой проблемы, разработчики Delphi создали библиотеку BORLNDMM.DLL. Она включает в себя еще один менеджер памяти :). Использование же модуля ShareMem, приводит к тому, что он заменяет встроенный в EXE (DLL) менеджер памяти на менеджер расположенный в BORLNDMM.DLL. Т.е., теперь и EXE-файл и DLL, будут использовать один, общий менеджер памяти.
Здесь важно отметить то, что если какой-либо из программных модулей (EXE или DLL) не будут иметь в списке импорта модуля ShareMem, то вся работа пойдет насмарку. Опять будут работать несколько менеджеров памяти. Опять будет бардак.
Ну вот, наконец-то и все. Теперь, Вы знаете о строках почти столько же как я :).
Конечно, этим тема не исчерпывается. Например, я ничего не рассказал о мультибайтовых строках (MBCS) используемых для мультиязыковых приложений. Может и еще что-то забыл рассказать. Но, не расстраивайтесь. Я свои знания получал, изучая книги, тексты своих и чужих программ, код сгенерированный компилятором, и т.п. Т.е., все из открытых источников. Значит это все доступно и Вам. Главное, чтобы Вы были любознательными, и почаще задавали себе вопросы "Как?", "Почему?", "Зачем?". Тогда во всем сможете разобраться и сами.
← →Zn ( 2002-11-14 10:31 ) [0]
Help me, please! Как корректно выделить и освободить память под PChar? Объем заранее неизвестен. Простите за тривиальный вопрос.
← →Calm ( 2002-11-14 10:37 ) [1]
Почему-то мне кажется, что delphi сама освободит.
var
MyChars:PChar;
begin
.
MyChars:="Hello word!";
.
end;
Alx2 ( 2002-11-14 10:43 ) [4]
>Zn (14.11.02 10:31)
Не иди на геморрой.
Используй String и приведение типов к PChar. Delphi остальное все сделает
Zn ( 2002-11-14 10:45 ) [5]
Перепробовал все эти варианты. Постоянно пишет "Invalid pointer operation". Не пойму в чем дело.
← →Calm ( 2002-11-14 10:46 ) [6]
> Не иди на геморрой.
> Используй String и приведение типов к PChar. Delphi остальное
> все сделает
Действительно :)
Вот правда, если нужно написать какую-нибуль dll, то иногда без PChar трудно :(
Calm ( 2002-11-14 10:47 ) [7]
А
> Перепробовал все эти варианты. Постоянно пишет "Invalid
> pointer operation". Не пойму в чем дело.
А конкретно, какие варианты (код)?
Zn ( 2002-11-14 10:56 ) [8]
function U866(var InpStr: PChar): PChar;
var i,z : Word;
ps: PChar;
begin
New(Result);
New(ps);
z:=Length(InpStr)-1;
for i:=0 to z do
begin
ps^:=InpStr[i];
case ps^ of
"в": Result:=strcat(Result,"
Calm ( 2002-11-14 11:08 ) [9]
Zn, страшные вещи пишите :)
А что, без PChar точно нельзя?
Alx2 ( 2002-11-14 11:10 ) [10]
>Zn (14.11.02 10:56)
Извини, но это бред.
Ты хочешь конвертер OemToAnsi написать?
Reindeer Moss Eater ( 2002-11-14 11:11 ) [11]
Так ты вернул PChar, а потом освободил память, не которую он указывал.
← →Skier ( 2002-11-14 11:12 ) [12]
>Zn
> Как корректно выделить и освободить память под PChar? Объем
> заранее неизвестен
Ну ежели объём заранее неизвестен, то можно просто кастингом
Что то типа :
А вообще при выделении и освобождении памяти спасает
AllocMem(. ) и FreeMem(. )
Zn ( 2002-11-14 11:12 ) [13]
Всё это будет сидеть DLL. Просто нужно перекодировать некоторые символы. Функцию будут использовать приложения Delphi и VB.
← →Reindeer Moss Eater ( 2002-11-14 11:13 ) [14]
Так ты вернул PChar, а потом освободил память, не которую он указывал.
Zn ( 2002-11-14 11:21 ) [15]
>Reindeer Moss Eater (14.11.02 11:13)
>Так ты вернул PChar, а потом освободил память, не которую он >указывал.
Т.е.? Освободил память под локальную переменную, которая, в принципе, и сама бы должна. А я для перестраховки. Просто с указателями не очень дружу.
Alx2 ( 2002-11-14 11:22 ) [16]
>Zn (14.11.02 11:12)
>Всё это будет сидеть DLL. Просто нужно перекодировать некоторые
>символы. Функцию будут использовать приложения Delphi и VB.
Просто перекодируем тогда по твоим мотивам:
Function U866(Const InpStr: PChar): PChar;
Var I : Integer;
Begin
Result := InpStr;
For I := 0 To StrLen(InpStr) - 1 Do
Case InpStr[I] Of
"в": Result[I] := "
Reindeer Moss Eater ( 2002-11-14 11:23 ) [17]
Dispose(ps) - это убиение строки, которую ты так заботливо собирал внутри функции
← →Alx2 ( 2002-11-14 11:24 ) [18]
>Alx2 © (14.11.02 11:22)
Кстати, InpStr=Result после отработки ф-ии
Ru ( 2002-11-14 11:26 ) [19]
>Zn (14.11.02 11:12)
слепой вася спокойно передает в дельфи строку:
declare function myfunc (byval st as string) as string
теперь в длл спокойно пишем:
function U866(var InpStr: string): PChar;
работаем с какой-либо внутренней переменно типа string, а в конце функции пишем:
result:=pchar(IntrStr);
во всяком случае для ворд васика прокатит.
icWasya ( 2002-11-14 11:27 ) [20]
Если посмотришь описание Windows процедур, которые используют PChar, то увидишь, что если процедура возвращает строку то она имеет приблизительно такое описание:
integer GetAnyThing(Buf:PChar;integer LenBuf);
то есть вызывающая программа заказывает массив под возвращаемую строку и передаёт процедуре размер массива и указатель на него,
а возвращает реальный размер записанных данных.
в данном случае код должен быть примерно таким:
function U866(InpStr: PChar;OutStr: PChar;OutLen:Integer):PChar;
var i,z : Word;
s: Char;
ps: PChar;
begin
i:=0;z:=0;
Result:=OutStr;
ps:=OutStr;
Это я пробовал. Не работает.
Alx2 ( 2002-11-14 11:36 ) [22]
>Zn (14.11.02 11:33)
>Это я пробовал. Не работает.
А мои посты тебе ничего не рассказали?
Zn ( 2002-11-14 11:36 ) [23]
Alx2 и icWasya! Благодарю за подсказку, сейчас попробую!
← →Zn ( 2002-11-14 12:34 ) [24]
Alx2! Ваш код работает отлично, но на последне строке функции (end;) выдает "Invalid pointer operation". Хотя дальше возвращаемое значение садится куда надо и родительская программа продолжает работать.
← →Alx2 ( 2002-11-14 13:04 ) [25]
>Zn (14.11.02 12:34)
как полностью объявлена ф-я в dll и как в родительской программе?
Zn ( 2002-11-14 14:01 ) [26]
>Alx2 © (14.11.02 13:04)
>как полностью объявлена ф-я в dll и как в родительской программе?
Идентично.
function U866(var InpStr: PChar): PChar;
Пытался вставлять в объявления stdcall, pascal или register - безрезультатно.
Alx2 ( 2002-11-14 14:05 ) [27]
>Zn (14.11.02 14:01)
>Пытался вставлять в объявления stdcall, pascal или register -
>безрезультатно.
Главное, чтобы в хосте и в dll спецификаторы вызова (stdcall, pascal или register) совпадали.
На всякий случай воткни в секцию uses модуль sharemem на первое место в dpr файле и в головном файле библиотеке. Из других мест (если есть) его выкинь.
Vga © ( 2006-12-06 11:55 ) [0]
Возник такой вопрос. Есть функция в DLL:
Foo(. ): PChar;
1) Как выделить память для результата?
2) Надо ли выполнять дополнительные действия по ее освобождению4 если да, то где и какие, если нет - то когда она будет освобождена?
BiN © ( 2006-12-06 12:02 ) [1]
Это зависит от реализации Foo
← →Vga © ( 2006-12-06 12:04 ) [2]
> [1] BiN © (06.12.06 12:02)
.
Я Foo и пишу. Мне надо выделить память для результата выполнения.
Romkin © ( 2006-12-06 12:04 ) [3]
1. Память здесь должна выделяться внутри функции. Вообще говоря, так писать вредно, потому что
2. Освобождать выделенную память должна вызывающая сторона, после использования. Как - зависит от того, как выделяется память в функции :)
В большинстве функций API сделано по другому, ты должен дать указатель на уже выделенный буфер и его размер. А результат функции - требуемый размер буфера. Это гораздо удобнее, ты выделяешь память :)
← →Vga © ( 2006-12-06 12:05 ) [4]
> [0] Vga © (06.12.06 11:55)
> освобождению4 если да, то где и какие
освобождению; если да, то где и какие
Vga © ( 2006-12-06 12:06 ) [5]
> [3] Romkin © (06.12.06 12:04)
Вот и у меня такие подозрения возникли. С другой стороны, мне помнится, есть в WinAPI функции, возвращающие PChar.
Сергей М. © ( 2006-12-06 12:14 ) [6]
> Vga
При таком подходе (выделение памяти в вызываемом модуле, а ее освобождение - в вызывающем модуле) должно быть соблюдено соглашение об использовании взвимодействующими модулями одного и того же менеджера памяти.
И пока это соглашение явно не определено, задача не имеет решения.
← →Romkin © ( 2006-12-06 12:16 ) [7]
> Я Foo и пишу. Мне надо выделить память для результата выполнения.
Ужас! Не пиши так! Пусть дают ссылку на буфер (см GetWindowText к примеру).
Reindeer Moss Eater © ( 2006-12-06 12:16 ) [8]
StrAlloc()
← →DevilDevil © ( 2006-12-06 12:19 ) [9]
> мне помнится, есть в WinAPI функции, возвращающие PChar.
Такие функции действительно есть. но они возвращают указатель на буффер, который ты указал в качестве одного из параметров этой функции. Вот такой вот WinApi-каламбур.
Кто выделяет, тот и освобождает.
← →Romkin © ( 2006-12-06 12:23 ) [11]
> Reindeer Moss Eater © (06.12.06 12:16) [8]
>
> StrAlloc()
Угу. И типа всем сразу юзать strDispose?!
Ну ты посоветуешь! Это же менеджер памяти Delphi, во-первых, а во-вторых, функции устарели.
На крайняк GlobalAlloc тогда уж.
Anatoly Podgoretsky © ( 2006-12-06 12:23 ) [12]
> Vga (06.12.2006 12:06:05) [5]
Большинство не возвращают, а использую выданный тобой буфер, с указанием размера или без.
← →clickmaker © ( 2006-12-06 12:26 ) [13]
Если функция возвращает уже распредленный в памяти буфер, то эта же ДЛЛ может предоставить функцию для ее освобождения. Как это сделано в НетАПИ - NetApiBufferFree
← →Romkin © ( 2006-12-06 12:34 ) [14]
clickmaker © (06.12.06 12:26) [13] Или обусловить, какой именно функцией надо освобождать буфер. (SHGetSpecialFolderLocation, например, но, к сожалению, в SDK Delphi нужной фразы и нет :( )
Обычно это - в случае сложной структуры буфера.
А для PChar проще всего попросить приложение предоставить буфер. Мне, например, удобно - дал вместо ссылки на буфер nil, размер указал 0, возвращает требуемый размер - выделяем память и вызываем снова :)
Vga © ( 2006-12-06 12:42 ) [15]
В общем понятно. Либо надо предоставлять функцию освобождения строки, либо возвращать ссылку на данные, которые будут верны все время, пока с ними работает вызывающая сторона (например, константу), либо не возвращать PChar.
← →Reindeer Moss Eater © ( 2006-12-06 12:46 ) [16]
Угу. И типа всем сразу юзать strDispose?!
Ну ты посоветуешь! Это же менеджер памяти Delphi, во-первых, а во-вторых, функции устарели.
На крайняк GlobalAlloc тогда уж.
Если отвлечься от вопроса, то я бы вообще посоветовал не создавать такие откровенно корявые функции.
function myfunc : PChar
Но раз уж она есть в вопросе, то вылделять память все равно надо, или функция эта бесполезна будет. Если только она не выполняет какое-нибудь бестолковое действие типа
Result := PChar(caption)
Reindeer Moss Eater © ( 2006-12-06 12:48 ) [17]
Кстати, каким местом устарела страллок?
function StrAlloc(Size: Cardinal): PChar;
begin
Inc(Size, SizeOf(Cardinal));
GetMem(Result, Size);
Cardinal(Pointer(Result)^) := Size;
Inc(Result, SizeOf(Cardinal));
end;
DevilDevil © ( 2006-12-06 12:51 ) [18]
> Reindeer Moss Eater © (06.12.06 12:48) [17]
>
> Кстати, каким местом устарела страллок?
GetMem - это функция, которая задействует Дельфийский менеджер памяти. Отсюда несовместимость.
← →Vga © ( 2006-12-06 12:51 ) [19]
> Но раз уж она есть в вопросе, то вылделять память все равно
> надо, или функция эта бесполезна будет. Если только она
> не выполняет какое-нибудь бестолковое действие типа
> Result := PChar(caption)
Вот это-то как раз пример, когда используется именно такая функция, так как никакой памяти выделять не надо. И насчет бестолковости действия вопрос спорный - иногда использующей стороне надо предоставить возможность чтения некоторых данных библиотеки.
Vga © ( 2006-12-06 12:55 ) [20]
Если ставить цель сохранить именно такое объявление, то можно выделить для возврата данных глобальную переменную, класть результат в нее и возвращать на нее ссылку, освобождать при финализации. Но это решение через то самое место :) К тому же, придется документировать, до каких пор результат содержит корректные данные, а доки не все читают. Однако решение с выделением буфера на вызывающей стороне тоже не очень хорошо, так как процесс получения результата (а следовательно и его длины) довольно продолжительный.
← →Anatoly Podgoretsky © ( 2006-12-06 12:55 ) [21]
> Vga (06.12.2006 12:42:15) [15]
Или что чаще всего делается, передавать этот буфер в функцию.
← →Anatoly Podgoretsky © ( 2006-12-06 12:56 ) [22]
> Vga (06.12.2006 12:51:19) [19]
Это тот случай, когда память выделять не надо, долго живущие данные. Такие есть и в АПИ
← →Vga © ( 2006-12-06 12:58 ) [23]
> [21] Anatoly Podgoretsky © (06.12.06 12:55)
Этот вариант входит в
> либо не возвращать PChar.
Reindeer Moss Eater © ( 2006-12-06 12:59 ) [24]
GetMem - это функция, которая задействует Дельфийский менеджер памяти. Отсюда несовместимостьи
Это не имеет значения.
1. Функция DLL написанная нечайником, никогда не распределит память сама, а скажет какой длины буфер ей надо что бы вернуть данные.
2. Если я узнал длину буфера, и у меня скажем нет переменной string которй я могу сделать setlength, то чем таким страшным устарела страллок, если я не собираюсь передвать ссылку ни в какой другой процесс?
← →Anatoly Podgoretsky © ( 2006-12-06 13:00 ) [25]
> Vga (06.12.2006 12:55:20) [20]
Поэтому таких в АПИ считаное количество, большинство функций АПИ ни таким, ни выделением не занимается, а использует буфер пользователя. Некоторые могут сообщить, какой по размеру требуется.
← →Anatoly Podgoretsky © ( 2006-12-06 13:04 ) [26]
> Vga (06.12.2006 12:58:23) [23]
Почему бы и нет, посмотри в справке функции работы с PChar многие из них возвращают как результат. Возвращение как результат позволяет использовать эту функцию в качестве параметра другой функции.
Твой вопрос без постановки задачи ничего не стоит.
Сначала надо поставить задачу, правда тогда и вопроса не будет.
Vga © ( 2006-12-06 13:05 ) [27]
Спасибо, свою ошибку я понял. Теперь надо думать, как реализовать. Склоняюсь к методу из [20] по причине удобства вызывающей стороны, так как результат обычно используется сразу, а если не так - можно скопировать.
Метод с передачей буфера не хотелось бы, так как функция не из быстрых, и вызывать два раза не хотелось бы.
DevilDevil © ( 2006-12-06 13:06 ) [28]
> Vga ©
А у тебя буфер переменной длинны?
Как вариант: в хедере к Dll, в implementation пусть статически будет лежать буфер. ПРи инициализации Dll, в Dll передаётся указатель на этот буфер. Соответственно, проблем с извлечением данных из буфера не будет.
← →Vga © ( 2006-12-06 13:07 ) [29]
> [26] Anatoly Podgoretsky © (06.12.06 13:04)
Вопрос довольно общий, навеяно конкретным случаем, из-за которого и возникли [20], [27], но я хотел получить информацию, которую можно использовать в других случаях, когда надо вернуть строковые данные. Такую информацию получил, спасибо всем.
Reindeer Moss Eater © ( 2006-12-06 13:08 ) [30]
Склоняюсь к методу из [20]
такое же корявое решение.
DLL вернув результат не должна париться над вопросом как долго вызывающее приложение будет юзать ссылку.
А приложение не должно париться можно ли менять содержимое по ссылке и вообще как долго будет живо содержимое этого буфера.
Vga © ( 2006-12-06 13:11 ) [31]
> [30] Reindeer Moss Eater © (06.12.06 13:08)
В общем случае. А частный я не описал, да и не буду, сам уже разобрался, так что [20] надо рассматривать как частный случай решения с ограниченной применимостью. Он ведь рабочий и при соблюдении ограничений вполне корректный.
Anatoly Podgoretsky © ( 2006-12-06 13:21 ) [32]
> Vga (06.12.2006 13:05:27) [27]
Можно вызывать только один раз, выдели буфер достаточного размера.
← →Anatoly Podgoretsky © ( 2006-12-06 13:22 ) [33]
> Vga (06.12.2006 13:11:31) [31]
Вопрос последний, а нужен ли PChar - для Дельфи не требуется, это если только извне, вин апи, user dll
← →Vga © ( 2006-12-06 13:26 ) [34]
> [33] Anatoly Podgoretsky © (06.12.06 13:22)
Требуется. Но это уже не по теме.
app © ( 2006-12-06 13:31 ) [35]
Ну почему не по теме, если не нужны, то тема сразу закрывается по причине отсутствия в необходимости.
Для справки string полностью совместим с PChar
← →Vga © ( 2006-12-06 13:35 ) [36]
> [35] app © (06.12.06 13:31)
Вопрос был, как передать именно PChar, точнее, языконезависимый строковый тип. Если бы ansistring подходили всегда и везде, вопроса бы и не возникло.
Vga © ( 2006-12-06 13:38 ) [37]
А учитывая, что ответ я получил, то можно тему и закрыть, ничего не имею против.
← →app © ( 2006-12-06 13:39 ) [38]
Ты не понял, не физически закрыть, а по причине отсутсвия вопроса.
← →Vga © ( 2006-12-06 13:41 ) [39]
> [38] app © (06.12.06 13:39)
Вопрос есть, но "нужен ли PChar" - вопрос вне темы, т.к. я спрашивал именно про PChar.
Anatoly Podgoretsky © ( 2006-12-06 13:53 ) [40]
> Vga (06.12.2006 13:35:36) [36]
← →Zn ( 2002-11-14 10:31 ) [0]
Help me, please! Как корректно выделить и освободить память под PChar? Объем заранее неизвестен. Простите за тривиальный вопрос.
← →Calm ( 2002-11-14 10:37 ) [1]
Почему-то мне кажется, что delphi сама освободит.
var
MyChars:PChar;
begin
.
MyChars:="Hello word!";
.
end;
Alx2 ( 2002-11-14 10:43 ) [4]
>Zn (14.11.02 10:31)
Не иди на геморрой.
Используй String и приведение типов к PChar. Delphi остальное все сделает
Zn ( 2002-11-14 10:45 ) [5]
Перепробовал все эти варианты. Постоянно пишет "Invalid pointer operation". Не пойму в чем дело.
← →Calm ( 2002-11-14 10:46 ) [6]
> Не иди на геморрой.
> Используй String и приведение типов к PChar. Delphi остальное
> все сделает
Действительно :)
Вот правда, если нужно написать какую-нибуль dll, то иногда без PChar трудно :(
Calm ( 2002-11-14 10:47 ) [7]
А
> Перепробовал все эти варианты. Постоянно пишет "Invalid
> pointer operation". Не пойму в чем дело.
А конкретно, какие варианты (код)?
Zn ( 2002-11-14 10:56 ) [8]
function U866(var InpStr: PChar): PChar;
var i,z : Word;
ps: PChar;
begin
New(Result);
New(ps);
z:=Length(InpStr)-1;
for i:=0 to z do
begin
ps^:=InpStr[i];
case ps^ of
"в": Result:=strcat(Result,"
Calm ( 2002-11-14 11:08 ) [9]
Zn, страшные вещи пишите :)
А что, без PChar точно нельзя?
Alx2 ( 2002-11-14 11:10 ) [10]
>Zn (14.11.02 10:56)
Извини, но это бред.
Ты хочешь конвертер OemToAnsi написать?
Reindeer Moss Eater ( 2002-11-14 11:11 ) [11]
Так ты вернул PChar, а потом освободил память, не которую он указывал.
← →Skier ( 2002-11-14 11:12 ) [12]
>Zn
> Как корректно выделить и освободить память под PChar? Объем
> заранее неизвестен
Ну ежели объём заранее неизвестен, то можно просто кастингом
Что то типа :
А вообще при выделении и освобождении памяти спасает
AllocMem(. ) и FreeMem(. )
Zn ( 2002-11-14 11:12 ) [13]
Всё это будет сидеть DLL. Просто нужно перекодировать некоторые символы. Функцию будут использовать приложения Delphi и VB.
← →Reindeer Moss Eater ( 2002-11-14 11:13 ) [14]
Так ты вернул PChar, а потом освободил память, не которую он указывал.
Zn ( 2002-11-14 11:21 ) [15]
>Reindeer Moss Eater (14.11.02 11:13)
>Так ты вернул PChar, а потом освободил память, не которую он >указывал.
Т.е.? Освободил память под локальную переменную, которая, в принципе, и сама бы должна. А я для перестраховки. Просто с указателями не очень дружу.
Alx2 ( 2002-11-14 11:22 ) [16]
>Zn (14.11.02 11:12)
>Всё это будет сидеть DLL. Просто нужно перекодировать некоторые
>символы. Функцию будут использовать приложения Delphi и VB.
Просто перекодируем тогда по твоим мотивам:
Function U866(Const InpStr: PChar): PChar;
Var I : Integer;
Begin
Result := InpStr;
For I := 0 To StrLen(InpStr) - 1 Do
Case InpStr[I] Of
"в": Result[I] := "
Reindeer Moss Eater ( 2002-11-14 11:23 ) [17]
Dispose(ps) - это убиение строки, которую ты так заботливо собирал внутри функции
← →Alx2 ( 2002-11-14 11:24 ) [18]
>Alx2 © (14.11.02 11:22)
Кстати, InpStr=Result после отработки ф-ии
Ru ( 2002-11-14 11:26 ) [19]
>Zn (14.11.02 11:12)
слепой вася спокойно передает в дельфи строку:
declare function myfunc (byval st as string) as string
теперь в длл спокойно пишем:
function U866(var InpStr: string): PChar;
работаем с какой-либо внутренней переменно типа string, а в конце функции пишем:
result:=pchar(IntrStr);
во всяком случае для ворд васика прокатит.
icWasya ( 2002-11-14 11:27 ) [20]
Если посмотришь описание Windows процедур, которые используют PChar, то увидишь, что если процедура возвращает строку то она имеет приблизительно такое описание:
integer GetAnyThing(Buf:PChar;integer LenBuf);
то есть вызывающая программа заказывает массив под возвращаемую строку и передаёт процедуре размер массива и указатель на него,
а возвращает реальный размер записанных данных.
в данном случае код должен быть примерно таким:
function U866(InpStr: PChar;OutStr: PChar;OutLen:Integer):PChar;
var i,z : Word;
s: Char;
ps: PChar;
begin
i:=0;z:=0;
Result:=OutStr;
ps:=OutStr;
Это я пробовал. Не работает.
Alx2 ( 2002-11-14 11:36 ) [22]
>Zn (14.11.02 11:33)
>Это я пробовал. Не работает.
А мои посты тебе ничего не рассказали?
Zn ( 2002-11-14 11:36 ) [23]
Alx2 и icWasya! Благодарю за подсказку, сейчас попробую!
← →Zn ( 2002-11-14 12:34 ) [24]
Alx2! Ваш код работает отлично, но на последне строке функции (end;) выдает "Invalid pointer operation". Хотя дальше возвращаемое значение садится куда надо и родительская программа продолжает работать.
← →Alx2 ( 2002-11-14 13:04 ) [25]
>Zn (14.11.02 12:34)
как полностью объявлена ф-я в dll и как в родительской программе?
Zn ( 2002-11-14 14:01 ) [26]
>Alx2 © (14.11.02 13:04)
>как полностью объявлена ф-я в dll и как в родительской программе?
Идентично.
function U866(var InpStr: PChar): PChar;
Пытался вставлять в объявления stdcall, pascal или register - безрезультатно.
Alx2 ( 2002-11-14 14:05 ) [27]
>Zn (14.11.02 14:01)
>Пытался вставлять в объявления stdcall, pascal или register -
>безрезультатно.
Главное, чтобы в хосте и в dll спецификаторы вызова (stdcall, pascal или register) совпадали.
На всякий случай воткни в секцию uses модуль sharemem на первое место в dpr файле и в головном файле библиотеке. Из других мест (если есть) его выкинь.
Пожалуйста, выделяйте текст программы тегом [сode=pas] . [/сode] . Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля. Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
2. Как "свернуть" программу в трей.
3. Как "скрыться" от Ctrl + Alt + Del (заблокировать их и т.п.)
4. Как прочитать список файлов, поддиректорий в директории?
5. Как запустить программу/файл?
. (продолжение следует) .
Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.
Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца.
Существует много различных функций выделения памяти, как встроеных в Делфи так и виндовских.
Какие из них лучше использовать, если необходимо передвать паметры между программой и программой, программой и ддлкой?
Функции GetMem, VitruaAlloc, ReallocMem и тд.
spider13, может проецируемые в память файлы (сам юзал и вот статейку в FAQ написал. скромно - но всё же) помогут?. + сорс имееццца
spider13, вспомни, как ты отвечаешь другим, и получи аналогичный ответ: В интернете полно информации о GetMem, VirtualAlloc и ReallocMem. Ищи здесь
(напомнить тебе, где ты отвечал в таком стиле?)
onyx, спс, но это не то что мне нужно.
volvo877, а вот на этот вопрос я бы хотел получить более подробный ответ, что эти функции делают и их параметры я могу найти в гугле, но не зря же их несколько в Винде зделали, значит есть какая то разница между ними..
Вот и хотелось бы узнать в чем разница, и какие лучше применять?
Функции и процедуры для работы с памятью и указателями
Addr Возвращает указатель на объект.
AllocMem Выделяет на куче блок памяти заданного размера, заполняет его нулями и возвращает указатель на начало блока.
CompareMem Выполняет бинарное сравнение двух участков памяти.
GetHeapStatus Возвращает текущее состояние диспетчера памяти.
GetMemoryManager Возвращает значения указателей полей текущего диспетчера памяти.
IsMemoryManagerSet Определяет, используется в настоящий момент диспетчер памяти, установленный по умолчанию, или был установлен другой диспетчер.
Ptr Возвращает указатель на адрес памяти, переданный в качестве аргумента.
SizeOf Возвращает размер памяти, занимаемый переменной.
SetMemoryManager Устанавливает значения полей диспетчера памяти.
SysFreeMem Высвобождает память, используемую динамической переменной.
SysGetMem Выделяет блок памяти заданного размера и возвращает указатель на него.
SysReallocMem Изменяет размер динамически распределенного блока памяти.
Процедуры для работы с динамическими переменными
Dispose Высвобождает память из-под динамической переменной.
Finalize Деинициализирует динамическую переменную.
FreeMem Высвобождает память из-под динамической переменной.
GetMem Создает динамическую переменную, выделяя под нее указанный объем памяти.
Initialize Инициализирует динамическую переменную.
New Создает динамическую переменную.
ReallocMem Перераспределяет память для динамической переменной.
--------------------------------------------------------------------------------
Функция AllocMem( Size: Cardinal ): Poiner;
Модуль: SysUtils
Описание: Функция выделяет на куче блок памяти размером Size байт, заполняет его нулями и возвращает указатель на начало блока.
--------------------------------------------------------------------------------
Функция SizeOf( X ): Integer;
Модуль: System
Описание: Функция возвращает размер памяти, которую занимает переменная X в байтах. Результат функции зависит только от типа переменной X и не зависит от ее значения. Данную функцию удобно использовать совместно с процедурами FillChar, Move, и GetMem.
--------------------------------------------------------------------------------
Процедура GetMem( var P: Pointer; Size: Integer );
Модуль: System
Описание: Процедура создает динамическую переменную: выделяет блок памяти размером Size байт под переменную, указанную в параметре P, и возвращает указатель на начало данного блока памяти. Параметр P может представлять собой любой тип указателя. Указатель на новую созданную переменную записывается как P^. Если для создания динамической переменной недостаточно памяти, то возникает исключение EOutOfMemory.
--------------------------------------------------------------------------------
Процедура FreeMem( var P: Pointer [; Size: Integer] );
Модуль: System
Описание: Процедура уничтожает переменную, с которой связан указатель P и высвобождает память, занимаемую данной переменной. В необязательном параметре Size указывается объем памяти в байтах, выделенный ранее динамически под переменную. Если после действия процедуры FreeMem, вызвать указатель P, то возникнет ошибка, т.к. указатель имеет неопределенное значение.
--------------------------------------------------------------------------------
Процедура ReallocMem (
var P: Pointer;
Size: Integer );
Модуль: System
Описание:
Процедура перераспределяет память размером Size байт под динамическую переменную P.
При вызове данной процедуры указатель P должен иметь значение nil или должен указывать на динамическую переменную, память под которую была предварительно выделена с помощью процедур GetMem или ReallocMem.
Если P = nil, Size = 0, то процедура не производит никаких действий.
Если P = nil, а Size <> 0, то процедура распределяет новый блок памяти размером Size и устанавливает указатель P на начало блока. Такой вызов процедуры аналогичен обращению к процедуре GetMem.
Если P <> nil, а Size = 0, то процедура высвобождает блок памяти, на который указывает P и устанавливает P = nil. Вызов процедуры с указанными параметрами аналогичен обращению к процедуре FreeMem, но в отличие от FreeMem процедура ReallocMem очищает указатель.
Если P <> nil и Size <> 0, то процедура устанавливает размер блока памяти, выделенный ранее под динамическую переменную P, равным Size. При этом существующие данные сохраняются. Если размер блока памяти будет увеличен в размерах, то данные в новой части блока будут неопределенными. Если новый размер блока памяти не может быть выделен в текущем адресном пространстве, то он перемещается на новое место и соответственно параметр P будет указывать на новый участок памяти.
Подробный формат команды можно увидель в Delphi нажав F1 или держа на Ctrl кликнуть на функцию.
Использование строк в записях
Проблема возникает тогда, когда одно поле (или несколько полей) имеют тип динамической строки. В этом случае, часто возникает проблема схожая с проблемой записи динамической строки в файл – не все данные лежат в записи, динамические строки представлены в ней лишь указателями. Решается проблема также как и с записью строк в файл. Жаль только что тогда нельзя будет оперировать (записать/прочитать) целиком всей записью. Строки придется обрабатывать особо. Можно сделать, например, так:
Обратите внимание на то, что перед записью в поток я делаю так, что бы в поле f3 попал указатель Nil. Если этого не сделать, то в поток попадет адрес текущего экземпляра динамической строки. При чтении, он будет прочитан в поле f3. Т.е. поле f3 станет указывать на какое-то место в памяти. При выполнении SetLength, поскольку Delphi сочтет что текущее значение f3 лежит по указанному адресу, будет попытка интерпретировать лежащую там информацию как динамическую строку. Если же в поток записать Nil, то SetLength, никуда лезть не будет – экземпляра-то нет.
Читайте также: