C # में स्ट्रक्चर्स के साथ काम करना

"सी # में मेमोरी में बड़ी मात्रा में डेटा संसाधित करना" हाल के विषय के बाद , मैं वहां उल्लिखित संरचनाओं के बारे में लेख का अनुवाद प्रस्तुत करता हूं

सी # और अधिकांश अन्य आधुनिक प्रोग्रामिंग भाषाओं में संरचनाएं मूलभूत डेटा प्रकार हैं। उनके मूल में, संरचनाएं सरल हैं, लेकिन आप आश्चर्यचकित हो सकते हैं कि उनके साथ काम करना कितनी जल्दी जटिल हो सकता है। सबसे अधिक, समस्याएँ तब उत्पन्न होती हैं जब आपको अन्य भाषाओं में बनाई गई संरचनाओं के साथ काम करना पड़ता है और डिस्क पर संग्रहीत होता है या पुस्तकालयों या कॉम ऑब्जेक्ट्स से कॉलिंग फ़ंक्शन के परिणामस्वरूप प्राप्त होता है। इस लेख में, मेरा मतलब है कि आप संरचना की अवधारणा से परिचित हैं, जानते हैं कि उन्हें कैसे परिभाषित किया जाए और संरचनाओं के साथ काम करने में बुनियादी कौशल हो। यह माना जाता है कि आपको पी / इनवोक का उपयोग करके एपीआई कार्यों को कॉल करने का एक विचार है, साथ ही साथ मार्शलिंग क्या है। यदि आप अपने ज्ञान के बारे में अनिश्चित हैं, तो आप प्रलेखन का उल्लेख कर सकते हैं।
इस आलेख में वर्णित कई तकनीकों को बढ़ाया जा सकता है और किसी भी प्रकार के डेटा पर लागू किया जा सकता है।

स्थान


