0

I'm trying to make an Array of pointers to be able to track the reference to any unmanage object added to it and change it, but the behaviour is different when I create an Pointer Array of Int and when create a Pointer Array of Persons what is unmanaged.

    public unsafe struct Person
    {
        public char* name;
        public int age;

        public Person(string name, int age)
        {
            this.name = (char*)Marshal.StringToCoTaskMemAuto(name);
            this.age = age;
        }
    }

For add:

public static unsafe void WriteValueAtPointerArrayIndex<T>(ref T** pointerArray, int index, in T value) where T : unmanaged
    {
        T** ptr = pointerArray;
        fixed (T* ptrValue = &value)
        {
            ptr += index;
            *ptr = ptrValue;

            Console.WriteLine("Address where add: {0}, Value: {1}", (long)ptr, **ptr);
        }
    }

For get:

public static unsafe ref T* GetPointerValueAtPointerArrayIndex<T>(ref T** pointerArray, int index) where T : unmanaged
    {
        T** ptr = pointerArray;
        ptr += index;

        ref T* value = ref *ptr;
        Console.WriteLine("Address to return: {0}, Value {1}", (long)ptr, *value);
        return ref value;
    }

Using the methods:

UnsafeList use the methods inside and create the internal Array using:

 T**_Array = (T**)Marshal.AllocHGlobal(Marshal.SizeOf<T>() * _Capacity);

where T: unmanaged (new feature of .NET 4.7.3)

        UnsafeList<int> list = new UnsafeList<int>(10);
        list.Add(10);
        list.Add(14);
        list.Add(20);
        list.Add(25);
        list.Add(30);

        for(int i = 0; i < list.Length; i++)
        {
            var v = list[i];
        }

Using it with Person:

        UnsafeList<Person> persons = new UnsafeList<Person>(10);
        Person miguel = new Person("Miguel", 23);
        Person elena = new Person("Elena", 24);
        Person ana = new Person("Ana", 34);
        Person ulises = new Person("Ulises", 23);


        persons.Add(miguel);
        persons.Add(elena);
        persons.Add(ana);
        persons.Add(ulises);

        for (int i = 0; i < persons.Length; i++)
        {
            var v = persons[i];
        }

Results:

  • For ints:

    Address where add: 2151492103952, Value: 10    
    Address where add: 2151492103960, Value: 14    
    Address where add: 2151492103968, Value: 20    
    Address where add: 2151492103976, Value: 25    
    Address where add: 2151492103984, Value: 30
    

///Here only returns the last added but all the address are correct

    Address to return: 2151492103952, Value 30
    Address to return: 2151492103960, Value 30
    Address to return: 2151492103968, Value 30
    Address to return: 2151492103976, Value 30
    Address to return: 2151492103984, Value 30
  • For Persons:

    Address where add: 2151492060192, Value: Miguel, 23
    Address where add: 2151492060200, Value: Elena, 24
    Address where add: 2151492060208, Value: Ana, 34
    Address where add: 2151492060216, Value: Ulises, 23
    
    Address to return: 2151492060192, Value Miguel, 23
    Address to return: 2151492060200, Value Elena, 24
    Address to return: 2151492060208, Value Ana, 34
    Address to return: 2151492060216, Value Ulises, 23
    

Why the results are not consistent when use Int32? I'm new in c# pointers.

Freddx L.
  • 455
  • 2
  • 7
  • 16

1 Answers1

0

Mmmmh found the problem. Your code doesn't have any sense.

fixed (T* ptrValue = &value)

and then

*ptr = ptrValue;

You are saving the address of the passed data. This is wrong if the passed data lives on the stack. The values on the stack are transient. You don't have much control on their lifetime. Now... The example with Person... You create four Persons on the stack. The C# compiler probably makes their lifetime until the end of the method. So you add their address to your UnsafeList<> and then when you take a look at the UnsafeList<> they are still there. But if for example you try to return from the method, probably the piece of stack with the Persons will be cleared/overwritten or at least won't be anymore owned by you. The first example (the one not working) is even worse: by using constants in the .Add(), the constant is pushed on the stack before the call, an address to that piece of stack is taken, and then the constant is popped from the stack at the end of the method. Then a new constant is pushed at the same address, the method is called again, and the constant is popped. So in the end you are saving multiple copies of the same address, that will point to the last value used of the constant. To make a test, try printing ptrValue!

To give a clear and simple example:

public static unsafe void Test<T>(in T value) where T : unmanaged
{
    fixed (T* ptr = &value)
    {
        Console.WriteLine($"Ptr: {(IntPtr)ptr}, Value: {value}");
    }
}

and then:

Test(1);
Test(2);
Test(3);
Test(4);
Test(5);

Console.WriteLine();

int a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5;

Test(a1);
Test(a2);
Test(a3);
Test(a4);
Test(a5);

Result:

Ptr: 97774252, Value: 1
Ptr: 97774252, Value: 2
Ptr: 97774252, Value: 3
Ptr: 97774252, Value: 4
Ptr: 97774252, Value: 5

Ptr: 97774340, Value: 1
Ptr: 97774336, Value: 2
Ptr: 97774332, Value: 3
Ptr: 97774328, Value: 4
Ptr: 97774324, Value: 5

How to correct your code? It isn't even clear what you are trying to do, but surely you are doing it wrongly! :-)

Some untested code!! I haven't even read the documentation about ref and in! :-)

public class RefList<T> 
{
    private T[] _array = new T[16];
    public int Capacity { get => _array.Length; }
    public int Length { get; private set; }

    // TODO Range checks ix >= 0 && ix < Length 
    public ref T this[int ix] { get => ref _array[ix]; }
    public ref T AddEmpty()
    {
        Length++;

        if (Length == _array.Length)
        {
            // This will invalidate all the ref that
            // have been returned until now!
            Array.Resize(ref _array, _array.Length * 2);
        }

        return ref _array[Length - 1];
    }

    public ref T Add(in T value)
    {
        ref T el = ref AddEmpty();

        // Note that value is *copied* to el!
        el = value;

        return ref el;
    }
}

then

public struct MyStruct
{
    public int A;
}

then

var rl = new RefList<MyStruct>();
ref var ms = ref rl.AddEmpty();
ms.A = 5;
Console.WriteLine(rl[0].A);
ms = new MyStruct { A = 10 };
Console.WriteLine(rl[0].A); 
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Thanks, first time using pointers, I trying to figurate how improve my code. What you say is correct, the way I found to solve this is using `GCHandle.Alloc(obj, GCHandleType.Pinned)` but that create problems if the passed object already is pinned, I dont know if is possible to know if an object is pinned in memory. How structs are not moved by GC I suppose that are pinned when you assing then to a variable. – Freddx L. Jun 04 '18 at 18:36
  • What I'm trying to do is a Custom List that store references to the objects added to it. Each time a new unmanaged object is added a pointer to that object is created, for that way I can retrieve/modify any added object because I have a direct reference to it. This is for avoid the fact that structs are copied each time are passed. – Freddx L. Jun 04 '18 at 18:39
  • @FreddxL. You can't pin valuetypes. You can only pin reference types. And what you are trying to do doesn't have much sense, because value types often live on the stack, so their lifetime is short. Alternatively they can live in an array, or they can live as a field of a reference type (in this case the reference type could be moved). – xanatos Jun 04 '18 at 18:42
  • So, to store a ValueType in my Pointer Array, it must be stored allocating on the heap using `Marshall.AllocHGlobal(...)` ? what I want is store a pointer that points to any object added to the List array, and then be able to access to those objects using their address, so I need that GC dont move those objects. – Freddx L. Jun 04 '18 at 19:22
  • @FreddxL. You could even use an array... You can ref access elements of an array. Still it isn't clear what you are trying to do. You want a collection that has a ref indexer? Then build a collection with a ref indexer. I'm adding a fragment of code as an example. Unclear if it works. – xanatos Jun 04 '18 at 19:54
  • Yes, practically thats what I want, access the element by ref, but I dont find a way for also add an element by ref because in the moment you add an struct to an Array you are adding a copy, so the element you add and return aren't the same, so changes in the added element dont affect the original.Practically I'm trying to use a ValueType as an Reference Type. – Freddx L. Jun 04 '18 at 20:19
  • @FreddxL. Yes, you can't do that. But see that there is an AddEmpty() method? You add an empty element and then use it directly. – xanatos Jun 04 '18 at 20:23