Bug in C++ /CLI 2005

  • Thread starter Thread starter Howard Swope
  • Start date Start date
H

Howard Swope

Greetings:

Is anyone aware of a fix for the apparent bug in C++ /CLI 2005...

If you create a class with a ref type member variable declared with stack semantics and you declare this member as [NonSerialized], when you deserialize an object of this class type, the member can no longer be used.

It is essentially orphaned.

Help...
 
A bit more info... when I say essentially orphaned what I mean is that the magic that happens to make things declared with stack semantics get placed on the heap doesn't happen on deserialization if your member is attributed with NonSerialized. Also, because it is declared with stack semantics there is no longer any way to assign storage to it, thus orphaned.

Usually orphaned means that memory has been allocated and there is no longer anyway to refer to it. In this case I have a reference and no way to assign memory to it.
"Howard Swope" <howard_swopeAThms3DOTcom> wrote in message Greetings:

Is anyone aware of a fix for the apparent bug in C++ /CLI 2005...

If you create a class with a ref type member variable declared with stack semantics and you declare this member as [NonSerialized], when you deserialize an object of this class type, the member can no longer be used.

It is essentially orphaned.

Help...
 
"Howard Swope" <howard_swopeAThms3DOTcom> wrote in message

I don't know if that's a bug or by design.

When the object is deserialized, a new object is created most likely NOT using stack semantics.

The member variable is still there, but it is uninitialized, probably because only code compiled with the C++ compiler is capable of using stack semantics to create objects.

I'd say the bug is allowing a member variable with stack semantics in the first place. It probably makes the class unusable outside of C++.

Mark

--
Mark Salsbery
Microsoft MVP - Visual C++


Greetings:

Is anyone aware of a fix for the apparent bug in C++ /CLI 2005...

If you create a class with a ref type member variable declared with stack semantics and you declare this member as [NonSerialized], when you deserialize an object of this class type, the member can no longer be used.

It is essentially orphaned.

Help...
 
Mark:

Declaring variables with stack semantics is one of the Advertised features in C++ CLI and a very useful one. For us it is especially useful because we have an unmanaged library of referenced counted objects that we use. We have created managed smart pointers to hold onto them and handle all the reference counting issues. The only real way to do this is by overloading all the pointer operators. This doesn't really work if you initialize them with gcnew. This is certainly not a feature. After deserialization there is no storage associated with the variable and no way to ever use the variable again. You can't use gcnew with a variable declared with stack semantics.

These smart pointers are essential to our code. I am supposed to release this product in 3 months and now it looks like I am going to have to manually implement ISerializable on a hunderd classes. I will have just made my code base magnitudes more error prone. If I don't get a work around for this, I am @#$_(_)+(++( 'd.
"Howard Swope" <howard_swopeAThms3DOTcom> wrote in message

I don't know if that's a bug or by design.

When the object is deserialized, a new object is created most likely NOT using stack semantics.

The member variable is still there, but it is uninitialized, probably because only code compiled with the C++ compiler is capable of using stack semantics to create objects.

I'd say the bug is allowing a member variable with stack semantics in the first place. It probably makes the class unusable outside of C++.

Mark

--
Mark Salsbery
Microsoft MVP - Visual C++


Greetings:

Is anyone aware of a fix for the apparent bug in C++ /CLI 2005...

If you create a class with a ref type member variable declared with stack semantics and you declare this member as [NonSerialized], when you deserialize an object of this class type, the member can no longer be used.

It is essentially orphaned.

Help...
 
Howard said:
Is anyone aware of a fix for the apparent bug in C++ /CLI 2005...

If you create a class with a ref type member variable declared with
stack semantics and you declare this member as [NonSerialized], when you
deserialize an object of this class type, the member can no longer be used.

It is essentially orphaned.

OK, you deserialize a class, but one of its members is marked and
[NonSerialized]. What do you expect the semantics of this member would
be? It cannot be deserialized, because you marked it so. I guess you
should either allow it to be deserialized and augment the data later, or
customize deserialization with your own code as to ignore this marking.

Feel free to ignore my advice, though. I personally think that C++/CLI
is devilishly difficult and only real experts in both domains C++ AND
..NET stand a chance to use it fluently. I'm not an expert in .NET by any
means.


B.
 
Thanks for the suggestion. I am at present exploring all my options.

If one marks a value type as NonSerialized it still has storage associated
with it after deserialization. It is just uninitialized. For example if you
had an int variable to which you assigned the value 5 and it was attributed
with NonSerialized. Upon deserialization the value is 0 as this is the value
given to an uninitialized integer. A reference type declared with stack
semantics needs to behave in the same fashion. Under the covers the
deserialization code needs to assign an uninitialized object of that type to
the variable.

I have tried to assign it storage in the OnDeserialized routine, something
like this...

//~~~~~~~~~~~~~~
// Declared previously as
[NonSerialized]
MyClass myClass;
//~~~~~~~~~~~~~~~~

void OnDeserialziation(Object^ sender)
{
%myClass = gcnew MyClass( ) // unfortunately it doesn't work like this.
}

I am going to put in a support call today at some point.

I agree that C++ / CLI is complex, but it is a wonderful tool to do what it
is designed for, to straddle the managed and unmanaged worlds. It is light
years ahead of its predecessor managed C++. It has a few bugs still to be
worked out, unfortunately mine is one of them. It makes interoperating
between the two worlds as painless as possible and I think is a very elegant
means of doing it. It is really quite remarkable the way that it allows you
to move, almost seamlessly, between managed and unmanaged. There are still
barriers that .Net imposes that it must work within. I would very much like
to be able to use unmanaged types as template parameters to managed classes
and I would very much like to be able to write inline assembler in an
unmanaged assembly. It is very unlikely that this will ever come to pass.
But, all in all a wonderful tool that I hope keeps evolving.

Bronek Kozicki said:
Howard said:
Is anyone aware of a fix for the apparent bug in C++ /CLI 2005...
If you create a class with a ref type member variable declared with
stack semantics and you declare this member as [NonSerialized], when you
deserialize an object of this class type, the member can no longer be
used.
It is essentially orphaned.

OK, you deserialize a class, but one of its members is marked and
[NonSerialized]. What do you expect the semantics of this member would be?
It cannot be deserialized, because you marked it so. I guess you should
either allow it to be deserialized and augment the data later, or
customize deserialization with your own code as to ignore this marking.

Feel free to ignore my advice, though. I personally think that C++/CLI is
devilishly difficult and only real experts in both domains C++ AND .NET
stand a chance to use it fluently. I'm not an expert in .NET by any means.


B.
 
Not member variables.

It makes no sense to have member variables with stack semantics - there's no "stack" involved unless you have an actual object.

Stack semantics is just a C++ language feature. The syntax allows controlling the scope of a managed object (NOT class or member) using the more familiar (to C++ programmers) automatic variable (stack-based) syntax.
Under the hood an object created using stack semantics is still allocated on the managed heap with gcnew.
It ONLY works in C++. You can't possibly expect deserialize code from the framework to handle member variables declared with stack semantics properly.
And, as mentioned by others, you can't expect a member marked [NonSerialized] to be handled at ALL by the deserialize code in the framework.

Mark
 
I'm not understanding why stack based semantic member variables helps you here.

Objects of a ref class can ONLY be created on the managed heap, so why do
you think not using gcnew is even possible?

Why use a managed smart pointer ref class to hold an unmanaged pointer?
Seems to me, all you get from that is less control over the lifetime of your unmanaged objects.

Mark
 
http://msdn.microsoft.com/en-us/library/ms177191.aspx

Prior to Microsoft Visual C++ 2005, an instance of a reference type could only be created using the new operator, which created the object on the garbage collected heap. However, beginning in Microsoft Visual C++ 2005, you can create an instance of a reference type using the same syntax that you would use to create an instance of a native type on the stack. So, you do not need to use gcnew) to create an object of a reference type and that when the object goes out of scope, the compiler will call the object's destructor.

My class has member variables created with stack semantics and the they work fine except for deserialization. I understand that there is really no stack and that the object is being added to the managed heap. However, if you have a member variable declared with stack semantics marked NonSerializable, the underlying object is set to null during deserialization which is logical. But since the variable is not a syntactically a reference pointer it seems there is no possible way to ever have it be anything but null.


Not member variables.

