Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2004.03.09;
Скачать: [xml.tar.bz2];

Вниз

Передача Pchar через стек и возврат памяти.   Найти похожие ветки 

 
Soft   (2004-02-26 02:37) [0]

Функция работает при значении Buffersize:=1000000;, при значении Buffersize:=4000000; она перестает возвращать результат.

Function TestStr:Pchar;stdcall;
var HexStr:String;
var i,Buffersize:integer;
begin
Try
Buffersize:=1000000;
SetLength(HexStr,Buffersize);
for i:=1 to Buffersize do
HexStr[i]:="1";
Result:=Pchar(HexStr);
except on exception do;
end;
end;


Скорее всего данная проблема связана с ограничением размерности стека. Как это можно обойти без передачи строк по ссылке.


 
Alex Konshin   (2004-02-26 02:55) [1]

Нет, проблема связана с тем, что этот код вообще неправильный.
Тип String (так же, как и WideString и Variant) принципиально отличается от простых типов. Компилятор автоматически вставляет код финализации при выходе переменных этих типов за область видимости. Для String это означает, что уменьшается счетчик ссылок и, если он при этом обнулился, то память, занимаемая значением, освобождается. Именно освобождение памяти и происходит в твоем случае, т.к. перевод в PChar никак не изменяет счетчик ссылок, и при выходе и функции HexStr просто удаляется. То, что это иногда работает, говорит только о том, что твоя программа использует память не слишком интенсивно и ты еще успеваешь увидеть оставшийся мусор.

Как бороться?
- Либо увеличивать область видимости. Например, записывать в глобальную переменную, либо в поле объекта с глобальной областью видимости;
- Либо писать значение в статический буфер (просто в переменную-массив, которая определена не в заголовке функции-метода);
- Либо брать честно память и обязать того, кто получает это значение, освобождать его.

Судя по заголовку дело происходит в некой функции в DLL. Если вызывающая программа тоже твоя, то можешь рассмотреть еще возможность возврата String и использования юнита ShareMem.

PS: И не путай слова размерность и размер, это разные понятия. Хотя к твоей проблеме это не имеет отношение.


 
Soft   (2004-02-26 03:15) [2]

>>Alex Konshin © (26.02.04 02:55) [1]

>>Нет, проблема связана с тем, что этот код вообще неправильный.

Спасибо, потому что сам видел, что что-то не то. Но если использовать PChar вместо String с выделением памяти GetMemory то не происходит автоматической очистки этой памяти, это ясно, хоть строка тогда передается без проблем.

>>Как бороться?
>>- Либо увеличивать область видимости. Например, записывать в глобальную переменную, либо в поле объекта с глобальной областью видимости;

Программа не моя, а на PowerBuilder, так что этот вариант отпадает.

>>- Либо писать значение в статический буфер (просто в переменную-массив, которая определена не в заголовке функции-метода);

Этот вариант немного не понял, чем этот вариант отличается от последующего.

>>- Либо брать честно память и обязать того, кто получает это значение, освобождать его.

Проблема в том, что человек для которого делается эта Dll не умеет работать с памятью в PowerBuilder, как и передавать получать значение переменной по указателю, выделять освобождать память... При этом варианте, более вероятно что мне самому придется разбиратся в этом языке(PowerBuilder), что не хотелось бы.

>>PS: И не путай слова размерность и размер, это разные понятия. Хотя к твоей проблеме это не имеет отношение.

Ну извините, уже неделю не высыпаюсь нормально, так вот получилось, так что мог ошибится в значении слова.

Функция в результате читает бинарный файл и передает в hex формате значение этого бинарного файла(не спрашиваете зачем, не я так придумал:). Нельзя ли строку большого размера передать через стек, стандартными методами?


 
Alex Konshin   (2004-02-26 03:51) [3]

Извини, но никогда с PowerBuilder не приходилось иметь дела.
А этот PowerBuilder переменные Variant понимает? Если да, то можно попробовать передавать эти строки через OleVariant, только нужно формировать эти значения врукопашную, т.к. дельфийский Variant для строковых типов - нестандартный. Дело в том, что для распределения памяти под значения variant используется специальные системные вызовы, то есть любые приложения могут с ними работать корректно (если они вообще умеют с ними работать).

