Yusefnejad

یوسف نژاد

Yusefnejad

یوسف نژاد

تو مطلب ساخت اسمبلی مدیریت شده، درباره نحوه کارکردن با کامپایلر #C بحث شد. روشها و انواع مختلف قابل تولید توسط این کامپایلر شرح داده شد. علاوه بر موارد اشاره شده، این کامپایلر یه سوییچ مهم دیگه برای تولید یه ماژول مدیریت شده هم داره. این سوییچ بصورت t:module/ استفاده میشه و مشخص میکنه که کامپایلر به جای تولید یه اسمبلی کامل فقط باید یه ماژول مدیریت شده تولید کنه.

این ماژولها درواقع فایلهای PE هستن که «مانفیست» ندارن. این فایلها قابلیت اجرای مستقیم ندارن (به فرمت فایلهای DLL تولید میشن) و همیشه باید به یه اسمبلی کامل اضافه بشن تا CLR بتونه از اونا استفاده کنه. با بکاربردن این سوییچ، کامپایلر #C بصورت پیشفرض از پسوند فایل netmodule. (نماینده عبارت «دات نت ماژول») استفاده میکنه.


ساخت اسمبلی چندفایله


برای ساخت یه اسمبلی چندفایله ابتدا باید یکی از فایلهای PE اسمبلی رو به عنوان فایل اصلی انتخاب کرد تا مانفیست اسمبلی تو اون ذخیره بشه. البته میشه یه فایل جدید به عنوان فایل اصلی اسمبلی تولید کرد تا محتوایی جز مانفیست اسمبلی نداشته باشه.


نکته: متاسفانه محیط توسعه ویژوال استودیو امکان تولید اسمبلی های چندفایله رو نداره و برای تولید این نوع از اسمبلی ها همیشه باید به ابزارهایی با خط فرمان کار میکنن رجوع کرد.


برای افزودن یه ماژول مدیریت شده به یه اسمبلی، روشهای مختلفی وجود داره. یکی از این روشها، استفاده از سوییچ addmodule/ کامپایلر #C هست. برای روشنتر شدن بحث، مثالی ارائه میشه. تو این مثال، دو فایل سورس کد وجود داره:

- source1.cs حاوی دسته اول نوع های برنامه

- source2.cs حاوی دسته دوم نوع ها

محتوای فایل source1.cs بصورت زیره:

public interface IWriter
{
  void Write(string value);
}

public class ConsoleWriter : IWriter
{
  public void Write(string value)
  {
    System.Console.Write(value);
  }
}

با استفاده از دستور زیر ماژول مدیریت شده حاوی نوع های تعریف شده تو فایل source1.cs تولید میشه:

csc /t:module /out:d:\source1.netmodule d:\source1.cs

اجرای این دستور تو تصویر زیر نشون داده شده:

با اجرای این دستور، کامپایلر #C یه فایل به نام source1.netmodule تولید میکنه. این فایل یه dll استاندارد به همراه داده های متادیتای بدون مانیفسته. بنابراین CLR امکان بارگذاری اون رو بصورت مجزا و به تنهایی نداره. برای کنجکاوی بیشتر این ماژول رو با استفاده از ابزار ILDasm.exe بررسی میکنیم. نتیجه چیزی شبیه تصویر زیره:

با کمال تعجب مشاهده میشه که یه بخش به نام «M A N I F E S T» وجود داره. درصورتیکه با توجه به مطالب ارائه شده تا اینجا، نباید چیزی به نام مانیفست برای ماژول های مدیریت شده که بخشی از یه اسمبلی کامل هستن تولید بشه. برای بررسی بیشتر، محتوای این قسمت (که با دابل کلیک روی MANIFEST نشون داده میشه) تو تصویر زیر آورده شده:

همونطور که مشاهده میشه این بخش داده مهم و خاصی نداره و خبری از جداول مانیفست معرفی شده تو این مطلب (که محتوای واقعی و اصلی مانیفست هستن) نیست. بنابراین عملا برای این ماژول مدیریت شده که بخشی از یه اسمبلی کامله، مانیفستی تولید نشده. (اطلاعاتی درباره محتوای این پنجره خاص)

در ادامه به فایل source2.cs که محتوای زیر رو داره پرداخته میشه:

public class DataContext
{
  private IWriter _logger;
  public DataContext(IWriter logger)
  {
    _logger = logger;
  }
  
  public void Save()
  {
    // save context
    _logger.Write("DataContext Saved");
  }
}

همونطور که مشخصه، تو این کد از اینترفیس تعریف شده تو سورس کد قبلی (IWriter) استفاده شده. تو گام بعدی کامپایل این سورس کد انجام میشه. اما تو این بخش از فایل نهایی تولید شده به عنوان فایل اصلی اسمبلی هم استفاده میشه. درضمن اینجا فرض میشه که اسمبلی موردنظر یه اسمبلی کتابخونه ای و بدون قابلیت اجرای مستقیمه. یعنی فایل نهایی پسوند dll خواهد داشت. برای اینکار از دستور زیر استفاده میشه:

csc /out:d:\asm.dll /t:library /addmodule:source1.netmodule source2.cs

نحوه اجرای این دستور تو تصویر زیر نشون داده شده:

این دستور به کامپایلر #C میگه که یه اسمبلی کتابخونه ای به نام asm.dll با کامپایل سورس کد source2.cs تولید کن. این فایل نهایی علاوه بر متادیتا، حاوی داده های مانیفست هم خواهد بود. همچنین سوییچ addmodule:source1.netmodule/ مشخص میکنه که ماژول source1 هم به عنوان بخشی از اسمبلی باید تو تولید فایل نهایی درنظر گرفته بشه. بنابراین تو مانیفست تولیدی این مورد دخالت داده میشه. درواقع تو جدول FileDef این مانیفست، یه رکورد برای این ماژول درج میشه. درضمن تمامی نوع های عمومی تعریف شده تو این ماژول تو جدول ExportedTypesDef ثبت میشن.

برای بررسی این اسمبلی تولید شده، محتوای اون رو با استفاده از ابزار ILDasm.exe مشاهده میکنیم که نتیجه تو تصویر زیر آورده شده:

همونطور که مشاهده میشه این فایل هم قسمتی به نام MANIFEST داره که محتوای اون تو تصویر زیر نشون داده شده:

مشاهده میشه که برخلاف ماژول source1.netmodule، اینجا کلیه جداول مانیفست موردنیاز نشون داده میشه. بنابراین این فایل، فایل اصلی اسمبلی مدیریت شده قلمداد میشه. البته دسته بندی بهتر و مناسبتر داده های مانیفست با مشاهده متادیتای هر ماژول (که در ادامه بصورت کامل آورده شده) در دسترسه.

تصویر زیر نمایی کلی از اونچه که تو این مثال تولید شده رو نشون میده:

فایل source1.netmodule حاوی کد IL حاصل از کامپایل سورس کد source1.cs هست. همچنین جداول متادیتای تولیدشده توسط کامپایلر برای این سورس کد که نوع ها و اعضای اونا رو توصیف میکنه هم تو این فایل وجود داره. محتوای این متادیتا برای مثال جاری بدین صورته:

===========================================================
ScopeName : source1.netmodule
MVID      : {3A6AF39A-5371-4424-B905-CA4CB4FBE498}
===========================================================
Global functions
-------------------------------------------------------

Global fields
-------------------------------------------------------

Global MemberRefs
-------------------------------------------------------

TypeDef #1 (02000002)
-------------------------------------------------------
	TypDefName: IWriter  (02000002)
	Flags     : [Public] [AutoLayout] [Interface] [Abstract] [AnsiClass]  (000000a1)
	Extends   : 01000000 [TypeRef] 
	Method #1 (06000001) 
	-------------------------------------------------------
		MethodName: Write (06000001)
		Flags     : [Public] [Virtual] [HideBySig] [NewSlot] [Abstract]  (000005c6)
		RVA       : 0x00000000
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  String
		1 Parameters
			(1) ParamToken : (08000001) Name : value flags: [none] (00000000)


TypeDef #2 (02000003)
-------------------------------------------------------
	TypDefName: ConsoleWriter  (02000003)
	Flags     : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100001)
	Extends   : 01000001 [TypeRef] System.Object
	Method #1 (06000002) 
	-------------------------------------------------------
		MethodName: Write (06000002)
		Flags     : [Public] [Final] [Virtual] [HideBySig] [NewSlot]  (000001e6)
		RVA       : 0x00002050
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  String
		1 Parameters
			(1) ParamToken : (08000002) Name : value flags: [none] (00000000)

	Method #2 (06000003) 
	-------------------------------------------------------
		MethodName: .ctor (06000003)
		Flags     : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor]  (00001886)
		RVA       : 0x0000205a
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.

	InterfaceImpl #1 (09000001)
	-------------------------------------------------------
		Class     : ConsoleWriter
		Token     : 02000002 [TypeDef] IWriter


TypeRef #1 (01000001)
-------------------------------------------------------
Token:             0x01000001
ResolutionScope:   0x23000001
TypeRefName:       System.Object
	MemberRef #1 (0a000003)
	-------------------------------------------------------
		Member: (0a000003) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.

