Last active
July 16, 2023 06:42
-
-
Save jbevain/933a81871c55e716f208 to your computer and use it in GitHub Desktop.
Allocating strings from unmanaged memory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Runtime.InteropServices; | |
| using System.Reflection; | |
| using System.Reflection.Emit; | |
| using System.Text; | |
| namespace StringPool | |
| { | |
| [StructLayout(LayoutKind.Explicit)] | |
| unsafe struct Str32 | |
| { | |
| [FieldOffset(0)] | |
| public IntPtr SyncBlock; | |
| [FieldOffset(4)] | |
| public IntPtr VTable; | |
| [FieldOffset(8)] | |
| public int Length; | |
| public const int HeaderSize = 12; | |
| } | |
| [StructLayout(LayoutKind.Explicit)] | |
| unsafe struct Str64 | |
| { | |
| [FieldOffset(0)] | |
| public IntPtr SyncBlock; | |
| [FieldOffset(8)] | |
| public IntPtr VTable; | |
| [FieldOffset(16)] | |
| public int Length; | |
| public const int HeaderSize = 20; | |
| } | |
| struct Header | |
| { | |
| public IntPtr SyncBlock; | |
| public IntPtr VTable; | |
| } | |
| class Program | |
| { | |
| static unsafe Header GetStringHeader(string str) | |
| { | |
| var handle = GCHandle.Alloc(str, GCHandleType.Pinned); | |
| Header header; | |
| if (IntPtr.Size == 8) | |
| { | |
| var s = (Str64*)((byte*)handle.AddrOfPinnedObject() - Str64.HeaderSize); | |
| header.SyncBlock = s->SyncBlock; | |
| header.VTable = s->VTable; | |
| } | |
| else | |
| { | |
| var s = (Str32*)((byte*)handle.AddrOfPinnedObject() - Str32.HeaderSize); | |
| header.SyncBlock = s->SyncBlock; | |
| header.VTable = s->VTable; | |
| } | |
| handle.Free(); | |
| return header; | |
| } | |
| static string GetUnmanagedString(IntPtr ptr) | |
| { | |
| return new ObjectHandle<string>(ptr).Value; | |
| } | |
| static unsafe void Main(string[] args) | |
| { | |
| Console.WriteLine(IntPtr.Size); | |
| var header = GetStringHeader(""); | |
| var chars = new[] { 'W', 'O', 'R', 'L', 'D', '\0' }; | |
| var headerSize = IntPtr.Size == 8 ? Str64.HeaderSize : Str32.HeaderSize; | |
| var memory = Marshal.AllocHGlobal(headerSize + (chars.Length * 2)); | |
| if (IntPtr.Size == 8) | |
| { | |
| var ns = (Str64*)memory; | |
| ns->SyncBlock = header.SyncBlock; | |
| ns->VTable = header.VTable; | |
| ns->Length = chars.Length; | |
| } | |
| else | |
| { | |
| var ns = (Str32*)memory; | |
| ns->SyncBlock = header.SyncBlock; | |
| ns->VTable = header.VTable; | |
| ns->Length = chars.Length; | |
| } | |
| var p = (((byte*)memory) + headerSize); | |
| for (int i = 0; i < chars.Length; i++ ) | |
| *(((char*)p) + i) = chars[i]; | |
| var world = GetUnmanagedString(memory); | |
| Console.WriteLine(world); | |
| Console.WriteLine(world.ToLower()); | |
| Console.WriteLine(world.GetHashCode()); | |
| Console.WriteLine("".GetHashCode()); | |
| lock (world) {} | |
| Marshal.FreeHGlobal(memory); | |
| } | |
| } | |
| public struct ObjectHandle<T> where T : class | |
| { | |
| private readonly IntPtr _handle; | |
| private readonly T _value; | |
| public ObjectHandle(IntPtr handle) | |
| { | |
| _handle = handle; | |
| _value = GetValue(handle + IntPtr.Size); | |
| } | |
| public T Value | |
| { | |
| get | |
| { | |
| return _value; | |
| } | |
| } | |
| private static readonly Func<IntPtr, T> GetValue; | |
| static ObjectHandle() | |
| { | |
| var m = new DynamicMethod("GetObject", typeof(T), new[] { typeof(IntPtr) }, typeof(ObjectHandle<T>), true); | |
| var il = m.GetILGenerator(); | |
| il.Emit(OpCodes.Ldarg_0); | |
| il.Emit(OpCodes.Ret); | |
| GetValue = m.CreateDelegate(typeof(Func<IntPtr, T>)) as Func<IntPtr, T>; | |
| } | |
| } | |
| } |
Author
Isn't the GC allowed to move world at any time, completely breaking your code?
Author
@SLaks : world is at an address not handled by the GC, so it won't move the object to another address during GC. But the GC may very well be confused by the fact that a managed variable contains an unmanaged address. The behavior is undefined.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This will only work for .net 4.0.