Skip to content

Instantly share code, notes, and snippets.

@jbevain
Last active July 16, 2023 06:42
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jbevain/933a81871c55e716f208 to your computer and use it in GitHub Desktop.
Allocating strings from unmanaged memory
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>;
}
}
}
@jbevain
Copy link
Author

jbevain commented Nov 11, 2014

This will only work for .net 4.0.

@SLaks
Copy link

SLaks commented Nov 12, 2014

Isn't the GC allowed to move world at any time, completely breaking your code?

@jbevain
Copy link
Author

jbevain commented Jan 5, 2015

@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