TypeRef #2 (01000002)
-------------------------------------------------------
Token:             0x01000002
ResolutionScope:   0x23000001
TypeRefName:       System.Runtime.CompilerServices.RuntimeCompatibilityAttribute
	MemberRef #1 (0a000001)
	-------------------------------------------------------
		Member: (0a000001) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.

TypeRef #3 (01000003)
-------------------------------------------------------
Token:             0x01000003
ResolutionScope:   0x23000001
TypeRefName:       System.Runtime.CompilerServices.AssemblyAttributesGoHere
	CustomAttribute #1 (0c000001)
	-------------------------------------------------------
		CustomAttribute Type: 0a000001
		CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor()
		Length: 30
		Value : 01 00 01 00 54 02 16 57  72 61 70 4e 6f 6e 45 78 >    T  WrapNonEx<
                      : 63 65 70 74 69 6f 6e 54  68 72 6f 77 73 01       >ceptionThrows   <
		ctor args: ()


TypeRef #4 (01000004)
-------------------------------------------------------
Token:             0x01000004
ResolutionScope:   0x23000001
TypeRefName:       System.Console
	MemberRef #1 (0a000002)
	-------------------------------------------------------
		Member: (0a000002) Write: 
		CallCnvntn: [DEFAULT]
		ReturnType: Void
		1 Arguments
			Argument #1:  String

AssemblyRef #1 (23000001)
-------------------------------------------------------
	Token: 0x23000001
	Public Key or Token: b7 7a 5c 56 19 34 e0 89 
	Name: mscorlib
	Version: 4.0.0.0
	Major Version: 0x00000004
	Minor Version: 0x00000000
	Build Number: 0x00000000
	Revision Number: 0x00000000
	Locale: 
	HashValue Blob:
	Flags: [none] (00000000)


User Strings
-------------------------------------------------------
70000001 : ( 1) L" "


Coff symbol name overhead:  0
===========================================================
===========================================================
===========================================================

فایل asm.dll در اینجا مثل فایل source1.netmodule حاوی کد IL که نتیجه کامپایل فایل سورس کد source2.cs هست. این فایل هم حاوی جداول متادیتای توصیف کننده اجزای مختلف موجوده. محتوای این متادیتا برای مثال جاری در ادامه آورده شده:

===========================================================
ScopeName : asm.dll
MVID      : {B03F2AB0-A424-4343-B1A4-81D4EFEEAAC5}
===========================================================
Global functions
-------------------------------------------------------

Global fields
-------------------------------------------------------

Global MemberRefs
-------------------------------------------------------

TypeDef #1 (02000002)
-------------------------------------------------------
	TypDefName: DataContext  (02000002)
	Flags     : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100001)
	Extends   : 01000002 [TypeRef] System.Object
	Field #1 (04000001)
	-------------------------------------------------------
		Field Name: _logger (04000001)
		Flags     : [Private]  (00000001)
		CallCnvntn: [FIELD]
		Field type:  Class IWriter

	Method #1 (06000001) 
	-------------------------------------------------------
		MethodName: .ctor (06000001)
		Flags     : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor]  (00001886)
		RVA       : 0x00002050
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  Class IWriter
		1 Parameters
			(1) ParamToken : (08000001) Name : logger flags: [none] (00000000)

	Method #2 (06000002) 
	-------------------------------------------------------
		MethodName: Save (06000002)
		Flags     : [Public] [HideBySig] [ReuseSlot]  (00000086)
		RVA       : 0x00002062
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.


TypeRef #1 (01000001)
-------------------------------------------------------
Token:             0x01000001
ResolutionScope:   0x23000001
TypeRefName:       System.Runtime.CompilerServices.RuntimeCompatibilityAttribute
	MemberRef #1 (0a000001)
	-------------------------------------------------------
		Member: (0a000001) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.

TypeRef #2 (01000002)
-------------------------------------------------------
Token:             0x01000002
ResolutionScope:   0x23000001
TypeRefName:       System.Object
	MemberRef #1 (0a000003)
	-------------------------------------------------------
		Member: (0a000003) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.

TypeRef #3 (01000003)
-------------------------------------------------------
Token:             0x01000003
ResolutionScope:   0x1a000001
TypeRefName:       IWriter
	MemberRef #1 (0a000004)
	-------------------------------------------------------
		Member: (0a000004) Write: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  String

TypeRef #4 (01000004)
-------------------------------------------------------
Token:             0x01000004
ResolutionScope:   0x23000001
TypeRefName:       System.Runtime.CompilerServices.CompilationRelaxationsAttribute
	MemberRef #1 (0a000002)
	-------------------------------------------------------
		Member: (0a000002) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  I4

