What am I doing wrong with GCHandle here...

S

Scott

namespace TestProg
{
class Globals
{
public static Int32 percent_done;
private static GCHandle percent_done_handle;
public static IntPtr percent_done_pointer;
}

public static void Init()
{
percent_done = 0;
percent_done_handle = GCHandle.Alloc(percent_done, GCHandleType.Pinned);
percent_done_pointer = percent_done_handle.AddrOfPinnedObject();
}

public static void Cleanup()
{
percent_done_handle.Free();
}

static class Program
{
[STAThread]
static void Main()
{
Globals.Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
Globals.Cleanup();
}
}
}

// Form1 spawns a thread that then passes percent_done_pointer to some
unmaged C++ that takes a really long time to execute.
// The C++ code will occasionally update percent_done through
percent_done_pointer.
// Another thread in this C# sharp program is polling the value of
percent_done and updating a progress bar on Form1.

// The problem is that percent_done does not change from the viewpoint of
the C# program.
// In the watch window it remains at 0, yet I can see the memory pointed to
by percent_done_pointer changing.
// I guess the only conclusion I can reach is that percent_done_pointer
points to some memory that is a copy of percent_done.
// But why?
 
S

Scott

Ben Voigt said:
What's this polling look like? If .NET can determine that the value is never
changed in the C# program, it never will re-read the variable.

It's just a timer control waking up every 10th of a second to update a
progress bar based on the value of percent_done.

And yes, normally I would expect the runtime code to be smart enough to
cache some variables in registers and not go to memory if the runtime doesn't
think they have changed, but...that's what "volatile" is for. I will
definitely have to mark percent_done as being volatile, but I found out
that's not the immediate problem; the immediate problem is that somehow the
pointer I get from the GCHandle points to some copy of percent_done and not
percent_done itself. I found a post elsewhere that states that this has to
do with the fact that value types (of which an Int32 is defintely one) get
boxed and you get a GCHandle to the boxed version, resulting in the inability
to change the real version. This jives with the behavior I'm seeing where I
can see the correct initial value of percent_done through
percent_done_pointer, and this value changes as the C++ code updates it, but
the actual value of percent_done as far as the CLR is concerned doesn't
change - because I'm modifying some copy of it.

The thread discussing this is here:
http://social.msdn.microsoft.com/Forums/en-US/clr/thread/3d3e1038-ea64-4c3d-9f46-03fce3e2639f

I though about using fixed() to get my pointer after pinning percent_done
using my GCHandle to it, but I don't think this will work, because
GCHandle.Alloc(), I think, is going to pin the boxed copy of percent_done and
not the real percent_done, therefore as soon as I exit the fixed() block my
pointer is potentially not valid because the garbage collector can move
percent_done. The garbage collector can't move the boxed copy of
percent_done, but that doesn't matter.

So the question of the hour is: is there any way to pin an instance of a
value type in memory, get a pointer to it, and use that pointer outside of a
fixed() block?
 
B

Ben Voigt [C++ MVP]

Scott said:
It's just a timer control waking up every 10th of a second to update a
progress bar based on the value of percent_done.

And yes, normally I would expect the runtime code to be smart enough
to cache some variables in registers and not go to memory if the
runtime doesn't think they have changed, but...that's what "volatile"
is for. I will definitely have to mark percent_done as being
volatile, but I found out that's not the immediate problem; the
immediate problem is that somehow the pointer I get from the GCHandle
points to some copy of percent_done and not percent_done itself. I
found a post elsewhere that states that this has to do with the fact
that value types (of which an Int32 is defintely one) get boxed and
you get a GCHandle to the boxed version, resulting in the inability
to change the real version. This jives with the behavior I'm seeing
where I can see the correct initial value of percent_done through
percent_done_pointer, and this value changes as the C++ code updates
it, but the actual value of percent_done as far as the CLR is
concerned doesn't change - because I'm modifying some copy of it.

The thread discussing this is here:
http://social.msdn.microsoft.com/Forums/en-US/clr/thread/3d3e1038-ea64-4c3d-9f46-03fce3e2639f

I though about using fixed() to get my pointer after pinning
percent_done using my GCHandle to it, but I don't think this will
work, because GCHandle.Alloc(), I think, is going to pin the boxed
copy of percent_done and not the real percent_done, therefore as soon
as I exit the fixed() block my pointer is potentially not valid
because the garbage collector can move percent_done. The garbage
collector can't move the boxed copy of percent_done, but that doesn't
matter.