It makes no sense to have member variables with stack semantics - there's no "stack" involved unless you have an actual object.

Stack semantics is just a C++ language feature. The syntax allows controlling the scope of a managed object (NOT class or member) using the more familiar (to C++ programmers) automatic variable (stack-based) syntax.
Under the hood an object created using stack semantics is still allocated on the managed heap with gcnew.
It ONLY works in C++. You can't possibly expect deserialize code from the framework to handle member variables declared with stack semantics properly.
And, as mentioned by others, you can't expect a member marked [NonSerialized] to be handled at ALL by the deserialize code in the framework.

Mark
 
The smart pointer class is wrapping an unmanaged pointer that is reference counted. In the unmanaged library we are using the objects are in charge of their own lifetime, very similar to the way COM does things. They have an IncrementRefCount and DecrementRefCount function. When the ref count gets to 0 the objects delete themselves. My smart pointer class overloads ->, *, ==, !=, and = oeprators to defer to the underlying unmanged pointer. Because I overload the -> I can't really use it any other way besides declared with stack semantics. We use these reference counted objects everywhere and the managed pointers keep us from getting memory leaks.

I'm not understanding why stack based semantic member variables helps you here.

Objects of a ref class can ONLY be created on the managed heap, so why do
you think not using gcnew is even possible?

Why use a managed smart pointer ref class to hold an unmanaged pointer?
Seems to me, all you get from that is less control over the lifetime of your unmanaged objects.

Mark
 
"Howard Swope" <howard_swopeAThms3DOTcom> wrote in message http://msdn.microsoft.com/en-us/library/ms177191.aspx

Prior to Microsoft Visual C++ 2005, an instance of a reference type could only be created using the new operator, which created the object on the garbage collected heap. However, beginning in Microsoft Visual C++ 2005, you can create an instance of a reference type using the same syntax that you would use to create an instance of a native type on the stack. So, you do not need to use gcnew) to create an object of a reference type and that when the object goes out of scope, the compiler will call the object's destructor.




Right. My point is, it's just semantics - which is probably why they called it stack semantics.

The code syntax is the same as a stack based (automatic) variable but the compiled code still needs to call gcnew.
A ref class object still needs to be created on the managed heap no matter what syntax you use.
As far as object lifetime management goes, there's no difference between

void somefunc()
{
SomeRefClass obj(...);
}

and

void somefunc()
{
SomeRefClass ^obj = gcnew SomeRefClass(...);
delete obj;
}


None of this applies to member variables.


I'll take a look at your code sample....I'll be back.


Cheers,
Mark
 
Howard said:
If you create a class with a ref type member variable declared with
stack semantics and you declare this member as [NonSerialized], when you
deserialize an object of this class type, the member can no longer be used.
An member object with stack semantics must be initialized in a
constructor. But if you use default serialization, no constructor is
called. It's as simple (and as bad) as that.

IMO, it's a design error in the .NET framework not to call any
constructor when deserializing an object. At least an optional special
constructor should have been introduced (similar to the one used if the
ISerializable interface is implemented).

And it seems that the designers of C++/CLI didn't think of that case, or
they thought that anyone who uses a member object with stack semantics
will implement ISerializable.

In your case, if the smart pointer class is actually a template (as it
should be, but contrary to the example code you provided) and if it is
possible for you to always create the native objects through a default
constructor (or another constructor with always the same signature), an
easy workaround would be to let the smart pointer class implement
ISerializable and pseudo-serialize it, i.e. remove the [NonSerialized]
attribute.

BTW: calling native code from a finalizer is dangerous. I hope you know
what you're doing and e.g. the ref count of the native object is thread
safe (and its destructor, too, I presume). On the other hand, if you're
always using stack semantic, each smart pointer will be disposed
automatically, and the finalizer is unnecessary anyway.

Joerg
 
Mark said:
Not member variables.

It makes no sense to have member variables with stack semantics -
there's no "stack" involved unless you have an actual object.

I definitely do NOT agree with this. Stack semantics give you all the
correct calls to Dispose in all the corner cases, like exceptions thrown in
constructor initializer lists, automatically. Stack semantics is perhaps
not the best description -- scoped variable lifetime (vs dynamic lifetime)
might be better.
 
