Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does "fixed" really guarantee anything when passing pointers (ie int[]) to DLLs?

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?

like image 200
Dax Fohl Avatar asked Dec 17 '22 05:12

Dax Fohl


2 Answers

UPDATE: This question was the subject of my blog on December 11th 2012. Thanks for the great question!


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?

like image 82
Eric Lippert Avatar answered Jan 13 '23 12:01

Eric Lippert


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);
        }
    }
like image 30
Dax Fohl Avatar answered Jan 13 '23 13:01

Dax Fohl