Вариант со статическим буфером скорее похож на первый, а не на последний. Опиши в заголовке DLL переменную, ей и присвой результат, и верни указатель на эту строку. Финт в том, что счетчик ссылок не обнулится. Но зато, имей в виду, что при следующем вызове туда запишется новое значение, а для старого счетчик обнулится и оно освободится. И этот прием нельзя использовать в multithread приложении, если возможны вызовы из разных thread.

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


 
Alex Konshin   (2004-02-26 03:54) [4]

Нельзя ли строку большого размера передать через стек, стандартными методами?
Когда ты возвращаешься из функции, то указатель стека тоже возращается в положение, в котором он был до вызова, так что через стек ты в принципе ничего передать несможешь (по крайней мере стандартными средствами.


 
Soft   (2004-02-26 04:08) [5]

Alex Konshin © (26.02.04 03:51) [3]

Спасибо, все понял.


 
Alex Konshin   (2004-02-26 04:10) [6]

Кстати, сразу не заметил: вообще-то обычно в непаскалевском мире предполагается, что значения PChar завершаются символом #0.
Так как у тея нигде и не пахнет указание длины, то скорее всего и в твоем случае должно быть так.
На свякий случай обращаю внимание, что для этого символа тоже нужно место, то есть размер буфера должен быть на 1 больше длины строки.


 
Alex Konshin   (2004-02-26 04:13) [7]

Поправка к последнему: это относится к случаю, если буфер будет в массиве. Если в String, то там Delphi сам добавляет #0 после конца строки.


 
Verg   (2004-02-26 07:53) [8]


> [3] Alex Konshin © (26.02.04 03:51)
> Извини, но никогда с PowerBuilder не приходилось иметь дела.
> А этот PowerBuilder переменные Variant понимает? Если да,
> то можно попробовать передавать эти строки через OleVariant,
> только нужно формировать эти значения врукопашную, т.к.
> дельфийский Variant для строковых типов - нестандартный.
> Дело в том, что для распределения памяти под значения variant
> используется специальные системные вызовы, то есть любые
> приложения могут с ними работать корректно (если они вообще
> умеют с ними работать).
>
> Вариант со статическим буфером скорее похож на первый, а
> не на последний. Опиши в заголовке DLL переменную, ей и
> присвой результат, и верни указатель на эту строку. Финт
> в том, что счетчик ссылок не обнулится. Но зато, имей в
> виду, что при следующем вызове туда запишется новое значение,
> а для старого счетчик обнулится и оно освободится. И этот
> прием нельзя использовать в multithread приложении, если
> возможны вызовы из разных thread.


Вот по этому поводу - как работает inet_ntoa для моногопоточных приложений?
Подозреваю, что даже не используя DLL_THREAD_ATTACH, DLL_THREAD_DETACH, можно вести статическую таблицу буферов для потоков, получая их ИД при вызове функции известным способом (GetCurrentThreadID). При этом предусмотреть удаление "устаревших записей" в этой таблице.


 
Alex Konshin   (2004-02-26 08:36) [9]

Но только не по нитям.
В ws2_32.dll просто по WSACleanup освобождается, не надо отслеживать нити (а кто мне запрещает передать эту строчку другой нити?).


 
Verg   (2004-02-26 08:42) [10]


> [9] Alex Konshin © (26.02.04 08:36)
> Но только не по нитям.
> В ws2_32.dll просто по WSACleanup освобождается, не надо
> отслеживать нити (а кто мне запрещает передать эту строчку
> другой нити?).


The string returned by inet_ntoa resides in memory that is allocated by Windows Sockets. The application should not make any assumptions about the way in which the memory is allocated. The data is guaranteed to be valid until the next Windows Sockets function call within the same thread—but no longer. Therefore, the data should be copied before another Windows Sockets call is made.

По-моему, именно отслеживаются нити. А WSAStartup/Cleanup тут вообще не при чем.


 
Anatoly Podgoretsky   (2004-02-26 08:45) [11]

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


 
Verg   (2004-02-26 08:50) [12]


> это не должно быть локальной переменной, кроме простых типов.


Не должно быть указателем на локальную переменную, простого она типа или сложного.


 
Alex Konshin   (2004-02-26 08:51) [13]

Вообще, что касается способов передачи строчек из/в dll, то я, помнится, просто либо экспортировал функции getmem/freemem из dll, либо, наоброт, в DLL реализовывал функцию, которая принимает в качестве параметра вектор указателей на функции, в числе которых функции выделения и освобождения памяти. Но это хорошо для случая, когда dll и приложение - наши и написаны на чем-нибудь типа Delphi, С/С++. Ну, а если все на Delphi, то можно и попроще.


 
Alex Konshin   (2004-02-26 08:52) [14]

По-моему, именно отслеживаются нити. А WSAStartup/Cleanup тут вообще не при чем.
Не, при таком раскладе все проще - указатель хранится в TLS.


 
Anatoly Podgoretsky   (2004-02-26 08:53) [15]

Verg © (26.02.04 08:50) [12]
Это тоже, все локальное находится на стеке и становится недействительным при выходе из функции.


 
Alex Konshin   (2004-02-26 08:59) [16]

Anatoly Podgoretsky © (26.02.04 08:45) [11]
Даже вне зависимости от всего выще сказаного, например если бы это была не строка, а локальный буфер, он все равно прекращает свое существование при выходе из функции, так что если нужно возвращать значение, это не должно быть локальной переменной, кроме простых типов.

Я немного не понял к чему именно относится этот комментарий.
Если про мои способы, то я там говорю про статические буферы.


 
Verg   (2004-02-26 09:03) [17]

Нет, Толя, я не понял почему локальная переменная не может возвращаться функцией
(
...так что если нужно возвращать значение, это не должно быть локальной переменной,...)

Вот функция, возвращает локальную переменную, при том сложного типа:

type
TFuncRet = record
A : integer;
B : boolean;
end;

function Func:TFuncRet;
var V : TFuncRet;
begin
V.A :=1234554321;
V.B := true;
result := V;
end;

Где ошибка?


 
Alex Konshin   (2004-02-26 09:10) [18]

Твоя ошибка в том, что эта запись была скопирована в result.
То есть все будет работать корректно, но это потому, что V никуда не передается.


 
Anatoly Podgoretsky   (2004-02-26 09:25) [19]

Alex Konshin © (26.02.04 08:59) [16]
Это не к твоему комментарию, а к вопросу автора

Verg © (26.02.04 09:03) [17]
Для простых случаев данные не на стеке, а возвращаются через регистр. А вот для указаного тобой случая данные на стеке, если конечно не произойдет копирования данных в возращаемый результат, и тогда они яявляются недействительными. Если посмотреть для других случае, когда запись не копируется, то будет более понятно. Наиболее наглядно это будет для PChar

function F: Pchar;
var
a: array[1..N] of ...
begin
Result := a;


 
Anatoly Podgoretsky   (2004-02-26 09:28) [20]

А правильно переписать твой код, чтобы не зависить от различных компиляторов так

function Func: TFuncRet;
begin
Result.A := 1234554321;
Result.B := true;
end;

Этим изначально исключается возможная ошибка, это уже относится к культуре программирования. Компилятор черный ящик и мы не даем возможности возникнуть трудно уловимой ошибке.


 
Verg   (2004-02-26 10:10) [21]


> [20] Anatoly Podgoretsky © (26.02.04 09:28)
> А правильно переписать твой код, чтобы не зависить от различных
> компиляторов так
>
> function Func: TFuncRet;
> begin
> Result.A := 1234554321;
> Result.B := true;
> end;
> Этим изначально исключается возможная ошибка, это уже относится
> к культуре программирования. Компилятор черный ящик и мы
> не даем возможности возникнуть трудно уловимой ошибке.


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

А вот это в корне неверно:


> function F: Pchar;
> var
> a: array[1..N] of ...
> begin
> Result := a;

Переменная и функция формально имеют разный тип.

Мало того, что это не скомпилируется вообще.

Даже если исправить ошибку и оперировать array[0..10] of char;
то

type
TFuncRet = array[0..10] of char;

function Func:pchar;
var a : array[0..10] of char;
begin
result := a;
end;

А в предыдущем случае result := @a[1]
Вот это и есть вернуть УКАЗАТЕЛЬ (Pchar) на локальную переменную.

А вот ниже - абсолютно корректная конструкция со всех точек зрения, включая "культуру программирования"

type
TFuncRet = array[1..10] of char;

function Func:TFuncRet;
var a : TFuncRet;
begin
result := a;
end;

Функция и переменная имеют ОДИНАКОВЫЙ тип.


 
Alex Konshin   (2004-02-26 10:21) [22]

Verg © (26.02.04 10:10) [21]
Твой пример корректный, только он не имеет отношения к вопросу.


 
Defunct   (2004-02-26 17:30) [23]

Btw, могу предложить вариант передачи скольугодно больших параметров через стек:

В DLL пишем:
Function SendfRecBuf:TMyStaticBuf;stdcall;
Var IncomingParams : TMyStaticBuf;
Begin

IncomingParams := Result
//Работаем с Result как с переданным параметром,
// в него же и помещаем результат (если надо)
Result := // any result if needed

End;


Здесь ассемблерный код для запуска вызова этой функии откуда угодно (хоть Delphi, хоть PowerBuilder (anything that supports asm), которая будет использовать область стека (Result) в качестве передаваемого параметра и результата одновременно


MyStaticBuf = .... Что угодно передать ...

...
asm
mov eax, SizeOf[TMyStaticBuf] // Костанта (длинна выделяемой памяти)
sub esp, eax // Зарезервировать область Result
mov edi, esp // Edi - начало области Result
mov esi, offset MyStaticBuf // адрес переменной в программе
push eax // Сохраним для восстановления позиции стека
push edi // сохраним для копирования результата
push esi // сохраним адрес лок. буфера (MyStaticBuf)
shr eax,2 // будем копировать по DWord (быстрее будет)
push eax // сохраним чтоб опять не вычислять длинну
mov ecx, eax // счетчик повторений уст. на кол-во Dword
Rep movsd // Копируем передваемый параметр в область Result
call SendBuf // вызов функции из DLL
// переданные нам параметры находятся в стеке
pop ecx // восстанавливаем размер данных
pop edi // Восстанавливаем адрес лок буфера (MyStaticBuf)
pop esi // Восстанавливаем адрес области Result
rep movsd // Копируем результат в переменную (MyStaticBuf)
pop eax // Восстанавливаем значение вершины стека
mov esp,eax // восстанавливаем esp
End;
..

// тут MyStaticBuf - содержит результат
// Ну и тут работаем с принятым буффером



 
Defunct   (2004-02-26 17:33) [24]

опечатка..
add esp,eax // восстанавливаем esp


 
Soft   (2004-02-26 17:37) [25]

Кончай спорить, вот рабочий код. Спасибо Alex Konshin.

unit binhexfunc;

interface

uses
SysUtils, Classes;

var HexStrOfBinFile:string;
Function BinFileToHexStr(BinFileName:PChar):Pchar;stdcall;
Function HexStrToBinFile(HexStr:PChar;BinFileName:PChar):longint;stdcall;

implementation

Function BinFileToHexStr(BinFileName:PChar):Pchar;stdcall;
var Buffer:String;
var F1:TFileStream;
var Buffersize:integer;
var Status:integer;
begin
Status:=-1;
try
Buffersize:=0;
//


 
Defunct   (2004-02-26 17:39) [26]

опечатка:

add esp,eax // восстанавливаем esp
End;
..


 
Verg   (2004-02-26 17:55) [27]


> [25] Soft © (26.02.04 17:37)

Оно может и так. Тут главное, чтобы ты понимал, что первая функция непотокобезопасна, и вызывающая прога (хост-приложение) должна это учитывать.


 
Soft   (2004-02-26 18:26) [28]

>>Verg © (26.02.04 17:55) [27]

Я знаю, обращение однопоточное к функции.



Страницы: 1 вся ветка

Форум: "Основная";
Текущий архив: 2004.03.09;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.54 MB
Время: 0.008 c
3-25699
Karlson
2004-02-10 19:30
2004.03.09
Обязательное поле, которое стало необязательным :)


14-25900
Soft
2004-02-15 03:51
2004.03.09
Россия. Воры в Законе.


1-25796
Ш-К
2004-02-26 09:40
2004.03.09
Работа с классами


6-25844
asdqwer
2003-12-29 18:19
2004.03.09
WebBrowser.Document.All.Tags


7-25950
YuRock
2003-12-19 14:47
2004.03.09
Программы для работы с измерителем VEEDER-ROOT





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский