View Full Version : سوال : رکورد در dll
baran_2005
شنبه 16 آبان 1388, 09:37 صبح
من یک رکورد تعریف کردم تا پارامترهای ارسالی به تابع زیاد نباشند و فقط رکورد و به توابع ارسال می کنم . رکورد در داخل برنامه اصلی و dll تعریف شده است یک متغیر سراسری در برنامه اصلی از نوع رکوردم دارم . امامشکل اینجاست که من هرگاه تابعی از داخل dll فراخوانی میکنم مقادیر رکوردم از بین میرن . با این که من تابعی رو فراخونی میکنم ه اصلا با این رکورد در ارتباط نیست و مقدار ان تغییر نمی کند . <لطفا کمک :عصبانی++:>
Mahmood_M
شنبه 16 آبان 1388, 10:03 صبح
هرگاه تابعی از داخل dll فراخوانی میکنم مقادیر رکوردم از بین میرن
منظورتون اینه که مقادیر رکورد در DLL از بین میره ؟ اگه اینطوره دلیلش اینه که با هر بار Load و UnLoad یا Free کردن DLL متغیرها و ... که توی DLL قرار داره هم دوباره لود میشن و مقدار داده شده به اونها هم از بین میره ، چطور DLL رو لود می کنید ؟ با هر بار فراخوانی یک تابع اون رو Free می کنید ؟
...
baran_2005
شنبه 16 آبان 1388, 10:11 صبح
من از dll ایستا استفاده میکنم و hedear توابع را یکبار تعریف کردم .در توابع داخل dll هم از دستور free استفاده نکردم به جز در چند مورد که برای بانک ها استفاده شده است . در رابطه با فرم ها هم قبلا بعد از اتمام کار Free می کردم که خودم این احتمال و دادم که اید از این بابت باشد . ان را هم حذف کردم . اما باز هم مشکل دارم .
Mahmood_M
شنبه 16 آبان 1388, 10:38 صبح
منظور این بود که آیا DLL رو بعد از هر بار فراخوانی Free می کنید ؟
مثال می زنم :
یک DLL داریم که یک رکورد و توابع زیر درش تعریف شده :
library Project1;
uses
SysUtils,
Classes;
type
MyRecord = Packed Record
MyIntValue : Integer;
MyStrValue : String;
end;
var
MR : MyRecord;
{$R *.res}
procedure SetIntValue(Value : Integer);
begin
MR.MyIntValue := Value;
end;
procedure GetIntValue(var N : Integer);
begin
N := MR.MyIntValue;
end;
exports
SetIntValue,
GetIntValue;
begin
end.
دستور SetIntValue یک مقدار رو در خاصیت MyIntValue از رکورد تعریف شده ثبت میکنه و دستور GetIntValue هم مقدار ثبت شده رو از MyIntValue می خونه ...
حالا یک پروژه ی جدید ایجاد میکنیم و 2 تا دکمه روی فرم می زاریم یکی برای اجرای دستور SetIntValue در DLL و یکی هم برای دستور GetIntValue :
برای SetIntValue :
procedure TForm1.SetBtnClick(Sender: TObject);
var
MyDLL : THandle;
SetIntV : Procedure(Value : Integer);
begin
MyDLL := LoadLibrary('Project1.dll');
@SetIntV := GetProcAddress(MyDLL, 'SetIntValue');
SetIntV(10);
end;
در کد بالا مقدار 10 رو در خاصیت MyIntValue از رکورد ثبت میکنیم ...
به این نکته توجه کنید که ما در کد بالا DLL رو Free نکردیم ...
برای GetIntValue :
procedure TForm1.GetBtnClick(Sender: TObject);
var
MyDLL : THandle;
GetIntV : Procedure(var N : Integer);
I : Integer;
begin
MyDLL := LoadLibrary('Project1.dll');
@GetIntV := GetProcAddress(MyDLL, 'GetIntValue');
GetIntV(I);
Caption := IntToStr(I);
end;
در کد بالا مقدار I برابر با مقدار MyIntValue در رکورد درون DLL خواهد شد ، چون ما بعد از قرار دادن مقدار 10 در خاصیت MyIntValue از رکورد DLL رو Free نکردیم ، مقدار 10 در اون خاصیت مونه و اگر کد ها رو به همین صورت بالا اجرا کنیم ، مقدار 10 در متغیر I قرار می گیره ...
اما اگر در پایان DLL رو Free کنیم :
procedure TForm1.SetBtnClick(Sender: TObject);
var
MyDLL : THandle;
SetIntV : Procedure(Value : Integer);
begin
MyDLL := LoadLibrary('Project1.dll');
@SetIntV := GetProcAddress(MyDLL, 'SetIntValue');
SetIntV(10);
FreeLibrary(MyDLL);
end;
بعد از کلیک بر روی دکمه ی مربوط به دستور GetIntValue ، مقدار I برابر 0 خواهد شد ، یعنی مقدار 10 که در خاصیت MyInyValue از رکورد بود پاک شده ، چون DLL رو Free کردیم ...
منظور من این بود که آیا همچین اتفاقی می افته یا نه ... ؟
یا شاید کلا بد متوجه شدم منظورتون رو ...
...
baran_2005
شنبه 16 آبان 1388, 11:02 صبح
من به عنوان نمونه یکی از توابع و رکورد رو در اینجا قرار دادم .
رکورد به صورت زیر در برنامه و dll تعریف کردم
type CurrDoreh=record
Doreh_No,Doreh_Name,Start_Day_,Start_Mon_,Start_Ye ar_,
End_Day_,End_Mon_, End_Year_:pchar;
x:integer;
end;
این کد داخل dll
function Curr_Doreh1(CDoreh1:CurrDoreh;controlconnection:ta doconnection ):CurrDoreh;stdcall;
var
s:CurrDoreh;
begin
try
with TADOQuery.Create(nil) do begin
Connection:=controlconnection;
SQL.Add('Select XDM0101,XDM0801,XDM0401,XDM0301,XDM0201,XDM0402,XD M0302,XDM0202 From Doreh');
if CDoreh1.Doreh_No<>'' then SQL.Add('Where XDM0101='+Fix_TextBox( CDoreh1.Doreh_No))
else SQL.Add('Where XDM1201=1');
Active:=True;
if RecordCount<>0 then begin
CDoreh1.Doreh_No:=pchar(FieldByName('XDM0101').AsS tring);
CDoreh1.Doreh_Name:=pchar( FieldByName('XDM0801').AsString);
CDoreh1.Start_Day_:=pchar( FieldByName('XDM0201').AsString);
CDoreh1.Start_Mon_:=pchar( FieldByName('XDM0301').AsString);
CDoreh1.Start_Year_:=pchar( FieldByName('XDM0401').AsString);
CDoreh1.End_Day_:=pchar( FieldByName('XDM0202').AsString);
CDoreh1.End_Mon_:=pchar(FieldByName('XDM0302').AsS tring);
CDoreh1.End_Year_:= pchar( FieldByName('XDM0402').AsString);
CDoreh1.x:=12;
end;
end;
if CDoreh1.Doreh_No<>'' then begin
with TADOCommand.Create(nil) do begin
Connection := controlconnection;
CommandText:='Update Doreh SET XDM1201=0 Where XDM0101<>'+Fix_TextBox( CDoreh1.Doreh_No);
Execute;
CommandText:='Update Doreh SET XDM1201=1 Where XDM0101='+Fix_TextBox( CDoreh1.Doreh_No);
Execute;
end;
end;
s:=CDoreh1;
result:=s;
except on e:exception do
showmessage(e.Message);
end;
end;
تعریف hedear داخل برنامه اصلی
function Curr_Doreh1(CDoreh1:CurrDoreh;controlconnection:ta doconnection ):CurrDoreh;stdcall;external 'procdll12.dll';
داخل برنامه اصلی به صورت سراسری متغیری تعریف کردم
var
CDoreh:CurrDoreh;
به عنوان مثال من اول این تابع رو فراخوانی می کنم و بعد مجدد با فراخوانی یک تابع دیگر وارد dll می شوم
CDoreh := Curr_Doreh1(CDoreh,d_m.ControlConnection ); // function in dll
ShowMessage( inttostr(CDoreh.Doreh_Name ) );
ShowWaitMsg(self); //function in dll
ShowMessage( inttostr(CDoreh.Doreh_Name ) );
با فراخوانی تابع curr_doreh رکورد cdoreh مقداردهی می شود . خروجی درست نشان داده می شود بعد از ورود مجدد به dll با فراخوانی تابع howwaitmsg مقدار خروجی cdoreh اشتباه میشود بدون انکه من تغییری در ان ایجاد کنم . البته فکر می کنم این مشکل فقط با نوع داده pchar باشد .
tdkhakpur
شنبه 16 آبان 1388, 11:09 صبح
بهترع داخل dll مستقيما از پارامتر تابع استفاده نكنيديك متغيير از نوع همان ساختار تعريف و پارامتر وارد شده را داخل ساختار جديد قرار بديد.
argument ->>> CDoreh1:CurrDoreh
var
newCDoreh:CurrDoreh;
begin
newCDoreh := CDoreh1;// و از اينجا به بعد از متغيير جديد استفاده كنيد.
ولي اگر به اينصورت هم حل نشد ساختار رو هم export كنيد و داخل برنامه قبل از فراخواني توابع موجود در dll اين ساختار را پركنيد.
baran_2005
شنبه 16 آبان 1388, 11:19 صبح
این روش رو هم قبلا امتحان کردم . باز هم همان مشکل به وجود می اید . نوع اده های من pchar اگر string کنم مشکل حل می شه اما باعث مشکلات دیگه ای در کدنویسی می شود .
Mahmood_M
شنبه 16 آبان 1388, 13:41 عصر
من با نوع داده Pchar در این مورد مشکلی نداشتم ، یک نمونه از روی سورس شما ایجاد کردم و امتحان کردم و مشکلی پیش نیومد ( خروجی ها یکسان بود ) ...
بهتر بود سورس ShowWaiteMsg رو هم قرار می دادید ، به احتمال قوی مشکل از همون باید باشه ...
از نوع PAnsiChar استفاده کردید ؟ در نسخه های پایینتر از دلفی 2009 توصیه میشه که از PAnsiChar به جای PChar استفاده بشه ...
نکته ی عجیب اینه که اطلاعات رکوردی که درون برنامه ( نه DLL ) تعریف شده تغییر می کنه !
توصیه می کنم سورس ShowWaiteMsg رو هم قرار بدید ...
...
vcldeveloper
شنبه 16 آبان 1388, 14:20 عصر
کد شما مشکلات متعددی داره، اولا اون اشیاء AdoQuery و AdoCommand ایی که می سازید را Free نمی کنید، و با این کد Memory Leak تولید می کنید.
از طرف دیگه، نحوه استفاده شما از PChar غلط هست. اینکه گفته میشه در DLL بجای string از PChar استفاده کنید، به معنی آن نیست که هر جا قرار بود از string استفاده کنید، بجای string بنویسید PChar، و مابقی چیزها به خودی خود حل می شود!
شما یک سری داده را به صورت PChar به تابع پاس دادید، و در داخل تابع بدون اینکه اصلا معلوم باشه آیا این PCharها به ساختار داده مناسبی اشاره می کنند یا نه، به آنها مقدار اختصاص دادید. در پایان کار هم بدون اینکه اصلا بررسی کنید آیا برنامه فراخوان تابع حافظه ایی برای مقدار خروجی تابع اختصاص داده یا نه، مقادیر مورد نظرتان را بصورت PChar به برنامه فراخوان تابع ارسال کردید.
شما در کد تابع خودتان یک سری string را به متغیرهای PChar نسبت دادید، ولی مقادیر این stringها جایی کپی نشدند، از طرف دیگه Type-cast کردن آنها به PChar موجب افزایش Reference-count آنها نمیشه، پس با خروج از تابع، مقادیر این stringها بطور خودکار حذف خواهند شد. در حالی که PCharهای شما همچنان به همان آدرس های حافظه اشاره می کنند!
کلا به نظر میاد شما از PChar استفاده کردید، ولی بدون اینکه درک درستی از آن و چگونگی استفاده از آن داشته باشید.
وقتی از PChar در پارامتر استفاده می کنید، کدی که تابع را فراخوانی میکنه باید یک آرایه را از قبل آماده کنه، و متغیر PChar مربوطه را طوری مقداردهی کنه که به این آرایه اشاره کنه، تا تابع مورد نظر بتونه مقادیر برگشتی را از طریق این PChar در آن فضای از قبل آماده شده کپی کنه.
از اونجایی که ظاهرا فقط برنامه دلفی خودتان از این DLL استفاده میکنه، بهتر هست بجای درگیر کردن خودتان با PChar، از یونیت SimpleShareMem استفاده کنید، و مقادیر پارامترها و فیلدهای رکورد را بصورت string تعریف کنید. اگر اصرار به استفاده از PChar دارید، درباره PChar تحقیق کنید، و همینطوری ازش استفاده نکنید.
در ضمن، برای اون مشکل Memory Leak ایجاد شده بخاطر ساختن اشیائی که Free نمیشند هم حتما راهکاری در نظر بگیرید، وگرنه با توجه به اینکه این اشیاء DataSet هستند و می تونند حجم زیادی از داده را در خود جای بدند، با چند بار فراخوانی این تابع یا توابعی مثل این، با چند ده یا چند صد مگابایت نشت حافظه مواجه میشید!
baran_2005
شنبه 16 آبان 1388, 17:47 عصر
کد شما مشکلات متعددی داره، اولا اون اشیاء AdoQuery و AdoCommand ایی که می سازید را Free نمی کنید، و با این کد Memory Leak تولید می کنید.
من اشیاء AdoQuery و AdoCommand را Free می کنم در این مثال خاص تنها می خواستم بدون free کردن اشیا و فرم ببینم از هم مشکل دارم یا نه .
شما در کد تابع خودتان یک سری string را به متغیرهای PChar نسبت دادید، ولی مقادیر این stringها جایی کپی نشدند،
من یک رکورد تعریف کردم که از نوع Var می باشد و این مقادیر در برنامه اصلی استفاده می شوند .
در مورد یونیت FastShareMem توضیح بیشتری بدهید .
vcldeveloper
شنبه 16 آبان 1388, 23:45 عصر
من یک رکورد تعریف کردم که از نوع Var می باشد و این مقادیر در برنامه اصلی استفاده می شوند .رکورد شما دارای فیلدهای PChar هست، و شما دارید به این فیلدها مقدار میدید. PChar یک نوع اشاره گر هست. فیلدهای PChar شما با استفاده از یک Type-Cast به یک سری string اشاره می کنند، ولی این stringها در پایان کار تابع از بین میرند، پس PCharهای شما به آدرس های غیرمعتبری در حافظه اشاره می کنند. اصلا روش استفاده از PChar به این صورت نیست. برنامه فراخوان تابع باید یک بافر مناسب اختصاص بده، و فیلدهای PChar به این بافرها اشاره کنند، و تابع مقادیر مورد نظرش را در این بافرها که آدرس شان توسط فیلدهای PChar مشخص شده، کپی کند.
در مورد یونیت SimpleShareMem توضیح بیشتری بدهید . توضیح خاصی نداره، یونیت SimpleShareMem عملکردی مثل یونیت معروف ShareMem دلفی داره که وقتی با استفاده از DLL Wizard دلفی یک DLL می سازید، در کامنت کدهای اولیه تولید شده، برای شما توضیح داده شده که اگر میخواید مقادیر string بین برنامه و DLL رد و بدل کنید، باید این یونیت ShareMem در هر دو پروژه به عنوان اولین یونیت بخش uses فایل DPR مربوطه درج بشه.
فرق SimpleShareMem با ShareMem معمولی این هست که SimpleShareMem برخلاف ShareMem موجب کاهش کارایی برنامه نمیشه، و نیازی هم به فایل DLL مدیر حافظه بورلند نداره. SimpleShareMem در دلفی 2007 و نسخه های بعد از آن موجود هست. برای نسخه های قدیمی تر دلفی، در اینترنت دنبال FastShareMem بگردید.
Saeed_m_Farid
یک شنبه 17 آبان 1388, 09:35 صبح
با فراخوانی تابع curr_doreh رکورد cdoreh مقداردهی می شود . خروجی درست نشان داده می شود بعد از ورود مجدد به dll با فراخوانی تابع howwaitmsg مقدار خروجی cdoreh اشتباه میشود بدون انکه من تغییری در ان ایجاد کنم . البته فکر می کنم این مشکل فقط با نوع داده pchar باشد .
ببینید اینطوری درست میشه؟ من یه تابع واسه کپی String به PChar نوشتم (که اشاره گر واسه متغیر جدید در حافظه اختصاص می دهد) و با اجازتون یه تغییراتی (که میدونم خودتون حذف کردین) دادم :
function Curr_Doreh1(CDoreh1: CurrDoreh;
controlconnection: TADOConnection ):CurrDoreh;stdcall;
var
strEx: String;
function Fix_TextBox(str: String): String;
begin
// I don't know what you are doing !!!
Result := str;
end;
function StrDup(str: String): PChar; // Duplicate String (pointer duplicate)
begin
GetMem(Result, Length(str)+1);
StrPCopy(Result, str);
end;
begin
try
with TADOQuery.Create(nil) do try
Connection:=controlconnection;
SQL.Add('Select XDM0101,XDM0801,XDM0401,XDM0301,XDM0201,XDM0402,XD M0302,XDM0202 From Doreh');
if CDoreh1.Doreh_No <> '' then
SQL.Add('Where XDM0101='+Fix_TextBox(CDoreh1.Doreh_No))
else
SQL.Add('Where XDM1201=1');
Active:=True;
if RecordCount<>0 then begin
CDoreh1.Doreh_No := StrDup(FieldByName('XDM0101').AsString);
CDoreh1.Doreh_Name := StrDup(FieldByName('XDM0801').AsString);
CDoreh1.Start_Day_ := StrDup(FieldByName('XDM0201').AsString);
CDoreh1.Start_Mon_ := StrDup(FieldByName('XDM0301').AsString);
CDoreh1.Start_Year_:= StrDup(FieldByName('XDM0401').AsString);
CDoreh1.End_Day_ := StrDup(FieldByName('XDM0202').AsString);
CDoreh1.End_Mon_ := StrDup(FieldByName('XDM0302').AsString);
CDoreh1.End_Year_ := StrDup(FieldByName('XDM0402').AsString);
CDoreh1.x := 12;
end;
finally
Free;
end;
if CDoreh1.Doreh_No<>'' then
with TADOCommand.Create(nil) do try
Connection := controlconnection;
CommandText:='Update Doreh SET XDM1201=0 Where XDM0101<>'
+Fix_TextBox(CDoreh1.Doreh_No);
Execute;
CommandText:='Update Doreh SET XDM1201=1 Where XDM0101='
+Fix_TextBox(CDoreh1.Doreh_No);
Execute;
finally
Free;
end;
Result:=CDoreh1;
except on e:exception do begin
strEx := e.ClassName + ': ' + e.Message;
ShowMessage(strEx);
end;
end;
end;
baran_2005
یک شنبه 17 آبان 1388, 10:32 صبح
با تشکر از همه دوستان
من از دلفی 2009 استفاده می کنم و fastsharemem را از ادرس زیر دانلود کردم . فقط یک سوال BORLNDMM.DLL باید حتما همراه پروژه باشد .
http://www.codexterity.com/download/fastsharemem-2.10.zip
http://dolba.net/tt/k2club/entry/FastSharemem?category=0
vcldeveloper
یک شنبه 17 آبان 1388, 15:23 عصر
ببینید اینطوری درست میشه؟ من یه تابع واسه کپی String به PChar نوشتم (که اشاره گر واسه متغیر جدید در حافظه اختصاص می دهد)
این روش، روش درستی نیست، باید اجازه بدید که برنامه فراخوان خودش حافظه مورد نیاز برای رشته ها را فراهم کنه، تا بعدا خودش هم بتونه حافظه مربوطه را آزاد کنه. الان در کد شما، تابع داخل DLL با استفاده از GetMem حافظه مورد نیاز را تامین میکنه، ولی این حافظه آزاد نمیشه، DLL نمیدونه کی باید این حافظه رو آزاد کنه، چون نمیدونه برنامه فراخوان کی کارش با این داده ها تمام میشه، پس نتیجه کار این میشه که شما Memory Leak می گیرید.
برای استفاده از PChar، توابع API ویندوز را به عنوان الگو در نظر بگیرید. هر وقت قرار باشه مقداری توسط این توابع به صورت PChar برگشت داده بشه، برنامه فراخوان آن تابع API خودش باید بافر را فراهم کنه، و به تابع بده، تابع API خودش این بافر را ایجاد نمیکنه.
فقط یک سوال BORLNDMM.DLL باید حتما همراه پروژه باشد .
من در پست های قبلی یک اشتباهی کردم؛ و FastShareMem را معرفی کردم. FastShareMem یونیتی هست که از مدت ها قبل بصورت Open-source منتشر شد، و ربطی به شرکت بورلند یا Embarcadero نداره. من این یونیت را در زمان دلفی 7 استفاده می کردم. اون یونیتی که به صورت پیش فرض به همراه دلفی 2007 و نسخه های بعد از آن (از جمله دلفی 2009) منتشر میشه، یونیت SimpleShareMem هست.
یونیت SimpleShareMem نیازی به فایل BorlandMM.dll نداره. البته FastShareMem هم نیازی به این فایل نداشت، ولی با توجه به اینکه SimpleShareMem توسط خودِ دلفی ارائه میشه، و از همون مدیر حافظه دلفی (که در نسخه های 2007 به بعد FastMM هست) استفاده میکنه، بهتر هست که شما از SimpleShareMem استفاده کنید.
نه، تا جایی که یادم هست، FastShareMem به BorlandMM.dll نیازی نداره.
Saeed_m_Farid
دوشنبه 18 آبان 1388, 01:01 صبح
این روش، روش درستی نیست، باید اجازه بدید که برنامه فراخوان خودش حافظه مورد نیاز برای رشته ها را فراهم کنه، تا بعدا خودش هم بتونه حافظه مربوطه را آزاد کنه. الان در کد شما، تابع داخل DLL با استفاده از GetMem حافظه مورد نیاز را تامین میکنه، ولی این حافظه آزاد نمیشه، DLL نمیدونه کی باید این حافظه رو آزاد کنه، چون نمیدونه برنامه فراخوان کی کارش با این داده ها تمام میشه، پس نتیجه کار این میشه که شما Memory Leak می گیرید.
ممنون، میدونم این کار درست نیست و تا حالا خودم همچین کاری نکردم یا ندیده بودم کسی هم همچین کاری بکنه، گفتم با کمترین تغییرات یه تابع مشابه تابع دوستمون نوشته بشه...
ولی یه سوالی که واسم پیش اومد اینه که : مگه متغیر از نوع ساختار بعنوان خروجی به برنامه استفاده کننده داده نمیشه؟ پس اگه اعضاء رکورد موردنظر Free بشن (یعنی مقدار بازگشتی از تابع موردنظر)، مگه حافظه آزاد نمیشه؟ منظورم اینه که کارمون که تموم شد مثلاً نمیتونیم CDoreh1.Doreh_Name رو FreeMem کنیم؟
فکر کنم من از اول بحث نبودم و زیاد تو باغ نیستم والا ما خروجی تابع (حالا تو dll یا جای دیگه) و اشاره گر اون رو داریم، چرا باید نگران Memory leak باشیم؟ هروقت کارمون تموم شد : حافظه رو آزاد می کنیم.
vcldeveloper
دوشنبه 18 آبان 1388, 03:34 صبح
منظورم اینه که کارمون که تموم شد مثلاً نمیتونیم CDoreh1.Doreh_Name رو FreeMem کنیم؟از اونجایی که DLL و برنامه EXE از مدیر حافظه های جداگانه ایی استفاده می کنند، در آزاد کردن حافظه Heapایی که در DLL اختصاص داده شده، از داخل EXE به مشکل بر می خورید. البته این رو من تست نکردم، ولی به احتمال زیاد بخاطر مدیر حافظه های جداگانه دچار مشکل میشید.
البته اگر هم مشکلی بوجود نمیامد، این فقط برای PChar صادق می شد که مشخص هست به یک string اشاره میکنه، و میشه طول string را بدست آورد. برای اشاره گری که حجم داده مورد اشاره اش مشخص نباشه، حتی اگر مدیر حافظه مشترک هم باشه، نمیشه حافظه رو آزاد کرد، مگر اینکه حجم داده را هم داشته باشیم.
مگه متغیر از نوع ساختار بعنوان خروجی به برنامه استفاده کننده داده نمیشه؟ساختار مورد نظر طولش برابر با طول حافظه اختصاص داده شده نیست. فیلدهای رکورد اشاره گر هستند، پس فقط چهار بایت اشغال می کنند، ولی این اشاره گر میتونه به یک داده چند صد مگابایتی اشاره کنه که حافظه اش در DLL اختصاص پیدا کرده، و بدون دانستن اندازه آن، نمیشه حافظه اختصاص داده شده بهش را آزاد کرد.
vBulletin® v4.2.5, Copyright ©2000-1404, Jelsoft Enterprises Ltd.