PDA

View Full Version : ایجاد اسمبلی های دینامیک (ایجاد اسمبلی در زمان اجرا)



omid_Ahmadi
دوشنبه 15 خرداد 1385, 12:20 عصر
یکی از قابلیتهای .NET 2 (فکر کنم توی .NET 2 اضافه شده چون توی .NET 1.1 چنین چیزی رو ندیده بودم) امکان ایجاد دینامیک اسمبلی ها بر اساس ورودی های برنامه در زمان اجرا و بعد ذخیره کردن اون توی دیسکه (و یا اجرا کردن اون، بدون ذخیره سازی). یعنی یه چیزی تو مایه های کد جنریشن.
تو این روش می تونیم با استفاده از نیم اسپیس System.Reflection.Emit و System.Reflection و کلاسهایی که توی این نیم اسپیس ها هستن، به صورت دینامیک یه اسمبلی رو توی AppDomain جاری ایجاد کنیم. البته یه مشکلی که این مدل کد جنریشن داره، یعنی ضعفی که نسبت به انجام این کار با استفاده از کلاسهای داخل CodeDOM داره اینه که توی این قسمت کدهایی که باید توی متدها قرار بگیرن (یعنی کدهایی که قراره به صورت دینامیک جنریت بشن) باید به زبان CIL نوشته بشن. :evil: یعنی عملا این روش به جز در موارد معدودی، کار برد زیادی نداره (همون از CodeDOM استفاده بشه بهتره).
اما خوب، این روش هم خیلی جالبه. به همین خاطر یه نمونه از این کار رو توی این قسمت نشون می دم:
فرض کنید می خوایم یه کلاسی مثل کلاس زیر رو به صورت دینامیک توی AppDomain جاری ایجاد کرده و بعد اون رو توی دیسک Save کنیم:



public class HelloWorld
{
private string theMessage;
HelloWorld()
{
}
HelloWorld(string s)
{
theMessage = s;
}
public string GetMsg()
{
return theMessage;
}
public void SayHello()
{
System.Console.WriteLine("Hello from the HelloWorld class!");
}
}


برای این کار اول باید به AppDomain ای که قراره این اسمبلی توی اون ایجاد بشه دسترسی داشته باشیم، یعنی این تنها چیزیه که لازم داریم. پس یه تابع می نویسیم که ورودیش یه ریفرنس به AppDomain جاری باشه، بعد این تابع این اسمبلی رو ایجاد کنه و بعد هم ذخیره کنه. پس خروجی اون هم باید Void باشه.



publicstaticvoid CreateMyAsm(AppDomain curAppDomain)
{


بعد از این باید یه کلاس ایجاد کنیم و مشخصات عمومی اسمبلی رو توی اون وارد کنیم:



// Establish general assembly characteristics.
AssemblyName assemblyName = newAssemblyName();
assemblyName.Name = "MyAssembly";
assemblyName.Version = newVersion("1.0.0.0");


در مرحله ی دوم باید خود اسمبلی و ماژولی که می خواهیم توی اون کلاس رو تعریف کنیم رو ایجاد کنیم، و چون این یه اسمبلی تک فایلیه، پس ایجاد یه ماژول کافیه، اگر چند فایلی بود باید توی هر کدوم از فایلها (اونهایی که پسوند .netmodule دارن) یه ماژول ایجاد می کردیم:



// Create new assembly within the current AppDomain.
AssemblyBuilder assembly = curAppDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save);

// Given that we are building a single-file
// assembly, the name of the module is the same as the assembly.
ModuleBuilder module = assembly.DefineDynamicModule("MyAssembly", "MyAssembly.dll");


خوب حالا می تونیم کلاس رو ایجاد کنیم. برای این کار کافیه از کلاسی که ماژول ایجاد شده رو نشون میداد (کلاس module) بخواهیم که کلاس مورد نظرمون رو ایجاد کنه، یعنی متد DefineType اون رو کال کنیم. اسم کلاس رو بهش می دیم و نوع اون رو هم public مشخص می کنیم. بعد هم با استفاده از کلاس helloWorldClass یه فیلد ایجاد می کنیم:



// Define a public class named "HelloWorld".
TypeBuilder helloWorldClass = module.DefineType("MyAssembly.HelloWorld", TypeAttributes.Public);
// Define a private String member variable named "theMessage".
FieldBuilder msgField = helloWorldClass.DefineField("theMessage", Type.GetType("System.String"), FieldAttributes.Private);


اضافه کردن کانستراکتور پیش فرض خیلی راحته:



// Create the default ctor.
helloWorldClass.DefineDefaultConstructor(MethodAtt ributes.Public);


اما کانستراکتور دومیه یه مقدار مشکل داره، چون باید کدهای موردنظرمون رو به IL بهش بدیم. برای این کار باید یه مقدار IL بلد بود. البته دستور هایی که اینجا استفاده شده زیاد هم سخت نیست:



// Create the custom ctor.
Type[] constructorArgs = newType[1];
constructorArgs[0] = typeof(String);
ConstructorBuilder constructor = helloWorldClass.DefineConstructor(MethodAttributes .Public, CallingConventions.Standard, constructorArgs);
ILGenerator constructorIL = constructor.GetILGenerator();
constructorIL.Emit(OpCodes.Ldarg_0);
Type objectClass = typeof(Object);
ConstructorInfo superConstructor = objectClass.GetConstructor(newType[0]);
constructorIL.Emit(OpCodes.Call, superConstructor);
constructorIL.Emit(OpCodes.Ldarg_0);
constructorIL.Emit(OpCodes.Ldarg_1);
constructorIL.Emit(OpCodes.Stfld, msgField);
constructorIL.Emit(OpCodes.Ret);


توی کد بالا، متد Emit از کلاس ILGenerator می تونه یه دستور IL به برنامه اضافه کنه.
بعد از ایجاد کانستراکتورها، دو متد دیگه رو به کلاس اضافه می کنیم و کدهای اون رو هم (به زبان IL) توی متد قرار می دیم.



// Now create the GetMsg() method.
MethodBuilder getMsgMethod = helloWorldClass.DefineMethod("GetMsg", MethodAttributes.Public, typeof(String), null);
ILGenerator methodIL = getMsgMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld, msgField);
methodIL.Emit(OpCodes.Ret);

// Create the SayHello method.
MethodBuilder sayHiMethod = helloWorldClass.DefineMethod("SayHello", MethodAttributes.Public, null, null);
methodIL = sayHiMethod.GetILGenerator();
methodIL.EmitWriteLine("Hello From the HelloWorldClass");
methodIL.Emit(OpCodes.Ret);


خوب به این ترتیب تمام موارد مورد نیاز کلاس رو به اون اضافه کردیم، حالا کافیه اسمبلی رو توی AppDomain ایجاد کنیم و بعد هم Save کنیم:



// Emitting the class HelloWorld.
helloWorldClass.CreateType();

// (Optionally) save the assembly to file.
assembly.Save("MyAssembly.dll");
}


یکی از جالبترین چیزهایی که توی این مثال به چشم میاد، اینه که به سادگی و با استفاده از چند تا کلاس میشه یه اسمبلی به زبان IL ایجاد کرد. همون ساختار سلسله مراتبی که در کلاسها و اسمبلی های .NET دیده میشه، در کلاسهای این قسمت هم به چشم میخوره. یعنی اون یه کلاس برای اسمبلی ایجاد می کنیم. بعد اون کلاس یه کلاس برای ماژول ما ایجاد می کنه، اون ماژول یه کلاس برای type ما ایجاد می کنه، حالا این کلاس می تومه برای ما متد، پراپرتی، فیلد و ... ایجاد کنه و به همین ترتیب.
اضافه کردن کد هم که راحته، دیدید که با استفاده از متد Emit به راحتی کدهای مورد نظرمون رو می تونیم توی متدها یا ... در کلاس قرار بدیم.
البته می دونید که چون این اسمبلی به صورت دینامیک ایجاد شده برای استفاده از اون باید از late binding استفاده کرد. نحوه ی استفاده از اون رو هم به زودی می گم (البته اون دیگه خیلی راحته، یه تابع چند خطی بیشتر نیست).

MAB_Soft
یک شنبه 21 خرداد 1385, 09:01 صبح
آقا امید دستت درد نکنه
خیلی جالب بود.