Howard said:
Thanks for the suggestion. I am at present exploring all my options.

If one marks a value type as NonSerialized it still has storage
associated with it after deserialization. It is just uninitialized.
For example if you had an int variable to which you assigned the
value 5 and it was attributed with NonSerialized. Upon
deserialization the value is 0 as this is the value given to an
uninitialized integer. A reference type declared with stack semantics
needs to behave in the same fashion. Under the covers the
deserialization code needs to assign an uninitialized object of that
type to the variable.

The compiler-generated code added to each constructor should initialize that
variable. It sounds as if deserialization creates an object without calling
any constructor? This is broken. With no constructor call, there is no
object instance, the lifetime has not started, and you can't safely use it
at all.
 
Ben Voigt said:
I definitely do NOT agree with this. Stack semantics give you all the
correct calls to Dispose in all the corner cases, like exceptions thrown
in constructor initializer lists, automatically. Stack semantics is
perhaps not the best description -- scoped variable lifetime (vs dynamic
lifetime) might be better.

Hi Ben,

How does scoped variable lifetime apply to member variables?
The scoped variable lifetime of a member variable is determined by it's
containing class, right?

For this to work, the compiler would need to generate the gcnew calls for
the member variables. That means one would be stuck with a parameterless
constructor, unless some (possibly obscure) construct syntax was added to
the language to allow other constructor calls for these "automatic"
variables.

I imagine a similar argument occurred when this feature of the language was
designed. :)

Is it a bug or by design?

I still think, given the way it currently works, that stack semantics on ref
class member variables shouldn't be allowed.

Mark
 
Ben Voigt said:
The compiler-generated code added to each constructor should initialize
that variable. It sounds as if deserialization creates an object without
calling any constructor? This is broken.


That's documented, as-designed behavior (right or wrong).

If a constructor was called we wouldn't be having this discussion I suppose
:)

Mark
 
Mark said:
Hi Ben,

How does scoped variable lifetime apply to member variables?
The scoped variable lifetime of a member variable is determined by
it's containing class, right?

For this to work, the compiler would need to generate the gcnew calls
for the member variables. That means one would be stuck with a
parameterless constructor, unless some (possibly obscure) construct
syntax was added to the language to allow other constructor calls for
these "automatic" variables.

Whoa, is someone impersonating the real Mark, the C++ MVP? Or are you just
having an off day?

Because ctor initializer-lists are hardly obscure and fully standard. I
even mentioned them in my post.

The same thing effectively happens for members of native types which take
parameters to their constructors, except it's use of placement new vs gcnew,
and the managed type isn't actually inside the containing class (although
they will be placed contiguously in memory under the most obvious
implementation of the managed heap, which is a stack not a heap anyway).
 
Mark said:
That's documented, as-designed behavior (right or wrong).

We can argue about terms like "right" and "wrong", but that's singularly
useless. The C++ way (enshrined in the ISO standard) is that an object's
life doesn't begin until its constructor finishes executing. So
deserialization creates dead objects that aren't safe to use.
 
Ben Voigt said:
Whoa, is someone impersonating the real Mark, the C++ MVP? Or are you
just having an off day?


Heh :) I'm impersonating me!
Because ctor initializer-lists are hardly obscure and fully standard. I
even mentioned them in my post.


I can't really be expected to read everything, can I? :)

I'm fully in binary deserialization mode here, so constructors were never
relevant to me in this thread.

I am, however, now following what you're saying.

I still don't like it. I like it in C#, but in C++ I want to see the "^" on
my ref type members.
That's my preference, but I withdraw my statements about stack-semantics
member variables being useless.

They don't work so well with deserialization, however.

I'm going with "having an off day" :)

Thanks Ben!

Cheers,
Mark
 
Ben Voigt said:
We can argue about terms like "right" and "wrong", but that's singularly
useless. The C++ way (enshrined in the ISO standard) is that an object's
life doesn't begin until its constructor finishes executing.


This is C++/CLI, not ISO standard C++.

So deserialization creates dead objects that aren't safe to use.


That even SOUNDS wrong!

That means I use a LOT of unsafe dead objects in my apps (I use .NET
serialization extensively) :)


Cheers,
Mark
 
Back
Top