Mono.Cecil: अपना खुद का "कंपाइलर" बनाएं

साइकिल के आविष्कार में लिप्त प्रोग्रामर के लिए सबसे शानदार विषयों में से एक अपनी भाषा, दुभाषियों और संकलक लिख रहा है। वास्तव में, अन्य कार्यक्रमों को सहज रूप से बनाने या क्रियान्वित करने में सक्षम एक प्रोग्राम कोडर्स के दिलों में खौफ पैदा करता है - क्योंकि यह जटिल, चमकीला, लेकिन बेहद रोमांचक है।



अधिकांश अपने स्वयं के दुभाषियों के साथ शुरू होते हैं, जो सामान्य रूप से एक लूप में आदेशों का एक बड़ा स्विच होता है। दिलचस्प, आराम से, लेकिन डरपोक और बहुत धीमा। मैं कुशलता से JIT'it के लिए कुछ और फुर्तीला करना चाहता हूं, और अधिमानतः, उसने खुद स्मृति का पालन किया।



इस समस्या का एक उत्कृष्ट समाधान .NET को लक्ष्य प्लेटफ़ॉर्म के रूप में चुनना है। चलो अगली बार लेक्सिकल विश्लेषण छोड़ दें, और आज आइए एक सरलतम कार्यक्रम बनाने की कोशिश करें जो एक कार्यशील निष्पादन योग्य निष्पादन बनाता है:









प्रोग्राम को नाम की आवश्यकता होगी, और कंसोल को आउटपुट नमस्कार,% उपयोगकर्ता नाम%।



उदाहरण के लिए, निष्पादन योग्य बनाने के कई तरीके हैं:



बस आखिरी विकल्प मैंने चुना। दुर्भाग्य से, मुझे नहीं पता कि इस कार्य के लिए सेसिल क्या परावर्तन से बेहतर है, लेकिन मैं सेसिल पर एक उदाहरण भर में आया था, इसलिए मैं इसका सटीक विश्लेषण करूँगा।



Mono.Cecil एक पुस्तकालय है जो आपको विधानसभा के साथ बाइट्स की एक सरणी के रूप में काम करने की अनुमति देता है। इसकी मदद से, आप दोनों अपनी असेंबली बना सकते हैं, और मौजूदा लोगों को चुन सकते हैं और संशोधित कर सकते हैं। यह उन वर्गों की एक विस्तृत श्रृंखला प्रदान करता है जो (आमतौर पर) सुविधाजनक हैं।



बातचीत का विषय



यहां, वास्तव में, तैयार कोड (वास्तविक जनरेटर विधि को छोड़कर, वर्ग, रूप, और बाकी सब के विवरण के बिना):



using Mono.Cecil; using Mono.Cecil.Cil; public void Compile(string str) { //      ,   :   var name = new AssemblyNameDefinition("SuperGreeterBinary", new Version(1, 0, 0, 0)); var asm = AssemblyDefinition.CreateAssembly(name, "greeter.exe", ModuleKind.Console); //     string  void asm.MainModule.Import(typeof(String)); var void_import = asm.MainModule.Import(typeof(void)); //   Main, , ,  void var method = new MethodDefinition("Main", MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig, void_import); //       var ip = method.Body.GetILProcessor(); //  ! ip.Emit(OpCodes.Ldstr, "Hello, "); ip.Emit(OpCodes.Ldstr, str); ip.Emit(OpCodes.Call, asm.MainModule.Import(typeof(String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }))); ip.Emit(OpCodes.Call, asm.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }))); ip.Emit(OpCodes.Call, asm.MainModule.Import(typeof(Console).GetMethod("ReadLine", new Type[] { }))); ip.Emit(OpCodes.Pop); ip.Emit(OpCodes.Ret); //  ,      :    //      var type = new TypeDefinition("supergreeter", "Program", TypeAttributes.AutoClass | TypeAttributes.Public | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, asm.MainModule.Import(typeof(object))); //     asm.MainModule.Types.Add(type); //     type.Methods.Add(method); //       asm.EntryPoint = method; //     asm.Write("greeter.exe"); }
      
      







अब खौफनाक दिखने वाले मध्य भाग के बारे में अधिक ध्यान से, जो वास्तव में, कोड उत्पन्न करता है।



वहां क्या चल रहा है?



C # में लिखा गया, एक ही प्रोग्राम इस तरह दिखेगा (मैं कक्षा विवरण को छोड़ दूँगा):



 static public void Main() { Console.WriteLine("Hello, " + "username"); Console.ReadLine(); }
      
      







ऐसा करने के लिए, हम दो लाइनें लेते हैं, पहला एक स्थिर है, दूसरा संकलन चरण पर निर्धारित किया जाता है और एक स्थिर भी बन जाता है, हम उन्हें स्टैक पर रख देते हैं। String.Concat इन पंक्तियों को जोड़ता है और परिणाम को ढेर के शीर्ष पर छोड़ देता है, जिसे Console.WriteLine द्वारा लिया जाता है और प्रदर्शित किया जाता है।



उसके बाद, ताकि प्रोग्राम को बंद करने से पहले हमारे पास कुछ पढ़ने के लिए समय न हो, हमें Console.ReadLine () की आवश्यकता होती है और चूँकि यह एक रीड लाइन देता है जिसकी हमें आवश्यकता नहीं होती है, हम इसे स्टैक से बाहर फेंक देते हैं, और फिर उपलब्धि की भावना के साथ हम पहले से ही चले जाते हैं। लगभग मेन का एक देशी कार्य।



Bytecode के बारे में



हम .NET वर्चुअल मशीन के लिए एक प्रोग्राम बनाते हैं, और विधि का शरीर, जाहिर है, इसके कमांड के होते हैं। .NET एक स्टैक्ड वर्चुअल मशीन है, इसलिए सभी ऑपरेशन स्टैक पर पड़े ऑपरेंड के साथ किए जाते हैं। उनकी पूरी सूची विकिपीडिया पर मिल सकती है, लेकिन मैं केवल उन लोगों के बारे में बात करूंगा, जिनका मैंने अधिक विस्तार से उपयोग किया है।



LDSTR स्टैक पर एक स्ट्रिंग को धकेलता है । जाहिर है, इसे एक पैरामीटर के रूप में एक स्ट्रिंग की आवश्यकता है। वास्तव में, "स्टैक पर एक स्ट्रिंग लोड करता है" इसका मतलब है कि स्ट्रिंग को स्टैक पर नहीं धकेल दिया गया है, लेकिन केवल एक पॉइंटर मेमोरी में उस स्थान पर है जहां यह स्थित है - लेकिन हमारे लिए, आईएल प्रोग्रामर के लिए, यह महत्वपूर्ण नहीं है। केवल महत्वपूर्ण बात यह है कि निम्नलिखित कमांड वहां से इसे लेने और उपयोग करने में सक्षम होंगे।



कॉल , जैसा कि नाम से पता चलता है, विधि कहती है। ऐसा करने के लिए, उसे स्वयं इस विधि के विवरण के साथ ऑब्जेक्ट का लिंक पास करना होगा, जिसे पहले आयात करना होगा। आयात के लिए, आपको सरणी के रूप में इसके मापदंडों के प्रकारों के नाम और सूची को पास करने के प्रकार में "खोज" करना चाहिए - यही कारण है कि रिकॉर्ड इतना भयानक है। एक अच्छे तरीके से, यहां कुछ प्रकार के हैंडलर लिखना आवश्यक होगा जो "स्ट्रींग.कॉन्कट (स्ट्रिंग, स्ट्रिंग)" रूप की एक स्ट्रिंग को इस डरावनी स्थिति में परिवर्तित करता है - आप ऐसा करने की कोशिश कर सकते हैं।



पीओपी स्टैक से शीर्ष आइटम को पॉप करता है। कुछ खास नहीं। हमें इसकी आवश्यकता है क्योंकि Console.ReadLine () एक मान लौटाता है, और हमारा फ़ंक्शन शून्य देता है, इसलिए हम इसे वहां नहीं छोड़ सकते हैं और इसे साफ़ करना होगा।



रीट - शब्द वापसी से, वर्तमान फ़ंक्शन से बाहर निकलता है। यह प्रत्येक फ़ंक्शन के अंत में होना चाहिए, और शायद एक नहीं - इस पर निर्भर करता है कि आपके पास कितने निकास बिंदु हैं।



काम के परिणाम





अंत में, प्रोग्राम को संकलित करना और चलाना, वहां अपना नाम दर्ज करना और वज़नदार संकलन बटन दबाकर, हम उसी फ़ोल्डर में एक लघु अभिवादन करते हैं। बाइनरी जिसका वजन ठीक 2048 बाइट्स है।



हम इसे लॉन्च करते हैं, और वॉयला!




All Articles