Allocating strings from unmanaged memory
This file contains 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>; | |
} | |
} | |
} |
Isn't the GC allowed to move world
at any time, completely breaking your code?
@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.