views:

128

answers:

2

I tried searching for this but haven't found anything, however when passing an int[] into a native DLL function as a pointer, isn't there still the danger that the DLL could maintain a reference to the pointer, and then try to access it again after the "fixed" block has terminated? Wouldn't this cause memory access errors if the GC has moved your array? If so, how do you get around this? Or is this an unlikely scenario?

+10  A: 

tried searching for this but haven't found anything

Consider reading the language specification when you have questions about the language.

when passing an int[] into a native DLL function as a pointer, isn't there still the danger that the DLL could maintain a reference to the pointer, and then try to access it again after the "fixed" block has terminated?

Yep. As the language specification states:


It is the programmer’s responsibility to ensure that pointers created by fixed statements do not survive beyond execution of those statements. For example, when pointers created by fixed statements are passed to external APIs, it is the programmer’s responsibility to ensure that the APIs retain no memory of these pointers.


Wouldn't this cause memory access errors if the GC has moved your array?

Yes!

If so, how do you get around this?

You don't. As the language specification states, you are required to never do that. Since you will never do that, you don't have to find a way to get around it. It's rather like asking "how do I recover from fatally shooting myself?" You don't -- if you don't want to end up dead, then a good rule is "don't shoot at yourself in the first place".

Or is this an unlikely scenario?

Are you likely to write C# programs that call DLLs which violate the rules of managed code? I don't know! You tell me -- is it the kind of thing you think you might be tempted to do?

Eric Lippert
Well, if I have a DLL that does retain memory of the pointer, then what's the best way to handle it? Should all pointers be allocated via Marshal.AllocHGlobal?
Dax
Seems reasonable, but unfortunate, since it will induce fragmentation in the GC heap. Why not instead do it the old fashioned way? Allocate a block of unmanaged memory, copy your array into it, and then hand the unmanaged block to the DLL code?
Eric Lippert
I'm confused about your suggestion to "Allocate a block of unmanaged memory". Isn't that the same thing as calling Marshal.AllocHGlobal? Or are you saying that Marshal.AllocHGlobal allocates to the GC heap, even though it's unmanaged memory? If so, to get around that do I have to do create my own heap with HeapCreate, and then allocate memory on that heap via HeapAlloc?Or was it just a miscommunication -- by "all pointers" I really only meant the ones that I was passing to the DLL (which is "all pointers" in my world).
Dax
Sorry, you are right, I am wrong. I was not thinking straight there. Yes, allochglobal allocates unmanaged memory. I was confused; I was thinking that it locks a managed block indefinitely and gives you an unmanaged pointer to it, but that's a different function entirely.
Eric Lippert
+2  A: 

Here's a class I made to fix the problem. It creates a memory space in unmanaged using AllocHGlobal, and then wraps a pointer to that space. Unfortunately making this generic doesn't seem to work, as there's no way that I can find to cast from void* to T in the this[int i] method.


    unsafe sealed class FixedFloatArray : IDisposable {
        readonly float* _floats;

        internal FixedFloatArray(int size) {
            _floats = (float*)Marshal.AllocHGlobal(sizeof(float) * size);
        }

        internal FixedFloatArray(float[] floats) {
            _floats = (float*)Marshal.AllocHGlobal(sizeof(float) * floats.Length);
            Marshal.Copy(floats, 0, (IntPtr)_floats, floats.Length);
        }

        internal float this[int i] {
            get { return _floats[i]; }
            set { _floats[i] = value; }
        }

        internal void CopyFrom(float[] source, int sourceIndex, int destinationIndex, int length) {
            var memoryOffset = (int)_floats + destinationIndex * sizeof(float);
            Marshal.Copy(source, sourceIndex, (IntPtr)memoryOffset, length);
        }

        internal void CopyTo(int sourceIndex, float[] destination, int destinationIndex, int length) {
            var memoryOffset = (int)_floats + sourceIndex * sizeof(float);
            Marshal.Copy((IntPtr)memoryOffset, destination, destinationIndex, length);
        }

        public static implicit operator IntPtr(FixedFloatArray ffa) {
            return (IntPtr)ffa._floats;
        }

        public void Dispose() {
            Marshal.FreeHGlobal((IntPtr)_floats);
        }
    }
Dax