ModuleRef #1 (1a000001)
-------------------------------------------------------
		ModuleRef: (1a000001) source1.netmodule: 

Assembly
-------------------------------------------------------
	Token: 0x20000001
	Name : asm
	Public Key    :
	Hash Algorithm : 0x00008004
	Version: 0.0.0.0
	Major Version: 0x00000000
	Minor Version: 0x00000000
	Build Number: 0x00000000
	Revision Number: 0x00000000
	Locale: 
	Flags : [none] (00000000)
	CustomAttribute #1 (0c000001)
	-------------------------------------------------------
		CustomAttribute Type: 0a000001
		CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor()
		Length: 30
		Value : 01 00 01 00 54 02 16 57  72 61 70 4e 6f 6e 45 78 >    T  WrapNonEx<
                      : 63 65 70 74 69 6f 6e 54  68 72 6f 77 73 01       >ceptionThrows   <
		ctor args: ()

	CustomAttribute #2 (0c000002)
	-------------------------------------------------------
		CustomAttribute Type: 0a000002
		CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32)
		Length: 8
		Value : 01 00 08 00 00 00 00 00                          >                <
		ctor args: (8)


AssemblyRef #1 (23000001)
-------------------------------------------------------
	Token: 0x23000001
	Public Key or Token: b7 7a 5c 56 19 34 e0 89 
	Name: mscorlib
	Version: 4.0.0.0
	Major Version: 0x00000004
	Minor Version: 0x00000000
	Build Number: 0x00000000
	Revision Number: 0x00000000
	Locale: 
	HashValue Blob:
	Flags: [none] (00000000)


File #1 (26000001)
-------------------------------------------------------
	Token: 0x26000001
	Name : source1.netmodule
	HashValue Blob : 1f bc 2d cb b2 3b 74 57  12 f8 77 78 71 d5 94 86  c7 64 f8 b8 
	Flags : [ContainsMetaData]  (00000000)


ExportedType #1 (27000001)
-------------------------------------------------------
	Token: 0x27000001
	Name: IWriter
	Implementation token: 0x26000001
	TypeDef token: 0x02000002
	Flags     : [Public] [AutoLayout] [Interface] [Abstract] [AnsiClass]  (000000a1)


ExportedType #2 (27000002)
-------------------------------------------------------
	Token: 0x27000002
	Name: ConsoleWriter
	Implementation token: 0x26000001
	TypeDef token: 0x02000003
	Flags     : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100001)


User Strings
-------------------------------------------------------
70000001 : (17) L"DataContext Saved"


Coff symbol name overhead:  0
===========================================================
===========================================================
===========================================================

اما همونطور که تو تصویر بالا هم معلومه، فایل asm.dll که در واقع فایل اصلی اسمبلی هم محسوب میشه، علاوه بر اینها، داده های مانیفست اسمبلی رو هم درخودش جا داده که قبلا نشون داده شد.

نکته ای که باید تو محتوای متادیتای فایل اصلی اسمبلی بیشتر بهش دقت بشه، بخش انتهایی اونه که حاوی داده های مانیفست اسمبلیه، و همونطور که مشخصه این بخش در متادیتای ماژول source1.netmodule وجود داره. برای روشنتر شدن موضوع این قسمت بصورت جداگانه در ادامه آورده میشه:

File #1 (26000001)
-------------------------------------------------------
	Token: 0x26000001
	Name : source1.netmodule
	HashValue Blob : 1f bc 2d cb b2 3b 74 57  12 f8 77 78 71 d5 94 86  c7 64 f8 b8 
	Flags : [ContainsMetaData]  (00000000)


ExportedType #1 (27000001)
-------------------------------------------------------
	Token: 0x27000001
	Name: IWriter
	Implementation token: 0x26000001
	TypeDef token: 0x02000002
	Flags     : [Public] [AutoLayout] [Interface] [Abstract] [AnsiClass]  (000000a1)


ExportedType #2 (27000002)
-------------------------------------------------------
	Token: 0x27000002
	Name: ConsoleWriter
	Implementation token: 0x26000001
	TypeDef token: 0x02000003
	Flags     : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100001)