ज्यादातर मामलों में, आप यह बताए बिना किसी संरचना का वर्णन और उपयोग कर सकते हैं कि इसे कैसे लागू किया जाता है - विशेष रूप से इसके क्षेत्र स्मृति में कैसे स्थित हैं। यदि आपको अन्य अनुप्रयोगों द्वारा उपयोग के लिए एक संरचना बनानी चाहिए, या आपको किसी और की संरचना का उपयोग करना चाहिए, तो स्मृति समस्याएं महत्वपूर्ण हो जाती हैं। आपको क्या लगता है कि अगली संरचना का आकार क्या है?
public struct struct1 { public byte a; // 1 byte public int b; // 4 bytes public short c; // 2 bytes public byte d; // 1 byte } 

उचित उत्तर 8 बाइट्स है, सभी क्षेत्रों के आकार का योग। हालाँकि, यदि आप संरचना के आकार का पता लगाने की कोशिश करते हैं:
 int size = Marshal.SizeOf(test); 

... तो (ज्यादातर मामलों में) यह पाते हैं कि संरचना 12 बाइट्स पर कब्जा करती है। इसका कारण इस तथ्य में निहित है कि अधिकांश प्रोसेसर डेटा के साथ बेहतर काम करते हैं जो बाइट्स से अधिक लेता है और कुछ पते सीमाओं के साथ गठबंधन किया जाता है। पेंटियम 16 ​​बाइट्स के ब्लॉक में डेटा को पसंद करता है, जो डेटा के आकार के समान एक आकार के साथ पता सीमाओं के साथ संरेखित होता है। उदाहरण के लिए, 4-बाइट पूर्णांक को 4-बाइट सीमा से संरेखित किया जाना चाहिए। इस मामले में विस्तृत विवरण महत्वपूर्ण नहीं हैं। महत्वपूर्ण बात यह है कि कंपाइलर संरचना के भीतर डेटा संरेखित करने के लिए लापता बाइट्स जोड़ देगा। हालाँकि, आप इसे मैन्युअल रूप से नियंत्रित कर सकते हैं, ध्यान दें कि कुछ प्रोसेसर अनलग्ड डेटा का उपयोग करते समय कोई त्रुटि दे सकते हैं। यह .NET कॉम्पैक्ट फ्रेमवर्क के उपयोगकर्ताओं के लिए अतिरिक्त समस्याएं पैदा करता है ( दिलचस्प रूप से, इनमें से कई? - लगभग। प्रति। )।

काम करने के लिए, आपको InteropServices के लिए एक लिंक की आवश्यकता होगी:
 using System.Runtime.InteropServices; 

मेमोरी में फ़ील्ड्स को मैन्युअल रूप से व्यवस्थित करने के लिए, स्ट्रक्चरलेयूट विशेषता का उपयोग किया जाता है। उदाहरण के लिए:
 [StructLayout(LayoutKind.Sequential)] public struct struct1 { public byte a; // 1 byte public int b; // 4 bytes public short c; // 2 bytes public byte d; // 1 byte } 

यह संकलक को घोषणा के क्रम में, क्रमिक रूप से खेतों को व्यवस्थित करने के लिए मजबूर करता है, जो कि डिफ़ॉल्ट है। अन्य विशेषता मान ऑटो हैं, जो कंपाइलर को उस क्रम को निर्धारित करने की अनुमति देता है जिसमें फ़ील्ड रखे गए हैं, और स्पष्ट है, जो प्रोग्रामर को प्रत्येक फ़ील्ड के आकार को निर्दिष्ट करने की अनुमति देता है। स्पष्ट प्रकार का उपयोग अक्सर पैकेजिंग के बिना अनुक्रमिक लेआउट के लिए किया जाता है, लेकिन ज्यादातर मामलों में पैक पैरामीटर का उपयोग करना आसान होता है। यह संकलक को बताता है कि कितनी मेमोरी आवंटित की जानी चाहिए और डेटा को कैसे संरेखित किया जाना चाहिए। उदाहरण के लिए, यदि आप पैक = 1 को निर्दिष्ट करते हैं, तो संरचना इस तरह से व्यवस्थित की जाएगी कि प्रत्येक फ़ील्ड एक बाइट की सीमाओं के भीतर होगी और बाईट-बाय-बाय पढ़ी जा सकती है - अर्थात कोई पैकेजिंग की आवश्यकता है। यदि आप संरचना घोषणा को बदलते हैं:
 [StructLayout(LayoutKind.Sequential, Pack=1)] public struct struct1 

... तो आप पाएंगे कि अब संरचना बिल्कुल 8 बाइट्स पर कब्जा कर लेती है, जो अतिरिक्त "पैकिंग" बाइट्स के बिना मेमोरी में खेतों की अनुक्रमिक व्यवस्था से मेल खाती है। यह विंडोज एपीआई और सी / सी ++ में घोषित अधिकांश संरचनाओं के साथ काम करने का तरीका है। ज्यादातर मामलों में, आपको अन्य पैक पैरामीटर मानों का उपयोग करने की आवश्यकता नहीं है। यदि आप पैक = 2 ​​सेट करते हैं, तो आप पाएंगे कि संरचना 10 बाइट्स पर कब्जा कर लेगी, क्योंकि प्रत्येक एक-बाइट फ़ील्ड में एक बाइट जोड़ी जाएगी, ताकि डेटा को 2 बाइट्स के टुकड़ों में पढ़ा जा सके। यदि आप पैक = 4 सेट करते हैं, तो संरचना का आकार 12 बाइट्स तक बढ़ जाएगा ताकि संरचना को 4 बाइट्स के ब्लॉक में पढ़ा जा सके। इसके अलावा, पैरामीटर मान को ध्यान में नहीं रखा जाएगा, क्योंकि पैक आकार को अनदेखा किया जाता है यदि यह इस प्रोसेसर में उपयोग किए गए संरेखण के बराबर या उससे अधिक है, जो इंटेल आर्किटेक्चर के लिए 8 बाइट्स है। अलग-अलग पैक मानों में मेमोरी में संरचना का स्थान चित्र में दिखाया गया है:


यह भी ध्यान देने योग्य है कि यह उस तरह से बदल सकता है जैसे कि इसमें खेतों के क्रम को बदलकर संरचना को पैक किया जाता है। उदाहरण के लिए, खेतों के क्रम को बदलते समय:
 public struct struct1 { public byte a; // 1 byte public byte d; // 1 byte public short c; // 2 bytes public int b; // 4 bytes } 

... संरचना को पैकिंग की आवश्यकता नहीं होगी, यह बिल्कुल 8 बाइट्स ले जाएगा।

सटीक होना


यदि आपको यह निर्दिष्ट करने की आवश्यकता है कि प्रत्येक फ़ील्ड के लिए कितनी मेमोरी आवंटित की जाएगी, तो स्पष्ट स्थान प्रकार का उपयोग करें। उदाहरण के लिए:
 [StructLayout(LayoutKind.Explicit)] public struct struct1 { [FieldOffset(0)] public byte a; // 1 byte [FieldOffset(1)] public int b; // 4 bytes [FieldOffset(5)] public short c; // 2 bytes [FieldOffset(7)] public byte d; // 1 byte } 

तो आपको अतिरिक्त संरेखण बाइट्स के बिना एक 8-बाइट संरचना मिलती है। इस स्थिति में, यह पैक = 1 का उपयोग करने के बराबर है। हालाँकि, Explicit का उपयोग करने से आप मेमोरी को पूरी तरह से नियंत्रित कर सकते हैं। उदाहरण के लिए:
 [StructLayout(LayoutKind.Explicit)] public struct struct1 { [FieldOffset(0)] public byte a; // 1 byte [FieldOffset(1)] public int b; // 4 bytes [FieldOffset(10)] public short c; // 2 bytes [FieldOffset(14)] public byte d; // 1 byte } 

यह संरचना बी फ़ील्ड के बाद अतिरिक्त बाइट्स के साथ 16 बाइट्स पर कब्जा करेगी। C # 2.0 से पहले, स्पष्ट प्रकार मुख्य रूप से तृतीय-पक्ष फ़ंक्शन को कॉल करते समय निश्चित आकार के बफ़र्स को निर्दिष्ट करने के लिए उपयोग किया जाता था। आप संरचना में एक निश्चित लंबाई की सरणी घोषित नहीं कर सकते क्योंकि फ़ील्ड आरंभीकरण निषिद्ध है।
 public struct struct1 { public byte a; public int b; byte[] buffer = new byte[10]; public short c; public byte d; } 

यह कोड एक त्रुटि फेंक देगा। यदि आपको 10 बाइट्स लंबे समय तक रखने की आवश्यकता है, तो यहां एक तरीका है:
 [StructLayout(LayoutKind.Explicit)] public struct struct1 { [FieldOffset(0)] public byte a; [FieldOffset(1)] public int b; [FieldOffset(5)] public short c; [FieldOffset(8)] public byte[] buffer; [FieldOffset(18)] public byte d; } 

इस तरह आप सरणी के लिए 10 बाइट छोड़ देते हैं। कई दिलचस्प बारीकियां हैं। सबसे पहले, 8 बाइट्स के ऑफसेट का उपयोग क्यों करें? कारण यह है कि आप किसी विषम पते के साथ सरणी शुरू नहीं कर सकते। यदि आप 7 बाइट्स की ऑफ़सेट का उपयोग करते हैं, तो आपको एक रनटाइम त्रुटि दिखाई देगी, जो यह इंगित करेगी कि संरचना संरेखण समस्याओं के कारण लोड नहीं की जा सकती है। यह महत्वपूर्ण है क्योंकि स्पष्ट का उपयोग करते समय, आप समस्याओं में भाग सकते हैं यदि आप यह नहीं समझते हैं कि आप क्या कर रहे हैं। दूसरा बिंदु यह है कि संरचना के अंत में अतिरिक्त बाइट्स जोड़े जाते हैं ताकि संरचना का आकार 8 बाइट्स का एक बहु हो। कंपाइलर अभी भी शामिल है कि मेमोरी में संरचना कैसे आवंटित की जाएगी। बेशक, व्यवहार में, कोई भी बाहरी संरचना जिसे आप C # संरचना में बदलने का प्रयास करते हैं, उसे सही ढंग से संरेखित किया जाना चाहिए।
अंत में, यह ध्यान देने योग्य है कि आप सरणी के नाम (उदाहरण के लिए, बफर [1]) का उपयोग करके 10-बाइट सरणी तक नहीं पहुंच सकते हैं, क्योंकि C # सोचता है कि सरणी को कोई मान असाइन नहीं किया गया है। इसलिए, यदि आप किसी सरणी का उपयोग नहीं कर सकते हैं और यह संरेखण समस्या का कारण बनता है, तो इस तरह की संरचना को घोषित करना बेहतर है:
 [StructLayout(LayoutKind.Explicit)] public struct struct1 { [FieldOffset(0)] public byte a; // 1 byte [FieldOffset(1)] public int b; // 4 bytes [FieldOffset(5)] public short c; // 2 bytes [FieldOffset(7)] public byte buffer; [FieldOffset(18)] public byte d; // 1 byte } 

सरणी तक पहुंचने के लिए आपको पॉइंटर्स पर अंकगणित का उपयोग करना होगा, जो कि असुरक्षित कोड है। संरचना को बाइट्स की एक निश्चित संख्या निर्दिष्ट करने के लिए, StructLayout विशेषता में आकार पैरामीटर का उपयोग करें:
 [StructLayout(LayoutKind.Explicit, Size=64)] 

अब C # 2.0 में, निश्चित आकार के सरणियों की अनुमति है, इसलिए उपरोक्त सभी निर्माण आम तौर पर वैकल्पिक हैं। यह ध्यान देने योग्य है कि निश्चित-लंबाई सरणियाँ एक ही तंत्र का उपयोग करती हैं: बाइट्स और पॉइंटर्स की एक निश्चित संख्या आवंटित करना (जो कि असुरक्षित भी है)। यदि आपको पुस्तकालयों से फ़ंक्शन को कॉल करने के लिए सरणियों का उपयोग करने की आवश्यकता है, तो शायद सबसे अच्छा तरीका एरे को स्पष्ट रूप से मार्शल करना होगा, जिसे "सुरक्षित" माना जाता है। आइए इन तीनों तरीकों को देखें।

एपीआई कॉल


संरेखण की आवश्यकता है कि एक संरचना के एक उदाहरण के रूप में, हम EnumDisplayDevices फ़ंक्शन का उपयोग कर सकते हैं, जिसे निम्नानुसार परिभाषित किया गया है:
 BOOL EnumDisplayDevices( LPCTSTR lpDevice, // device name DWORD iDevNum, // display device PDISPLAY_DEVICE lpDisplayDevice, // device information DWORD dwFlags // reserved ); 

C # में कनवर्ट करना बहुत आसान है:
 [DllImport(“User32.dll”, CharSet=CharSet.Unicode )] extern static bool EnumDisplayDevices( string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags); 

DISPLAY_DEVICE संरचना निम्नानुसार परिभाषित की गई है:
 typedef struct _DISPLAY_DEVICE { DWORD cb; WCHAR DeviceName[32]; WCHAR DeviceString[128]; DWORD StateFlags; WCHAR DeviceID[128]; WCHAR DeviceKey[128]; } DISPLAY_DEVICE, *PDISPLAY_DEVICE; 

यह स्पष्ट है कि इसमें एक निश्चित लंबाई के साथ चार वर्ण सरणियाँ हैं। स्पष्ट संरेखण प्रकार का उपयोग करके, हम C # में संरचना को फिर से लिखते हैं:
 [StructLayout(LayoutKind.Explicit, Pack = 1,Size=714)] public struct DISPLAY_DEVICE { [FieldOffset(0)] public int cb; [FieldOffset(4)] public char DeviceName; [FieldOffset(68)] public char DeviceString; [FieldOffset(324)] public int StateFlags; [FieldOffset(328)] public char DeviceID; [FieldOffset(584)] public char DeviceKey; } 

DeviceKey फ़ील्ड को संग्रहीत करने के लिए आवश्यक स्थान निर्दिष्ट करने के लिए आकार पैरामीटर के उपयोग पर ध्यान दें। यदि आप किसी फ़ंक्शन को कॉल करते समय इस संरचना का उपयोग करते हैं:
 DISPLAY_DEVICE info = new DISPLAY_DEVICE(); info.cb = Marshal.SizeOf(info); bool result = EnumDisplayDevices(null, 0, ref info, 0); 

... तब आप सीधे पहुंच सकते हैं सभी सरणियों के पहले अक्षर हैं। उदाहरण के लिए, DeviceString में डिवाइस जानकारी की स्ट्रिंग का पहला वर्ण होता है। यदि आप सरणी के बाकी वर्णों को प्राप्त करना चाहते हैं, तो आपको डिवाइसस्ट्रीमिंग के लिए एक संकेतक प्राप्त करना होगा और सरणी के माध्यम से जाने के लिए पॉइंटर्स पर अंकगणित का उपयोग करना होगा।
C # 2.0 का उपयोग करते समय, संरचना में सरणियों का उपयोग करना सबसे सरल उपाय है:
 [StructLayout(LayoutKind.Sequential, Pack = 1)] public unsafe struct DISPLAY_DEVICE { public int cb; public fixed char DeviceName[32]; public fixed char DeviceString[128]; public int StateFlags; public fixed char DeviceID[128]; public fixed char DeviceKey[128]; } 

ध्यान दें कि संरचना को असुरक्षित संशोधक के साथ चिह्नित किया जाना चाहिए। अब एपीआई कॉल के बाद हम पॉइंटर्स का उपयोग किए बिना सरणियों से डेटा प्राप्त कर सकते हैं। हालांकि, वे अभी भी अंतर्निहित रूप से उपयोग किए जाते हैं, और किसी भी कोड जो सरणियों तक पहुंचता है, उसे असुरक्षित के रूप में चिह्नित किया जाना चाहिए।
तीसरी और अंतिम विधि कस्टम मार्शलिंग है। कई सी # प्रोग्रामर यह नहीं समझते हैं कि मार्शलिंग का सार केवल यह नहीं है कि कैसे लाइब्रेरी कॉल को डेटा टाइप किया जाता है, यह एक सक्रिय प्रक्रिया भी है जो प्रबंधित डेटा को कॉपी और संशोधित करता है। उदाहरण के लिए, यदि आप किसी टाइप किए गए एरे के संदर्भ को पास करना चाहते हैं, तो आप इसे मान कर पास कर सकते हैं, और सिस्टम इसे एक निश्चित लंबाई के एरे में बदल देगा और आपके हिस्से पर आगे की कार्रवाई के बिना एक प्रबंधित एरे में वापस आ जाएगा।
इस मामले में, हमारे लिए ऐसा करने के लिए सभी मार्शल के गुण हैं जो एरे के प्रकार और आकार को दर्शाते हैं:
 [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] public struct DISPLAY_DEVICE { public int cb; [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)] public char[] DeviceName; [MarshalAs(UnmanagedType.ByValArray, SizeConst=128)] public char[] DeviceString; public int StateFlags; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public char[] DeviceID; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public char[] DeviceKey; } 

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

संरचनाओं का क्रमांकन


अब जब हमने मेमोरी में संरचनाओं के प्लेसमेंट से संबंधित कुछ काफी जटिल मुद्दों को कवर किया है, तो यह सीखने का समय है कि संरचना को बनाने वाले सभी बाइट्स कैसे प्राप्त करें। दूसरे शब्दों में, एक संरचना को कैसे क्रमबद्ध किया जाए? ऐसा करने के कई तरीके हैं, सबसे अधिक बार। मार्शल। ऑलोकहॉडल विधि का उपयोग एक अप्रबंधित सरणी के लिए ढेर पर मेमोरी आवंटित करने के लिए किया जाता है। उसके बाद, सब कुछ मैमोरी फंक्शंस जैसे कि स्ट्रक्चरट्रो या कॉपी से किया जाता है। एक उदाहरण:
 public static byte[] RawSerialize(object anything) { int rawsize = Marshal.SizeOf(anything); IntPtr buffer = Marshal.AllocHGlobal(rawsize); Marshal.StructureToPtr(anything, buffer, false); byte[] rawdata = new byte[rawsize]; Marshal.Copy(buffer, rawdata, 0, rawsize); Marshal.FreeHGlobal(buffer); return rawdata; } 

वास्तव में, इतने सारे कार्यों की कोई आवश्यकता नहीं है, एक मध्यवर्ती बफर का उपयोग किए बिना संरचना बाइट को सीधे बाइट सरणी में स्थानांतरित करना आसान है। इस विधि में मुख्य वस्तु GCHandle है। यह गारबेज कलेक्टर के हैंडल को वापस कर देगा, और आप संरचना का आरंभ पता प्राप्त करने के लिए AddrOfPinnedObject विधि का उपयोग कर सकते हैं। RawSerialize विधि निम्नानुसार लिखी जा सकती है:
 public static byte[] RawSerialize(object anything) { int rawsize = Marshal.SizeOf(anything); byte[] rawdata = new byte[rawsize]; GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned); Marshal.StructureToPtr(anything, handle.AddrOfPinnedObject(), false); handle.Free(); return rawdata; } 

यह विधि सरल और तेज है। आप एक बाइट सरणी से संरचना में डेटा deserialize करने के लिए समान विधियों का उपयोग कर सकते हैं, हालांकि एक धारा से एक संरचना को पढ़ने की समस्या के समाधान पर विचार करना अधिक उपयोगी होगा।

धागे से संरचनाएं पढ़ना


कभी-कभी एक संरचना को पढ़ने की आवश्यकता होती है, संभवतः दूसरी भाषा में, सी # संरचना में लिखी जाती है। उदाहरण के लिए, आपको एक बिटमैप फ़ाइल पढ़ने की आवश्यकता है जो फ़ाइल हेडर, फिर बिटमैप हेडर और फिर बिट डेटा के साथ शुरू होती है। फ़ाइल शीर्ष लेख संरचना इस तरह दिखती है:
 [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct BITMAPFILEHEADER { public Int16 bfType; public Int32 bfSize; public Int16 bfReserved1; public Int16 bfReserved2; public Int32 bfOffBits; }; 

एक फ़ंक्शन जो किसी भी स्ट्रीम को पढ़ेगा और सामान्यीकरण का उपयोग किए बिना एक संरचना को वापस कर सकता है:
 public object ReadStruct(FileStream fs, Type t) { byte[] buffer = new byte[Marshal.SizeOf(t)]; fs.Read(buffer, 0, Marshal.SizeOf(t)); GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); Object temp = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), t); handle.Free(); return temp; } 

डेटा ट्रांसफर के लिए, यहाँ GCHandle का उपयोग किया जाता है। इस कोड में नया एक पैरामीटर का उपयोग है जो संरचना के प्रकार को दर्शाता है। दुर्भाग्य से, आप इस प्रकार का उपयोग रिटर्न वैल्यू के लिए नहीं कर सकते हैं, इसलिए, फ़ंक्शन को कॉल करने के बाद, आपको इसका परिणाम बदलने की आवश्यकता है:
 FileStream fs = new FileStream(@”c:\1.bmp”, FileMode.Open, FileAccess.Read); BITMAPFILEHEADER bmFH = (BITMAPFILEHEADER) ReadStruct(fs, typeof(BITMAPFILEHEADER)); 

यदि हम रूपांतरण से बचना चाहते हैं, तो हमें एक सामान्यीकृत विधि का उपयोग करने की आवश्यकता है:
 public T ReadStruct<T> (FileStream fs) { byte[] buffer = new byte[Marshal.SizeOf(typeof(T))]; fs.Read(buffer, 0, Marshal.SizeOf(typeof(T))); GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); T temp = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); return temp; } 

ध्यान दें कि अब हमें PtrToStructure पद्धति द्वारा दी गई वस्तु को स्वयं विधि में बदलना चाहिए, न कि कॉल के स्थान पर, जो अब इस तरह दिखता है:
 BITMAPFILEHEADER bmFH = ReadStruct<BITMAPFILEHEADER>(fs); 

यह देखना अच्छा है कि सामान्यीकृत विधि का उपयोग कितना बेहतर दिखता है।

मैनुअल मार्शलिंग


मार्सलिंग अधिकांश मामलों में इतनी अच्छी तरह से काम करता है कि आप इसके अस्तित्व को पूरी तरह से भूल सकते हैं। हालाँकि, यदि आप कुछ असामान्य करते हैं, तो आपको आश्चर्य हो सकता है कि क्या होता है जब मार्शलिंग काम करना बंद कर देता है। उदाहरण के लिए, कुछ एपीआई कॉल को एक पॉइंटर से पॉइंटर को एक संरचना में पास करने की आवश्यकता होती है। आप पहले से ही जानते हैं कि एक पॉइंटर को एक संरचना में कैसे पारित किया जाए - यह सिर्फ संदर्भ से गुजर रहा है - और इसलिए यह आपको लग सकता है कि एक पॉइंटर को पॉइंटर पास करना भी सरल है। हालाँकि, सब कुछ आपकी अपेक्षा से अधिक जटिल है। आइए देखते हैं।
AVIFileCreateStream फ़ंक्शन में, अंतिम दो मापदंडों को क्रमशः IntPtr और संरचना के संकेत के रूप में पारित किया जाता है:
 [DllImport(“avifil32.dll”)] extern static int AVIFileCreateStream(IntPtr pfile, ref IntPtr pavi, ref AVISTREAMINFO lParam); 

इस फ़ंक्शन को कॉल करने के लिए, आप लिखेंगे:
 result = AVIFileCreateStream(pFile, ref pStream, ref Sinfo); 

पिछले उदाहरणों के आधार पर, पॉइंटर को स्वयं से गुजरने से संरचना को बदलना आसान लगता है। ऐसा लगता है कि निम्नलिखित घोषणा में क्या गलत हो सकता है:
 [DllImport(“avifil32.dll”)] extern static int AVIFileCreateStream(IntPtr pfile, ref IntPtr pavi, IntPtr lParam); 

हालाँकि, यदि आप पिन किए गए ढांचे के पते को पास करने की कोशिश करते हैं:
 GCHandle handle = GCHandle.Alloc(Sinfo, GCHandleType.Pinned); result = AVIFileCreateStream(pFile, ref pStream, handle.AddrOfPinnedObject()); handle.Free(); 

... तब आपको एक त्रुटि दिखाई देगी।


इस त्रुटि का कारण यह है कि यद्यपि आप एक संकेतक को संरचना की शुरुआत के पते पर भेजते हैं, यह संरचना प्रबंधित मेमोरी में स्थित है, और अप्रबंधित कोड इसे एक्सेस नहीं कर सकता है। हम भूल जाते हैं कि पॉइंटर्स बनाते समय स्टैंडर्ड मार्शलिंग कुछ और काम करता है। बिंदु बनाने से पहले, संदर्भ द्वारा पारित सभी मापदंडों के लिए, पूर्ण प्रतियां अप्रबंधित मेमोरी में बनाई जाती हैं। कॉल समाप्त होने के बाद, अप्रबंधित मेमोरी से डेटा वापस प्रबंधित मेमोरी में कॉपी किया जाता है।
एक समान कार्य लिखना जो मार्शलिंग कार्य को आसान और स्पष्ट रूप से उपयोगी बनाता है:
 private IntPtr MarshalToPointer(object data) { IntPtr buf = Marshal.AllocHGlobal(Marshal.SizeOf(data)); Marshal.StructureToPtr(data, buf, false); return buf; } 

यहां इंटप्रेट केवल उस ढेर पर क्षेत्र में लौटता है जिसमें डेटा की एक प्रति होती है। केवल अप्रिय क्षण यह है कि आपको आवंटित मेमोरी को मुक्त करने के लिए याद रखने की आवश्यकता है:
 IntPtr lpstruct = MarshalToPointer(Sinfo); result = AVIFileCreateStream(pFile, ref pStream, lpstruct); Marshal.FreeHGlobal(lpstruct); 

ऊपर दिया गया कोड बिल्कुल मानक मार्शलिंग की तरह काम करता है। हालांकि, यह मत भूलो कि लेप्रिस्टर को पूर्णांक के रूप में मूल्य से पारित किया जाता है। परिणाम को संरचना में वापस कॉपी करने के लिए, आपको एक और विधि की आवश्यकता होगी:
 private object MarshalToStruct(IntPtr buf, Type t) { return Marshal.PtrToStructure(buf, t); } 

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

एक उदाहरण के रूप में, AVISaveOption फ़ंक्शन को लें यह दो सूचक को पैरामीटर के रूप में इंगित करता है:
 [DllImport(“avifil32.dll”)] extern static int AVISaveOptions( IntPtr hWnd, int uiFlags, int noStreams, IntPtr ppavi, IntPtr ppOptions); 

वास्तव में, ppavi पैरामीटर एक हैंडल (जो बदले में एक पॉइंटर है) के लिए एक पॉइंटर है, और ppOptions एक पॉइंटर से एक संरचना के लिए एक पॉइंटर है। इस पद्धति को कॉल करने के लिए, हमें एक संरचना की आवश्यकता है:
 AVICOMPRESSOPTIONS opts = new AVICOMPRESSOPTIONS(); 

इस संरचना की परिभाषा को AVI मानक के लिए प्रलेखन में पाया जा सकता है। अगले चरण में, हमें संरचना के लिए मार्शल्ड पॉइंटर प्राप्त करने की आवश्यकता है:
 IntPtr lpstruct = MarshalToPointer(opts); 

... और फिर एक पॉइंटर को पॉइंटर:
 IntPtr lppstruct = MarshalToPointer(lpstruct); 

... इसके बाद हैंडल को हैंडल:
 IntPtr lphandle = MarshalToPointer(pStream); 

अब फ़ंक्शन कॉल:
 result = AVISaveOptions(m_hWnd, ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE, 1, lphandle, lppstruct); 

... जहां अन्य पैरामीटर रुचि के नहीं हैं, उनके बारे में जानकारी प्रलेखन में पाई जा सकती है।

फ़ंक्शन को कॉल करने के बाद, जो कुछ भी करना बाकी है, वह डेटा को अप्रबंधित बफर से संरचना में स्थानांतरित करना है:
 opts = (AVICOMPRESSOPTIONS) MarshalToStruct(lpstruct, typeof(AVICOMPRESSOPTIONS)); 

ध्यान दें कि आपको संरचना के लिए एक सूचक का उपयोग करने की आवश्यकता है, न कि एक सूचक को सूचक! खैर, अंत में, स्मृति मुक्त करें:
 Marshal.FreeHGlobal(lpstruct); Marshal.FreeHGlobal(lppstruct); Marshal.FreeHGlobal(lphandle); 

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

Source: https://habr.com/ru/post/In114953/


All Articles