تو مطلب ساخت اسمبلی مدیریت شده، درباره نحوه کارکردن با کامپایلر #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 تولید میشه:
اجرای این دستور تو تصویر زیر نشون داده شده:
با اجرای این دستور، کامپایلر #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 خواهد داشت. برای اینکار از دستور زیر استفاده میشه:
نحوه اجرای این دستور تو تصویر زیر نشون داده شده:
این دستور به کامپایلر #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 اشاره دارن. که در بالا آورده شده.
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 خطای زیر رو صادر میکنه:
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 به اونا حتی نیگا هم نمیکنه!
.: . :.