با استفاده از این داده های مانیفست، مشخص میشه که فایل source1.netmodule به عنوان بخشی از اسمبلی با توکن 0x26000001 درنظر گرفته میشه. این داده ها تو جدول FileDef مانیفست و تو ورودی شماره 1 اون (یا (26000001) File #1) قرار دارن.

همچنین مشاهد میشه که دو نوع با دسترسی عمومی تو ماژول source1.netmodule وجود داره که تو جدول ExportedTypesDef و تو ورودی های 1 و 2 (یا (27000001) ExportedType #1 و (27000002) ExportedType #2) قرار دارن. در ضمن تو این ورودیها مشخص شده که پیاده سازی این دو نوع تو فایلی با توکن 0x26000001 قرار داره که همون فایل source1.netmodule هست.

توکنی که تو قسمت TypeDef این ورودیها آورده شده (0x02000002 و 0x02000003)، درواقع به ایندکس هایی از جدول تعریف نوع (جدول TypeDef) متادیتای خود ماژول source1.netmodule اشاره دارن. که در بالا آورده شده.


نکته: توکن های متادیتا که در بالا چندین بار بهشون اشاره شد، مقادیری 4 بایتی هستن. اولین بایت این مقدار مشخص کننده نوع توکن یا جدول متادیتای مربوطه هست:

TypeRef : 0x01

TypeDef : 0x02

AssemblyRef : 0x23

File : 0x26 (تعریف فایل یا FileDef)

ExportedType : 0x27

و ...

برای آشنایی با همه توکنهای موجود به اینجا یه سری بزنین.

سه بایت باقیمانده موقعیت رکورد موردنظر تو جدول مربوطه رو نشون میده. مثلا توکن 0x26000001 به اولین رکورد جدول FileDef اشاره داره. برای بیشتر جداول موجود این رکورد ها از شماره 1 (و نه از شماره 0) شروع میشن. درضمن تو جدول TypeDef رکوردها از شماره 2 شروع میشن.

اطلاعات کاملتر درباره توکن های متادیتا.


استفاده از اسمبلی چندفایله


برای استفاده از یه اسمبلی چندفایله، مثل اسمبلی های تک فایله، اسمبلی مقصد باید با استفاده از سوییچ r/ ریفرنس به فایل اصلی اسمبلی رو به کامپایلر #C اعلام کنه. مثلا برای مثال جاری باید از سوییچ r:asm.dll/ استفاده کرد. با استفاده از این سوییچ، کامپایلر برای یافتن نوع های موردنیاز جهت اجرای فرایند کامپایل، به جداول مانیفست اسمبلی مراجعه کرده و پس از یافتن ماژولی که نوع موردنظر توش پیاده شده، اقدام به بارگذاری اون میکنه.

بنابراین کامپایلر به همه فایلهای تشکیل دهنده اسمبلی برای اجرای درست عملیات کامپایل نیاز داره و درصورت عدم وجود یکی از فایلهای تشکیل دهنده اسمبلی چندفایله در زمان کامپایل، فرایند کامپایل با موفقیت انجام نمیشه. مثلا برای مثال جاری درصورتیکه فایل source1.netmodule در کنار فایل asm.dll وجود نداشته باشه، کامپایلر #C خطای زیر رو صادر میکنه:

fatal error
CS0009: Metadata file 'd:\asm.dll' could not be opened—'Error importing module 'source1.netmodule' of assembly 'd:\asm.dll'—The system cannot find the file specified.

پس در زمان کامپایل، برای تولید یه اسمبلی جدید، تمامی فایلهای تشکیل دهنده اسمبلی های ریفرنس داده شده باید در دسترس باشن.

هنگام اجرای کدهای اسمبلی مقصد، CLR تمامی نوع های ریفرنس داده شده و موردنیاز برای اجرای یه متد رو شناسایی کرده و سپس با توجه به داده های متادیتا و مانیفست، اقدام به بارگذاری فایلهای مربوطه میکنه. اگه نوع مورد نیاز تو فایل اصلی اسمبلی (حاوی مانیفست) که بارگذاری شده، پیاده شده باشه، بدون نیاز به بارگذاری فایلی اضافی، کار ادامه پیدا میکنه. اما اگه با توجه به داده های مانیفست، فایلی که نوع موردنظر توش پیاده شده، ماژول دیگه ای غیر از فایل اصلی اسمبلی ریفرنس داده شده باشه، CLR اقدام به یافتن و بارگذاری اون فایل میکنه تا نوع موردنظر برای اجرا آماده بشه.

بنابراین در زمان اجرا، برخلاف زمان کامپایل، نیازی نیست تا همه فایلهای تشکیل دهنده اسمبلی چندفایله در دسترس باشن و تا زمانیکه به کدهای پیاده شده تو سایر فایلهای اسمبلی نیازی نباشه، CLR به اونا حتی نیگا هم نمیکنه!


.: . :.

  • یوسف نژاد

Multifile Assembly

NET Framework.

نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی
آخرین نظرات