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?
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?
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With