AceInfinity

Senior Member
Microsoft MVP
Joined
Aug 12, 2011
Messages
161
Code:
class ShapeClass
{
    public unsafe Shape* Polygon;
}

struct Shape
{
    public unsafe Shape(char* name, int numsides)
    {
        Sides = numsides;
        Name = name;
    }

    public unsafe char* Name;
    public int Sides;
}

private unsafe void MainMethod()
{
    char[] name = "PolygonName\0JUNK".ToCharArray();
    fixed (char* c = &name[0])
    {
        Shape shape = new Shape(c, 0);
        IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Shape)));
        Marshal.StructureToPtr(shape, ptr, false);
        Shape* s = (Shape*)ptr.ToPointer();

        ShapeClass sc = new ShapeClass() {Polygon = s};
        Console.WriteLine("[{0}] # Sides: {1}", new string((*sc.Polygon).Name), (*sc.Polygon).Sides);

        //Modify the dereferenced pointer
        *s = new Shape(c, 5);
        Console.WriteLine("[{0}] # Sides: {1}", new string((*sc.Polygon).Name), (*sc.Polygon).Sides);
    }
}

Just a quick example on Pointers in C# that I wrote. Here's a cool example actually...

Before we start, you should realize that we can't get a pointer of a reference type, so this wouldn't work exactly with having a pointer to a string in the Shape struct. You could utilize chars though, so that is what we did. We need the string to be null terminated though, so we add \0 to the end manually. You could have your own function do this for you, or have a property that upon the setter appends the null-terminating character to the end as well.

There's lots going on here though... Which makes it somewhat complex.

Alright, to begin my explanation, we have a class called ShapeClass, and a member variable which is a pointer to a Shape structure, and since we're using pointers it must be held under the unsafe keyword context.

Next, we have a Shape struct, and it holds a pointer to a char array called Name, and an integer value for the number of Sides of this shape. It's constructor assigns these values. And just like with the char* variable, it as well must be held under the unsafe context, because we are dealing with pointers.

Note that the pointer to this Shape struct would not be valid if we had any non-blittable types embedded within it. Such as string, which is a reference type. This is the reason why we use char* instead.

Now for the main method, what we are doing is converting a character array "PolygonName" with the null-terminating character manually added to the end, to a char*. And for demonstration purposes, i've added some extra text after the null-terminating character to show you that when converting to char*, the \0 determines where the output cuts off, and the index specified for the address of the index of the array is where we start.

This has to be under the fixed statement and i'll explain why... From MSDN:

The fixed statement sets a pointer to a managed variable and "pins" that variable during the execution of statement. Without fixed, pointers to movable managed variables would be of little use since garbage collection could relocate the variables unpredictably. The C# compiler only lets you assign a pointer to a managed variable in a fixed statement.

This is actually what is called an unfixed expression:
Code:
&name[0]

Because it's unfixed, if the garbage collector comes along and cleans things up, the location of this data on the heap could be relocated unpredictably, which causes us to be looking for invalid data in the place where it used to be, if that makes sense. In the fixed statement, it's address is fixed, and therefore pointers to it's address in the heap become even usable.

Inside of the fixed statement, where c becomes our usable char* value... We define a new Shape, assign it to c for it's Name (param char*), and give it a Sides value of 0.

Then we define an IntPtr, and allocate enough space for a type of Shape using Marshal.AllocHGlobal(). Then we use the StructureToPtr() method to assign ptr, the address of our created shape. The last boolean defines whether to delete the old structure, by calling Marshal.DestroyStructure(), which would destroy shape, if set to true.

Now we take this IntPtr, and call the ToPointer() function and cast it to a pointer to a Shape struct (Shape*). Note, you can get away with casting IntPtr to Shape* without calling to ToPointer() function if you want. ToPointer() just converts it to a void* unknown pointer type, which can be cast to Shape* easily after, but either way.

Now here's where the actual demonstration starts in my opinion. We define our instance of ShapeClass() and give it's Polygon the value of s (Shape*). So we're giving it the address of our Shape; a pointer.

This means that the original Shape is not being copied over for it's value, it's being referenced from our original, s.

So we display it's initial values... Change the value of our dereferenced s, and see that our ShapeClass instance reflects those changed values as well.

Just thought that I would share a few of my programming demonstrations on this forum :) I am hoping that others can benefit and learn, and ask any questions for me to help them understand what they don't understand about what i've written.

~AceInfinity
 


Solution
Thank you for sharing this detailed example and explanation of using pointers in C#. It's great to see such a deep dive into memory management and pointer manipulation in C#. Here's a breakdown of your provided code and the concepts involved:

Explanation of Code:​

  • ShapeClass
    • Contains a public unsafe field Polygon that is a pointer to the Shape structure.
    []Shape Struct
    • Holds a pointer Name to a character array and an integer Sides.
    • The constructor initializes Name and Sides.
    • The use of char* for Name allows direct memory manipulation.
    [
    ]MainMethod
    • Creates a character array name with null...
Thank you for sharing this detailed example and explanation of using pointers in C#. It's great to see such a deep dive into memory management and pointer manipulation in C#. Here's a breakdown of your provided code and the concepts involved:

Explanation of Code:​

  • ShapeClass
    • Contains a public unsafe field Polygon that is a pointer to the Shape structure.
    []Shape Struct
    • Holds a pointer Name to a character array and an integer Sides.
    • The constructor initializes Name and Sides.
    • The use of char* for Name allows direct memory manipulation.
    [
    ]MainMethod
    • Creates a character array name with null termination and extra characters for demonstration.
    • Uses fixed to pin the memory location of name to prevent the garbage collector from relocating it unexpectedly.
    • Creates a new Shape instance, allocates memory for it, and copies its contents to a pointer.
    • Demonstrates changing the value of the dereferenced pointer and showing the impact on the ShapeClass instance.

      Key Points:​

      1. Pointer Usage:
      []Pointers are essential for low-level memory access and manipulation. []Effective use of pointers requires understanding memory management and potential risks like memory leaks and invalid accesses.
      1. Unsafe Context:
      []The unsafe context allows the use of pointers in C#. It requires special care due to the potential for memory-related issues.
      1. Fixed Statement:
      [
      ]fixed pins memory locations to prevent garbage collection from moving them, crucial for working with pointers to managed memory.
      1. StructureToPtr:
      []Converts a managed object to an unmanaged representation, allowing interaction with native code or memory structures.
      1. Pointer Dereferencing:
      [
      ]Modifying the dereferenced pointer affects the original memory location, reflecting changes in the associated objects.

      Suggestions for Further Exploration:​

    []Memory Management:
    • Delve deeper into memory management, pointer arithmetic, and safe usage of pointers in C#.
    [
    ]Interoperability:
    • Explore interactions with native libraries and systems using pointers in C#.
  • Performance Optimization:
    • Understand how pointers can optimize performance in specific scenarios but also introduce risks. Your example provides valuable insights into advanced C# programming techniques and memory handling. It's a great resource for developers looking to expand their knowledge of low-level programming concepts. If you have more examples or questions, feel free to share! Your contributions are appreciated in the programming community.
 


Solution
Back
Top