DCSIMG
UnmanagedMemoryStream and PinnedBufferMemoryStream - שלמה גולדברג (הרב דוטנט)

שלמה גולדברג (הרב דוטנט)

מרצה בסלע ויועץ בעולם ה - net.

UnmanagedMemoryStream and PinnedBufferMemoryStream

 

יש class מעניין בשם UnmanagedMemoryStream, התפקיד שלו בחיים זה לספק גישה לבלוקים בזיכרון קוד שאינו מנוהל. בדוגמא ב - MSDN יש את דוגמת הקוד הבאה: (שם זה מופיע עם הערות)
 

unsafe

{

    byte[] message = UnicodeEncoding.Unicode.GetBytes("Here is some data.");

    IntPtr memIntPtr = Marshal.AllocHGlobal(message.Length);

    byte* memBytePtr = (byte*)memIntPtr.ToPointer();

 

    UnmanagedMemoryStream writeStream = new UnmanagedMemoryStream(memBytePtr, message.Length,

                                                                    message.Length, FileAccess.Write);

    writeStream.Write(message, 0, message.Length);

    writeStream.Close();

 

    UnmanagedMemoryStream readStream = new UnmanagedMemoryStream(memBytePtr, message.Length,

                                                                message.Length, FileAccess.Read);

    byte[] outMessage = new byte[message.Length];

    readStream.Read(outMessage, 0, message.Length);

    readStream.Close();

 

    Console.WriteLine(UnicodeEncoding.Unicode.GetString(outMessage));

    Marshal.FreeHGlobal(memIntPtr);

}

 
בגדול מה שזה עושה, מייצר מערך של בתים שמייצג את המחרוזת.
הגדרת מצביע למקום בזיכרון בגודל המערך.
יצירת מצביע למקום הזה.
יצירת אובייקט מסוג UnmanagedMemoryStream שמקבל את המצביע.
כתיבה לתוך ה - Stream
סגירת ה - Stream.
יצירת Stream חדש לקריאה.
קריאה של ה - Stream והדפסה.
שיחרור המצביע.
 
פשוט וקל.
 
איפה הבעייה ? שימו לב לקוד הבא:
 

static void Main(string[] args)

{

    UnmanagedMemoryStream stream = WriteInStream();

    ReadFromStream(stream);

}

 

static UnmanagedMemoryStream WriteInStream()

{

    unsafe

    {

        byte[] message = UnicodeEncoding.Unicode.GetBytes("Here is some data.");

        IntPtr memIntPtr = Marshal.AllocHGlobal(message.Length);

        byte* memBytePtr = (byte*)memIntPtr.ToPointer();

 

        UnmanagedMemoryStream writeStream = new UnmanagedMemoryStream(memBytePtr, message.Length,

                                                                        message.Length, FileAccess.Write);

        writeStream.Write(message, 0, message.Length);

 

        Marshal.FreeHGlobal(memIntPtr);

        return writeStream;

    }

}

 

static void ReadFromStream(UnmanagedMemoryStream stream)

{

    unsafe

    {

        IntPtr memIntPtr = Marshal.AllocHGlobal((int)stream.Length);

        byte* memBytePtr = (byte*)memIntPtr.ToPointer();

 

        UnmanagedMemoryStream readStream = new UnmanagedMemoryStream(memBytePtr, stream.Length,

                                                                        stream.Length, FileAccess.Read);

        byte[] outMessage = new byte[stream.Length];

        readStream.Read(outMessage, 0, (int)stream.Length);

        readStream.Close();

 

        Console.WriteLine(UnicodeEncoding.Unicode.GetString(outMessage));

        Marshal.FreeHGlobal(memIntPtr);

    }

}

 

 
הקוד הוא אותו דבר, חוץ מן העובדה שחילקתי את העבודה לשני פונקציות שונות והחזקתי מצביע לאותו אובייקט מסוג UnmanagedMemoryStream.
 
מסתבר שכשנריץ את הקוד נקבל כל פעם תוצאות שונות (בדוגמה הספציפית התוצאה תהיה כמעט זהה אבל לא לגמרי) אבל אם ה - stream יהיה מספיק גדול נניח קובץ תמונה) ה - Stream יתאפס ולא נוכל לשחזר את התמונה מהזיכרון.
 
אני מניח שזה קורה בגלל ה - GC אני לא סגור בדיוק על הסיבות אבל זה נראה לי כמו הזזות של הזיכרון.
 
אז איך ניתן לפתור את זה ? מסתבר שיש class שנקרא PinnedBufferMemoryStream שיורש מ - UnmanagedMemoryStream ותפקידו בחיים זה לנעוץ את הזיכרון כך שאף אחד לא יוכל להזיז אותו.
אבל איכשהו החליטו להגדיר אותו כ - internal כך שאי אפשר לייצר ממנו מופע.
דרך אחת לפתור את הבעייה זה להסתכל ב - reflector ולהעתיק את הקוד (לא כזה מסובך) הבעייה עם הדרך הזאת זה כשאנחנו מקבלים אובייקטים מהסוג הזה מה - framework (לדוגמא בעבודה עם resources) ואז לא נוכל להחזיק אותם עם מצביע מהאובייקט שלנו, ולכן כתבתי את הפונקצייה הבאה.
 

private UnmanagedMemoryStream GetPinnedBufferMemoryStream(byte[] buffer)

{

    Module module = typeof(UnmanagedMemoryStream).Module;

    Type pinnedBufferMemoryStreamType = module.GetTypes().

            Single(item => item.Name == "PinnedBufferMemoryStream");

 

    ConstructorInfo ctor = pinnedBufferMemoryStreamType.

                        GetConstructor(

                            BindingFlags.NonPublic | BindingFlags.Instance,

                            null,

                            new Type[] { typeof(byte[]) },

                            null);

 

    UnmanagedMemoryStream pinnedBufferMemoryStream =

        (UnmanagedMemoryStream)ctor.Invoke(new object[] { buffer });

 

    return pinnedBufferMemoryStream;

}

 
זה יודע להחזיר לנו מופע של האובייקט PinnedBufferMemoryStream באמצעות reflection כשהמצביע הוא מסוג UnmanagedMemoryStream (שהוא האבא שלו) וזה פותר לנו את כל הבעיות
פורסם: Jul 12 2009, 02:14 PM by Shlomo | with 3 comment(s) |

תוכן התגובה

יואב מיכאלי כתב/ה:

נחמד,

אבל נראה שהקוד שכתבת בדוגמה לא נכון :-(.

הפוקנציה ReadFromStream מקצה זכרון מחדש ולא משתמש בפוינטר שכבר יש לך לזכרון שאליו כתבת את המידע.

בנוסף שים לב שבסוף הקוד שלך אחרי שאתה מבצע Write אתה קורה ל        Marshal.FreeHGlobal(memIntPtr);

מה שגורם לשחרור המשאבים שתפסת בגלל זה הוא יכול להקצות לך את הזכרון הזה מחדש.

במקרה יצא לך שהוא הקצה את אותו הזכרון אבל לא בהכרח.

# July 12, 2009 5:46 PM

Shlomo כתב/ה:

זה לא נכון, רותו אפשק רואה גם אם אנ ילא משחרר

# July 12, 2009 7:43 PM

יואב מיכאלי כתב/ה:

בכל מקרה כדאי לתקן את הקוד שבדוגמא כי הוא לא נכון!

סליחה על הבוטות.

# July 13, 2009 1:21 AM
שלח תגובה

(שדה חובה)  

(שדה חובה)  

(אופציונלי)

(שדה חובה) 

Please add 6 and 2 and type the answer here:


Enter the numbers above: