13

UPDATE: There is now an accepted answer that "works". You should never, ever, ever, ever use it. Ever.


First let me preface my question by stating that I'm a game developer. There's a legitimate - if highly unusual - performance-related reason for wanting to do this.


Say I have a C# class like this:

class Foo
{
    public int a, b, c;
    public void MyMethod(int d) { a = d; b = d; c = a + b; }
}

Nothing fancy. Note that it is a reference type that contains only value types.

In managed code I'd like to have something like this:

Foo foo;
foo = Voodoo.NewInUnmanagedMemory<Foo>(); // <- ???
foo.MyMethod(1);

What would the function NewInUnmanagedMemory look like? If it can't be done in C#, could it be done in IL? (Or maybe C++/CLI?)

Basically: Is there a way - no matter how hacky - to turn some totally arbitrary pointer into an object reference. And - short of making the CLR explode - damn the consequences.

(Another way to put my question is: "I want to implement a custom allocator for C#")

This leads to the follow-up question: What does the garbage collector do (implementation-specific, if need be) when faced with a reference that points outside of managed memory?

And, related to that, what would happen if Foo had a reference as a member field? What if it pointed at managed memory? What if it only ever pointed at other objects allocated in unmanaged memory?

Finally, if this is impossible: Why?


Update: Here are the "missing pieces" so far:

#1: How to convert an IntPtr to an object reference? It might be possible though unverifiable IL (see comments). So far I've had no luck with this. The framework seems to be extremely careful to prevent this from happening.

(It would also be nice to be able to get the size and layout information for non-blittable managed types at runtime. Again, the framework tries to make this impossible.)

#2: Assuming problem one can be solved - what does the GC do when it encounters an object reference that points outside of the GC heap? Does it crash? Anton Tykhyy, in his answer, guesses that it will. Given how careful the framework is to prevent #1, it does seem likely. Something that confirms this would be nice.

(Alternatively the object reference could point to pinned memory inside the GC heap. Would that make a difference?)

Based on this, I'm inclined to think that this idea for a hack is impossible - or at least not worth the effort. But I'd be interested to get an answer that goes into the technical details of #1 or #2 or both.

Community
  • 1
  • 1
Andrew Russell
  • 26,924
  • 7
  • 58
  • 104
  • 4
    Could you explain your “performance-related reason”? – svick May 29 '12 at 13:27
  • If you're concerned with performance and wanting to use unmanaged memory, then why write it in C#, why not just C++? – CAbbott May 29 '12 at 13:28
  • @CAbbott That is a possible solution. However moving a lot of code and tooling (and experience, for that matter) from C# to C++ is fairly distasteful. – Andrew Russell May 29 '12 at 13:29
  • @svick The short version is that I need to be able to very, very, *very* quickly "rewind" the state of gameplay objects. I know that there are other possible solutions. The tradeoff I'm exploring here is an epic hack for "perfect" performance and the ability to be (mostly) transparent to the game code that is being "rewound". – Andrew Russell May 29 '12 at 13:53
  • 2
    @AndrewRussell "Rewind" how? Where does unmanaged memory come in? – Anton Tykhyy May 29 '12 at 14:03
  • Put simply - I want the ability to treat the entire game state as both a block of contiguous memory (that I can very quickly copy and perform byte-level calculations on) and as easy-to-use game objects. More keeping a history than rewinding. Obviously there are ways to do this without this optimisation (lots of messing-about with reflection and copying comes to mind). But this way has some advantages that are compelling enough to make it worth investigating. – Andrew Russell May 29 '12 at 14:19
  • 2
    I see. Yes, you'd need a new GC to implement this as you've written. However, there might be other ways to get the same effect. What kind of byte-level operations are you thinking about? Are your game objects immutable? – Anton Tykhyy May 29 '12 at 14:30
  • @AntonTykhyy To be honest I'm rather hoping to avoid messing around with things like making my game objects immutable. That turns the problem from "one really huge hack in one place" into "lots of small hacks everywhere". That said, I do intend to look into usr's idea of using pointers to structs in place of object references. That seems like it could be a workable alternative. – Andrew Russell May 29 '12 at 14:39
  • 1
    @AndrewRussell, using immutable objects if you want to keep history is not a hack, it's the correct solution (or at least one of them). – svick May 29 '12 at 16:31
  • @svick Actually I will contend that immutable objects is a "hack" (of sorts) in this case. The gameplay code, from its own perspective, should be able to mutate the game state as time advances. Having it work on immutable objects requires it to know about and be constrained by implementation details of the history tracking system. Certainly a valid solution. But it is a different set of tradeoffs to the set I am trying to investigate with my question. – Andrew Russell May 30 '12 at 00:27
  • One thing that I've discovered while investigating this, is that .NET makes it *really* hard to discover the size and layout of managed types. Like: "use the SOS debugger" hard. I'm beginning to think this is impossible using pure IL. And I'm not sure I want to go so far as writing unmanaged code yet. Without some way to do this, manually crafting arbitrary object instances in unmanaged memory is not viable. – Andrew Russell May 30 '12 at 03:57
  • This link http://social.msdn.microsoft.com/Forums/en-US/clr/thread/06ac44b0-30d8-44a1-86a4-1716dc431c62/ provides a method for converting an `IntPtr` to an object reference. Although I haven't got the provided code to work in .NET 4.0 yet. Being able to do this would be the cornerstone of this hack. What I have found so far is that the .NET framework deliberately makes this very, very difficult to accomplish (as it probably rightly should). – Andrew Russell May 30 '12 at 04:46
  • @AndrewRussell after 3 years, did you manage to write something? My first experiments with Untiy3d are quite promising and I am investigating for the same reason, avoiding GC during performance critical situations where memory allocations are still needed. – sebas Dec 28 '15 at 16:05
  • @sebas This question isn't really about avoiding allocations or GC. This is all about avoiding serialization costs for memory-only serialization (a fairly narrow problem domain, and ultimately I ended up simply writing a very fast serializer). For avoiding GC, there are much simpler approaches - largely involving pre-allocation and maybe pooling. – Andrew Russell Dec 29 '15 at 01:50
  • Thanks Andrew, you are right about the anti-GC strategies. I am doing this just for fun, even if it works, I don't think I am going to put such a solution in production :) – sebas Dec 29 '15 at 01:53
  • @sebas Yeah, I think this approach is "fun only". Aside from the obvious safety issues, I think it is a dead-end. For the serialization case, as soon as you want to do something like write to disk, send across the network, add versioning, etc, etc - you have nowhere to go. Whereas a serializer that simply does an object walk, you have room to add those things. Definitely worth the performance hit to leave those options open. – Andrew Russell Dec 29 '15 at 02:14

8 Answers8

8

I have been experimenting creating classes in unmanaged memory. It is possible but has a problem I am currently unable to solve - you can't assign objects to reference-type fields -see edit at the bottom-, so you can have only structure fields in your custom class. This is evil:

using System;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

public class Voodoo<T> where T : class
{
    static readonly IntPtr tptr;
    static readonly int tsize;
    static readonly byte[] zero;

    public static T NewInUnmanagedMemory()
    {
        IntPtr handle = Marshal.AllocHGlobal(tsize);
        Marshal.Copy(zero, 0, handle, tsize);
        IntPtr ptr = handle+4;
        Marshal.WriteIntPtr(ptr, tptr);
        return GetO(ptr);
    }

    public static void FreeUnmanagedInstance(T obj)
    {
        IntPtr ptr = GetPtr(obj);
        IntPtr handle = ptr-4;
        Marshal.FreeHGlobal(handle);
    }

    delegate T GetO_d(IntPtr ptr);
    static readonly GetO_d GetO;
    delegate IntPtr GetPtr_d(T obj);
    static readonly GetPtr_d GetPtr;
    static Voodoo()
    {
        Type t = typeof(T);
        tptr = t.TypeHandle.Value;
        tsize = Marshal.ReadInt32(tptr, 4);
        zero = new byte[tsize];

        DynamicMethod m = new DynamicMethod("GetO", typeof(T), new[]{typeof(IntPtr)}, typeof(Voodoo<T>), true);
        var il = m.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        GetO = m.CreateDelegate(typeof(GetO_d)) as GetO_d;

        m = new DynamicMethod("GetPtr", typeof(IntPtr), new[]{typeof(T)}, typeof(Voodoo<T>), true);
        il = m.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        GetPtr = m.CreateDelegate(typeof(GetPtr_d)) as GetPtr_d;
    }
}

If you care about memory leak, you should always call FreeUnmanagedInstance when you are done with your class. If you want more complex solution, you can try this:

using System;
using System.Reflection.Emit;
using System.Runtime.InteropServices;


public class ObjectHandle<T> : IDisposable where T : class
{
    bool freed;
    readonly IntPtr handle;
    readonly T value;
    readonly IntPtr tptr;

    public ObjectHandle() : this(typeof(T))
    {

    }

    public ObjectHandle(Type t)
    {
        tptr = t.TypeHandle.Value;
        int size = Marshal.ReadInt32(tptr, 4);//base instance size
        handle = Marshal.AllocHGlobal(size);
        byte[] zero = new byte[size];
        Marshal.Copy(zero, 0, handle, size);//zero memory
        IntPtr ptr = handle+4;
        Marshal.WriteIntPtr(ptr, tptr);//write type ptr
        value = GetO(ptr);//convert to reference
    }

    public T Value{
        get{
            return value;
        }
    }

    public bool Valid{
        get{
            return Marshal.ReadIntPtr(handle, 4) == tptr;
        }
    }

    public void Dispose()
    {
        if(!freed)
        {
            Marshal.FreeHGlobal(handle);
            freed = true;
            GC.SuppressFinalize(this);
        }
    }

    ~ObjectHandle()
    {
        Dispose();
    }

    delegate T GetO_d(IntPtr ptr);
    static readonly GetO_d GetO;
    static ObjectHandle()
    {
        DynamicMethod m = new DynamicMethod("GetO", typeof(T), new[]{typeof(IntPtr)}, typeof(ObjectHandle<T>), true);
        var il = m.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        GetO = m.CreateDelegate(typeof(GetO_d)) as GetO_d;
    }
}

/*Usage*/
using(var handle = new ObjectHandle<MyClass>())
{
    //do some work
}

I hope it will help you on your path.

Edit: Found a solution to reference-type fields:

class MyClass
{
    private IntPtr a_ptr;
    public object a{
        get{
            return Voodoo<object>.GetO(a_ptr);
        }
        set{
            a_ptr = Voodoo<object>.GetPtr(value);
        }
    }
    public int b;
    public int c;
}

Edit: Even better solution. Just use ObjectContainer<object> instead of object and so on.

public struct ObjectContainer<T> where T : class
{
    private readonly T val;

    public ObjectContainer(T obj)
    {
        val = obj;
    }

    public T Value{
        get{
            return val;
        }
    }

    public static implicit operator T(ObjectContainer<T> @ref)
    {
        return @ref.val;
    }

    public static implicit operator ObjectContainer<T>(T obj)
    {
        return new ObjectContainer<T>(obj);
    }

    public override string ToString()
    {
        return val.ToString();
    }

    public override int GetHashCode()
    {
        return val.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        return val.Equals(obj);
    }
}
IS4
  • 11,945
  • 2
  • 47
  • 86
  • 1
    Evil? This is borderline satanic. Answer accepted - congratulations :) **No one should ever use this code!** – Andrew Russell Dec 13 '12 at 11:37
  • soo....I am coding a memory manager that uses unmanaged memory just for fun and I was struggling to find a way to map a class to preallocated memory. Well, your ObjectHandle class works just fine! I now have to understand what you have done here :D – sebas Dec 27 '15 at 19:53
  • I would like to know how tsize = Marshal.ReadInt32(tptr, 4) works. I guess this is the most unsafe part, since it can change if they decide to change the compiler behaviour. I also wonder why is it possible to do this, but c# doesn't give a sizeof for objects – sebas Dec 27 '15 at 22:21
  • 1
    @sebas `tptr` is a pointer to the internal run-time type information (known as method table), which contains some information about the type, including base instance size, which is located at offset 4. – IS4 Dec 27 '15 at 22:25
  • thanks! still I don't understand why it must be used as hack and it's not available as standard c# option. Maybe this link explains why: http://blogs.msdn.com/b/cbrumme/archive/2003/04/15/51326.aspx – sebas Dec 27 '15 at 22:26
  • 1
    @sebas Probably because it is internal information used only by the CLR, therefore a subject to change, and platform and implementation-specific. – IS4 Dec 27 '15 at 22:29
  • last question if you don't mind, do you know why the type handle is written at handle+4 and not handle? What's in the first 4 bytes? – sebas Dec 28 '15 at 00:28
  • 1
    @sebas The first four bytes are the sync block, which contains some information related to locking etc. The next four bytes are the type pointer, but due to optimalizations, the actual object pointer points to the latter; thus I first add 4 bytes to the handle and then obtain the object reference from that. – IS4 Dec 28 '15 at 09:35
  • thanks and sorry to make this discussion so long. I studied the c# memory layout and it's everything clear now. The only really voodoo part left is how you found out that the type object pointed data holds the size of the object. I couldn't find any information about it, I guess Microsoft wants to keep it really secret. This is the scariest part of the code to be honest. – sebas Dec 28 '15 at 19:07
  • @sebas I have found it somewhere, but can't remember where at the moment. – IS4 Dec 28 '15 at 19:33
  • the exception is only when VS debugger is attached. i have a workaround to set reference fields which is ok for debugger and only %10-%15 slower than setting them normally and will fallback to normal mechanism if not compiled with DEBUG constant with no overhead in release build. but still not enough. its safe if your all objects are created this way but if you contain managed objects be carefull to not trigger GC because it wont update new location in your unmanaged object. i have a workaround for this, you will keep fields as GCHandles and encapsulate them with getter setters. – TakeMeAsAGuest Feb 22 '19 at 15:33
  • Wouldnt it be 8 bytes in x64 build? – Kirikan Apr 07 '21 at 08:02
  • @Kirikan Indeed. It's probably best to use `IntPtr.Size`. – IS4 Apr 07 '21 at 08:18
7

"I want to implement a custom allocator for C#"

GC is at the core of the CLR. Only Microsoft (or the Mono team in case of Mono) can replace it, at a great cost in development effort. GC being at the core of the CLR, messing around with the GC or the managed heap will crash the CLR — quickly if you're very-very lucky.

What does the garbage collector do (implementation-specific, if need be) when faced with a reference that points outside of managed memory?

It crashes in an implementation-specific way ;)

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
  • +1 for actually answering my question. Although I want to just get a more detailed confirmation (particularly due to the ";)" you put in there) - is that "it crashes" down to intuition. Or have you tried it / experienced it / seen something like it / got a reference? – Andrew Russell May 29 '12 at 14:08
  • 1
    It's intuition all right, in that I haven't tried it, but it's a well-founded intuition. Look at the old SSCLI code, how many preconditions and contracts and whatnot are there whenever a method takes a managed pointer. If you just throw in a random pointer in there, it is practically guaranteed to break *something* — and it need not be something obvious, it might manifest only under load, in a multi-processor system etc. etc. – Anton Tykhyy May 29 '12 at 14:26
  • I think I'll put off diving into the SSCLI code for a bit - perhaps as a last resort. Although you did remind me about something interesting: String interning. This is really beyond my knowledge of the CLR - but are interned strings stored in GC memory? If not then perhaps a precedent for references that point outside the GC heap? (A glimmer of hope for my dreadful hack? ;) – Andrew Russell May 29 '12 at 14:49
  • AFAIK interned strings are still allocated on the managed heap in the normal way. SSCLI code appears to confirm this idea. BTW, SSCLI code is not close to actual Microsoft CLR code in many critical places, so it can only be used for pointers and a rough understanding of what can be involved. – Anton Tykhyy May 29 '12 at 15:20
5

Purely C# Approach

So, there are a few options. The easiest is to use new/delete in an unsafe context for structs. The second is to use built-in Marshalling services to deal with unmanaged memory (code for this is visible below). However, both of these deal with structs (though I think the latter method is very close to what you want). My code has a limitation in that you must stick to structures throughout and use IntPtrs for references (using ChunkAllocator.ConvertPointerToStructure to get the data and ChunkAllocator.StoreStructure to store the changed data). This is obviously cumbersome, so you'd better really want the performance if you use my approach. However, if you are dealing with only value-types, this approach is sufficient.

Detour: Classes in the CLR

Classes have a 8 byte "prefix" in their allocated memory. Four bytes are for the sync index for multithreading, and four bytes are for identifying their type (basically, virtual method table and run-time reflection). This makes it hard to deal with unamanaged memory since these are CLR specific and since the sync index can change during run-time. See here for details on run-time object creation and here for an overview of memory layout for a reference type. Also check out CLR via C# for a more in-depth explanation.

A Caveat

As usual, things are rarely so simple as yes/no. The real complexity of reference types has to do with how the garbage collector compacts allocated memory during a garbage collection. If you can somehow ensure that a garbage collection doesn't happen or that it won't affect the data in question (see the fixed keyword) then you can turn an arbitrary pointer into an object reference (just offset the pointer by 8 bytes, then interpret that data as a struct with the same fields and memory layout; perhaps use StructLayoutAttribute to be sure). I would experiment with non-virtual methods to see if they work; they should (especially if you put them on the struct) but virtual methods are no-go due to the virtual method table that you'd have to discard.

One Does Not Simply Walk Into Mordor

Simply put, this means that managed reference types (classes) cannot be allocated in unmanaged memory. You could use managed reference types in C++, but those would be subject to garbage collection... and the process and code is more painful than the struct-based approach. Where does that leave us? Back where we started, of course.

There is a Secret Way

We could brave Shelob's Lair memory allocation ourselves. Unfortunately, this is where our paths must part, because I am not that knowledgeable about it. I will provide you with a link or two - perhaps three or four in actuality. This is rather complicated and begs the question: Are there other optimizations you could try? Cache coherency and superior algorithms is one approach, as is judicious application of P/Invoke for performance-critical code. You could also apply the aforementioned structures-only memory allocation for key methods/classes.

Good luck, and let us know if you find a superior alternative.

Appendix: Source Code

ChunkAllocator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace MemAllocLib
{
    public sealed class ChunkAllocator : IDisposable
    {
        IntPtr m_chunkStart;
        int m_offset;//offset from already allocated memory
        readonly int m_size;

        public ChunkAllocator(int memorySize = 1024)
        {
            if (memorySize < 1)
                throw new ArgumentOutOfRangeException("memorySize must be positive");

            m_size = memorySize;
            m_chunkStart = Marshal.AllocHGlobal(memorySize);
        }
        ~ChunkAllocator()
        {
            Dispose();
        }

        public IntPtr Allocate<T>() where T : struct
        {
            int reqBytes = Marshal.SizeOf(typeof(T));//not highly performant
            return Allocate<T>(reqBytes);
        }

        public IntPtr Allocate<T>(int reqBytes) where T : struct
        {
            if (m_chunkStart == IntPtr.Zero)
                throw new ObjectDisposedException("ChunkAllocator");
            if (m_offset + reqBytes > m_size)
                throw new OutOfMemoryException("Too many bytes allocated: " + reqBytes + " needed, but only " + (m_size - m_offset) + " bytes available");

            T created = default(T);
            Marshal.StructureToPtr(created, m_chunkStart + m_offset, false);
            m_offset += reqBytes;

            return m_chunkStart + (m_offset - reqBytes);
        }

        public void Dispose()
        {
            if (m_chunkStart != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(m_chunkStart);
                m_offset = 0;
                m_chunkStart = IntPtr.Zero;
            }
        }

        public void ReleaseAllMemory()
        {
            m_offset = 0;
        }

        public int AllocatedMemory
        {
            get { return m_offset; }
        }

        public int AvailableMemory
        {
            get { return m_size - m_offset; }
        }

        public int TotalMemory
        {
            get { return m_size; }
        }

        public static T ConvertPointerToStruct<T>(IntPtr ptr) where T : struct
        {
            return (T)Marshal.PtrToStructure(ptr, typeof(T));
        }

        public static void StoreStructure<T>(IntPtr ptr, T data) where T : struct
        {
            Marshal.StructureToPtr(data, ptr, false);
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MemoryAllocation
{
    class Program
    {
        static void Main(string[] args)
        {
            using (MemAllocLib.ChunkAllocator chunk = new MemAllocLib.ChunkAllocator())
            {
                Console.WriteLine(">> Simple data test");
                SimpleDataTest(chunk);

                Console.WriteLine();

                Console.WriteLine(">> Complex data test");
                ComplexDataTest(chunk);
            }

            Console.ReadLine();
        }

        private static void SimpleDataTest(MemAllocLib.ChunkAllocator chunk)
        {
            IntPtr ptr = chunk.Allocate<System.Int32>();

            Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr));
            System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr) == 0, "Data not initialized properly");
            System.Diagnostics.Debug.Assert(chunk.AllocatedMemory == sizeof(Int32), "Data not allocated properly");

            int data = MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr);
            data = 10;
            MemAllocLib.ChunkAllocator.StoreStructure(ptr, data);

            Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr));
            System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr) == 10, "Data not set properly");

            Console.WriteLine("All tests passed");
        }

        private static void ComplexDataTest(MemAllocLib.ChunkAllocator chunk)
        {
            IntPtr ptr = chunk.Allocate<Person>();

            Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr));
            System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Age == 0, "Data age not initialized properly");
            System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Name == null, "Data name not initialized properly");
            System.Diagnostics.Debug.Assert(chunk.AllocatedMemory == System.Runtime.InteropServices.Marshal.SizeOf(typeof(Person)) + sizeof(Int32), "Data not allocated properly");

            Person data = MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr);
            data.Name = "Bob";
            data.Age = 20;
            MemAllocLib.ChunkAllocator.StoreStructure(ptr, data);

            Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr));
            System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Age == 20, "Data age not set properly");
            System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Name == "Bob", "Data name not set properly");

            Console.WriteLine("All tests passed");
        }

        struct Person
        {
            public string Name;
            public int Age;

            public Person(string name, int age)
            {
                Name = name;
                Age = age;
            }

            public override string ToString()
            {
                if (string.IsNullOrWhiteSpace(Name))
                    return "Age is " + Age;
                return Name + " is " + Age + " years old";
            }
        }
    }
}
GGulati
  • 1,027
  • 6
  • 11
  • I think your answer is worthy of an upvote, just for the effort involved. However it still doesn't really answer my question. In particular, I should say that the reason for wanting to use unmanaged memory is in hope that the GC will just ignore my "hacked together" references pointing outside the GC heap. There's no other reason for putting data in unmanaged memory - so marshalling is certainly undesirable. – Andrew Russell May 30 '12 at 04:07
  • In that case, no, unless you can somehow avoid GCs altogether. Or you can simply copy a huge chunk of memory (as a byte array). – GGulati May 30 '12 at 04:21
2

You can write code in C++ and call it from .NET using P/Invoke or you can you can write code in managed C++ that gives you full access to the native API from inside a .NET language. However, on the managed side you can only work with managed types so you will have to encapsulate your unmanaged objects.

To give a simple example: Marshal.AllocHGlobal allows you to allocate memory on the Windows heap. The handle returned is not of much use in .NET but can be required when calling a native Windows API requiring a buffer.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
2

This is not possible.

However you can use a managed struct and create a pointer of this struct type. This pointer can point anywhere (including to unmanaged memory).

The question is, why would you want to have a class in unmanaged memory? You wouldn't get GC features anyway. You can just use a pointer-to-struct.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Structs come with the baggage of pass-by-value semantics, which I'd like to avoid. – Andrew Russell May 29 '12 at 13:30
  • Not structs but pointers to structs. Pointers to structs behave about the same as in C++. Would you disagree? – usr May 29 '12 at 13:31
  • Sorry - I misread your answer. You're quite right. I'll have to have a think about the viability of this alternative. You might be onto something... – Andrew Russell May 29 '12 at 13:33
  • A struct has to be declared `unsafe` to contain a pointer to other structs, see http://msdn.microsoft.com/en-us/library/aa664771. An unsafe struct is basically the same as a C++ struct, but inheritance is not supported. All code that manipulates pointers has to be declared `unsafe`, too. – Anton Tykhyy May 29 '12 at 14:45
  • @AntonTykhyy Indeed. The first question I will attempt to answer when I get a chance to try this idea is "just *how* many times do I have to type `unsafe`?" ;) – Andrew Russell May 29 '12 at 14:52
0

Nothing like that is possible. You can access managed memory in unsafe context, but said memory is still managed and subject to GC.

Why?

Simplicity and security.

But now that I think about it, I think you can mix managed and unmanaged with C++/CLI. But I'm not sure about that, because I never worked with C++/CLI.

Euphoric
  • 12,645
  • 1
  • 30
  • 44
  • Not really the "why" I'm after ;) I'm looking for a technical explanation. – Andrew Russell May 29 '12 at 13:31
  • It is actually one of the most cited reasons : http://blogs.msdn.com/b/ericlippert/archive/2009/06/22/why-doesn-t-c-implement-top-level-methods.aspx – Euphoric May 29 '12 at 13:42
  • What I'm trying to say is: I already know that it's a bad idea and *why* it's a bad idea. I know it's a hack. I'm asking if anyone knows if/how the CLR will explode if I introduce what is essentially a (cleverly crafted, specific) corruption into its memory model. And how one might introduce such a corruption. – Andrew Russell May 29 '12 at 13:45
  • @AndrewRussell: You can't "corrupt" managed memory (well you can from unamanged code but in that world you can do anything). However, there are ways to access unmanaged memory from .NET allowing you to do your own memory handling including whatever "corruption" you want to introduce. In .NET this will be opague objects like `IntPtr` etc. – Martin Liversage May 29 '12 at 13:59
0

I don't know a way to hold a C# class instance in the unmanaged heap, not even in C++/CLI.

Shahar
  • 655
  • 2
  • 7
  • 23
  • You can pin a managed object on the managed heap allowing unmanaged code to access it. Otherwise managed garbage collection could move the object leaving the unmanaged code with "dangling" pointers. There is no such thing as pinning unmanaged objects. – Martin Liversage May 29 '12 at 13:54
  • @Shahar I was about to comment, but I think Martin has covered it better. What you have said in your answer doesn't make sense. – Andrew Russell May 29 '12 at 13:55
  • I removed my answer to prevent people from making mistakes. I'll read more about pinning to be sure I understand the topic... – Shahar May 29 '12 at 15:27
0

It's possible to design a value-type allocator entirely within .net, without using any unmanaged code, which can allocate and free an arbitrary number of value-type instances without any significant GC pressure. The trick is to create a relatively small number of arrays (possibly one for each type) to hold the instances, and then pass around "instance reference" structs which hold the array indices of the index in question.

Suppose, for example, that I want to have a "creature" class which holds XYZ positions (float), XYZ velocity (also float), roll/pitch/yaw (ditto), damage (float), and kind (enumeration). An interface "ICreatureReference" would define getters and setters for all those properties. A typical implementation would be a struct CreatureReference with a single private field int _index, and property accessors like:

  float Position {
    get {return Creatures[_index].Position;} 
    set {Creatures[_index].Position = value;}
  };

The system would keep a list of which array slots are used and vacant (it could, if desired, use one of the fields within Creatures to form a linked list of vacant slots). The CreatureReference.Create method would allocate an item from the vacant-items list; the Dispose method of a CreatureReference instance would add its array slot to the vacant-items list.

This approach ends up requiring an annoying amount of boilerplate code, but it can be reasonably efficient and avoid GC pressure. The biggest problems with are probably that (1) it makes structs behave more like reference types than structs, and (2) it requires absolute discipline with calling IDispose, since non-disposed array slots will never get reclaimed. Another irksome quirk is that one will be unable to use property setters for read-only values of type CreatureReference, even though the property setters would not try to mutate any fields of the CreatureReference instance to which they are applied. Using an interface ICreatureReference may avoid this difficulty, but one must be careful to only declare storage locations of generic types constrained to ICreatureReference, rather than declaring storage locations of ICreatureReference.

supercat
  • 77,689
  • 9
  • 166
  • 211