So the question of the hour is: is there any way to pin an instance
of a value type in memory, get a pointer to it, and use that pointer
outside of a fixed() block?

Pin the ref class containing the value member variable.

Or since you're reading it only 10 times per second, just allocate memory
from the unmanaged heap (several options in the Marshal class) and use
Marshal to copy the data from it as needed.
 
S

Scott

I have tried wrapping percent_done in a ref class, and so far it isn't
working...

It failed with a System.ArgumentException on the GCHandle.Alloc line.

It gives Additional Information as: Object contains non-primitive or
non-blittable data.

Uh...not that I can see...

I'm passing it a Pinned object which has one Int32 member, which is
primitive and blittable, so I don't know why this is happening.

Here is new example code.

namespace TestProg
{
internal class Pinned
{
public Int32 percent_done;
}

internal static class Globals
{
public static Pinned pinned_stuff;
private static GCHandle pinned_stuff_handle;
public static IntPtr percent_done_pointer;
}

public static void Init()
{
pinned_stuff = new Pinned();
pinned_stuff_handle = GCHandle.Alloc(pinned_stuff, GCHandleType.Pinned);
unsafe (Int32 * percent_done_ptr = & pinned_stuff.percent_done)
{
percent_done_pointer = (IntPtr) percent_done_ptr;
}
}

public static void Cleanup()
{
percent_done_handle.Free();
}

static class Program
{
[STAThread]
static void Main()
{
Globals.Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
Globals.Cleanup();
}
}
}


Ben Voigt said:
Pin the ref class containing the value member variable.

Or since you're reading it only 10 times per second, just allocate memory
from the unmanaged heap (several options in the Marshal class) and use
Marshal to copy the data from it as needed.
 
B

Ben Voigt [C++ MVP]

Scott said:
I have tried wrapping percent_done in a ref class, and so far it isn't
working...

It failed with a System.ArgumentException on the GCHandle.Alloc line.

It gives Additional Information as: Object contains non-primitive or
non-blittable data.

Yuck. Well, I've used pinning GCHandle before with arrays, so I'd suggest
using an array with Length == 1 instead of declaring your own class.
Uh...not that I can see...

I'm passing it a Pinned object which has one Int32 member, which is
primitive and blittable, so I don't know why this is happening.

Here is new example code.

namespace TestProg
{
internal class Pinned
{
public Int32 percent_done;
}

internal static class Globals
{
public static Pinned pinned_stuff;
private static GCHandle pinned_stuff_handle;
public static IntPtr percent_done_pointer;
}

public static void Init()
{
pinned_stuff = new Pinned();
pinned_stuff_handle = GCHandle.Alloc(pinned_stuff,
GCHandleType.Pinned);
unsafe (Int32 * percent_done_ptr = & pinned_stuff.percent_done)
{
percent_done_pointer = (IntPtr) percent_done_ptr;
}
}

public static void Cleanup()
{
percent_done_handle.Free();
}

static class Program
{
[STAThread]
static void Main()
{
Globals.Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
Globals.Cleanup();
}
}
}



__________ Information from ESET NOD32 Antivirus, version of virus signature database 4178 (20090622) __________

The message was checked by ESET NOD32 Antivirus.

http://www.eset.com
 
S

Scott

Thanks Ben,
I'm unsure what you mean here.
Could you provide a brief example of this concept by marking up the most
recent code I posted.
 
B

Ben Voigt [C++ MVP]

Scott said:
Thanks Ben,
I'm unsure what you mean here.
Could you provide a brief example of this concept by marking up the
most recent code I posted.

Sure, see below.

namespace TestProg
{
// removed Pinned class

internal static class Globals
{
public static int[] pinned_stuff; // type was Pinned
private static GCHandle pinned_stuff_handle;
// public static IntPtr percent_done_pointer;
}

public static void Init()
{
pinned_stuff = new int[1]; // was creating a Pinned object
pinned_stuff_handle = GCHandle.Alloc(pinned_stuff,
GCHandleType.Pinned);
// this is no longer needed because arrays marshal as pointer-to-element
by default
//unsafe (Int32 * percent_done_ptr = &pinned_stuff[0])
//{
// percent_done_pointer = (IntPtr) percent_done_ptr;
//}
}

public static void Cleanup()
{
percent_done_handle.Free();
}

static class Program
{
[STAThread]
static void Main()
{
Globals.Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
Globals.Cleanup();
}
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top