۱۳۸۸/۰۶/۲۵

كامپايل پوياي كد در دات نت


در دات نت فريم ورك امكان كامپايل پوياي يك قطعه كد دريافت شده از يك رشته، توسط فضاي نام CodeDom مهيا است كه قدرت قابل توجهي را در اختيار برنامه نويس قرار مي‌دهد.

مثال يك:
رشته زير را كامپايل كرده و تبديل به يك فايل exe كنيد:

string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
روش انجام كار به همراه توضيحات مربوطه به صورت كامنت:

using System;
using System.Collections.Generic;
//دو فضاي نامي كه براي اين منظور اضافه شده‌اند
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace compilerTest
{
class Program
{
static void compileIt1()
{
//سورس كد ما جهت كامپايل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعيين نگارش كامپايلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعيين اينكه كد ما سي شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//تعيين اينكه خروجي يك فايل اجرايي است بعلاوه مشخص سازي محل ذخيره سازي فايل نهايي
CompilerParameters compilerParams = new CompilerParameters
{
OutputAssembly = "D:\\Foo.EXE",
GenerateExecutable = true
};

//عمليات كامپايل در اينجا صورت مي‌گيرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

//اگر خطايي وجود داشته باشد نمايش داده خواهد شد
Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
foreach (CompilerError err in results.Errors)
{
Console.WriteLine("ERROR {0}", err.ErrorText);
}
}

static void Main(string[] args)
{
compileIt1();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
مثال 2:
كد مورد نظر را به صورت يك فايل dll كامپايل كنيد.
براي اين منظور تمامي مراحل مانند قبل است فقط GenerateExecutable ذكر شده به false تنظيم شده و نام خروجي نيز به foo.dll بايد تنظيم شود.


مثال 3:
كد مورد نظر را در حافظه كامپايل كرده (خروجي dll يا exe نمي‌خواهيم)، سپس متد SayHello آن را به صورت پويا فراخواني نموده و خروجي را نمايش دهيد.
در اين حالت روش كار همانند مثال 1 است با اين تفاوت كه GenerateInMemory = true و GenerateExecutable = false تنظيم مي‌شوند. همچنين جهت دسترسي به متد كلاس ذكر شده،‌ از قابليت‌هاي ريفلكشن موجود در دات نت فريم ورك استفاده خواهد شد.

using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace compilerTest
{
class Program
{
static void compileIt2()
{
//سورس كد ما جهت كامپايل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعيين نگارش كامپايلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعيين اينكه كد ما سي شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//نحوه تعيين مشخص سازي كامپايل در حافظه
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false
};

//عمليات كامپايل در اينجا صورت مي‌گيرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

// اگر خطايي در كامپايل وجود نداشت متد دلخواه را فراخواني مي‌كنيم
if (results.Errors.Count == 0)
{
//استفاده از ريفلكشن براي دسترسي به متد و فراخواني آن
Type type = results.CompiledAssembly.GetType("Foo.Bar");
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(null, null);
}
}


static void Main(string[] args)
{
compileIt2();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
نكته: نحوه‌ي استفاده از اسمبلي‌هاي ديگر در رشته سورس كد خود
مثال:
اگر رشته سورس ما به صورت زير بوده و از اسمبلي System.Drawing.Dll نيز كمك گرفته باشد،‌

string source =
@"
namespace Foo
{

public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
var r = new System.Drawing.Rectangle(0,0,100,100);
System.Console.WriteLine(r);
}
}
}
";
هنگام كامپايل آن توسط روش مثال يك، با خطاي زير مواجه خواهيم شد.

Number of Errors: 1
ERROR The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)

براي رفع اين مشكل و معرفي اين اسمبلي،‌ سطر زير بايد پس از تعريف compilerParams اضافه شود.

compilerParams.ReferencedAssemblies.Add("System.Drawing.Dll");
اكنون كد كامپايل شده و مشكلي نخواهد داشت.
نمونه‌اي ديگر از اين دست، استفاده از LINQ مي‌باشد. در اين حالت اسمبلي System.Core.Dll نيز به روش ذكر شده بايد معرفي گردد تا مشكلي در كامپايل كد رخ ندهد.


كاربردها:
1- استفاده در ابزارهاي توليد كد (براي مثال در برنامه Linqer از اين قابليت استفاده مي‌شود)
2- استفاده‌هاي امنيتي (ايجاد روش‌هاي توليد يك سريال به صورت پويا و كامپايل پوياي كد مربوطه در حافظه‌اي محافظت شده)
3- استفاده جهت مقاصد محاسباتي پيشرفته
4- دادن اجازه‌ي كد نويسي به كاربران برنامه‌ي خود (شبيه به سيستم‌هاي ماكرو و اسكريپت نويسي موجود)
و ...