Parameter Marshalling with Heap and Stack Objects

snarfblam

Mega-Ultra Chicken
Joined
Jun 10, 2003
Messages
1,832
Location
USA
User Rank
*Expert*
Ive seen certain questions asked a number of times and decided that it would be appropriate to have some reference that clearly explains the details of this tricky aspect of function calls. To get the most out of this you should understand the concept of pointers and the difference between value-type objects and reference-type objects.

Pointers, Pointers Everywhere
Like traditional VB, DotNet languages (C++ aside, this article will focus on VB and C#) do an excellent job of hiding pointers from the programer, despite the fact that they are a very fundamental aspect of DotNet programming, used with reference types, pass-by-reference parameters, Delegate objects, and unsafe C# code. In order to understand the intricacies involved in all of these aspects of programming, however, the programmer must be aware of how pointers play into DotNet programming.

Reference types
Ultimately, references are dumbed down pointers. Of course, this is not an insult at our programming ability, but it is necessitated by our programming paradigm. DotNet is intended to be a purely object-oriented programming language, and memory addresses, dereferencing, and pointer math dont fit the bill. The downside, however, is that it is easy to forget (and just as easy to never realize in the first place) that we are using pointers. A variable that holds a reference to a DotNet object actually simply holds a pointer to that object. A reference is a pointer. See Figure 1, which pictures a value type variable, which directly holds data, and a reference type variable, which holds a pointer to data.

Handles
Okay, I lied. DotNet references arent pointers. They are handles. So, what is the difference? In general, the difference between a pointer and a handle is that a pointer contains an actual memory address, whereas a handle contains a value which identifies a resource. This value may be a pointer. It may not. (In fact, you could think of a pointer as a type of handle.) The point is that the term "handle" stipulates certain behavior (a subset of the features and behavior of a pointer). Handles are usually some kind of pointer, quite often a pointer to a pointer, though they can be as simple as the index of an object in a list of objects. But DotNet handles are actually pointers to runtime data for our DotNet objects (which is actually stapled onto the beginning of our objects in memory), so for the sake of this discussion the statement that DotNet references are pointers is sufficiently accurate.

Pass-By-Reference Arguments
Arguments are passed to functions on the stack. When you pass an argument to a function by reference (using the ref or out keywords in C# or the ByRef keyword in VB) a pointer to the argument is placed on the stack instead of the object itself. This behavior allows us to prevent large objects from having to be copied to the stack and allows the function being called to write back to the original variable.

The Confusion
The question I see all too often is whether or not there is any difference between passing a reference object by reference versus passing a reference object by value. Either way you are passing a pointer, right? So there is no difference, right? And, of course, we would not be right. Let is investigate some different scenarios involving reference types, value types, and by-reference and by-value arguments. Of course, DotNet does a great job simplifying things, so all of these scenarios would look very similar when written in code, but the resulting compiled code would look pretty different (and it very well could behave equally differently).

Value-Type Pass-By-Value This is a very simple and relatively common way of passing arguments to a function. This is how one usually passes an integer, a Rectangle, an enumeration, or any other value type object. The object itself is copied onto the stack. It may be examined by the callee (the function that is being called), but the original object can not be modified because the object passed to the callee is a copy and changes will not be reflected in the original. See Figure 2a.

Value-Type Pass-By-Reference This is a much less common way to pass arguments to a function. Any time you see the word "reference," think pointer. Instead of copying the object onto the stack, a pointer is placed on the stack which points to the original object. This means two things. Firstly, the callee has direct access to the original object via the pointer and may freely modify its value. Secondly, large objects will not need to be copied, which may improve performance (though you would need to see a huge number of function calls with large numbers of large objects in order for this to be truly advantageous). See Figure 2b. Note that the original data can be accessed via the pointer on the stack.

Reference-Type Pass-By-Value This is another common way to pass arguments to a function. Since we are using a reference-type object, we will place a reference on the stack. And what is a reference? A pointer! So we will be placing a pointer to an object on the stack. Say, does that sound familiar? It does! That is exactly what we did when we passed a value-type object by reference. There is an important difference though. There are certain restrictions imposed upon us with reference-type objects that arent there with value-type objects. For instance, would could copy the data from one Rectangle object directly to another Rectangle object. We dont have this kind of direct access, however, with reference-type objects. Not because it is not possible, but because DotNet doesnt allow it. We can only access reference-type objects via public properties and methods. If it werent for these restrictions, reference-type passed by-value would be exactly the same as value-type passed by-reference. See Figure 2c. Note that the end result on the stack is essentially identical to that of figure 2b.

Reference-Type Pass-By-Reference This exceedingly rare creature is the most confusing of them all. See that we have the word "reference" in there twice? Two references means two pointers. Or to be more specific, a pointer to a pointer. The fact that we have a pointer to a pointer means that the callee (the function being called) has direct access to the reference that was passed into the function. In other words, the callee can change the reference and cause it to point to something else. No other argument marshalling mechanism provides this (rarely useful) capability. You can pass a Form to a function, and after the function is called, your variable that referenced said form could, for all you know, reference a completely different form. This, however, is the only difference between reference by-value and reference by-reference. See Figure 2d. Note that the stack contains a pointer to a pointer, allowing us to modify what that pointer points to.

In And Out
C# offers a parameter marshalling scenario in addition to the default pass-by-value mechanism and the pass-by-reference mechanism (using the ref keyword). Although it isnt essential to understanding the difference between by-value and by-reference mechanisms, it is related and I will cover it anyways to complete the article.

C# allows parameters to use the out modifier as an alternative to the ref modifier (or no modifier at all). The out modifier specifies that a variable is passed-by-reference (i.e. a pointer is placed on the stack), but stipulates that the variable isnt necessarily initialized when it is passed into a function, and that the variable must be initialized before the function is finished. The purpose is to provide a mechanism that allows a function to return more than one value. Although this would be possible to do with a ref parameter, using an out parameter forces the function being called to return a value through the parameter. From within the function being called it would be an compiler error to not return something through an out parameter, and it would be a compiler error to read the value of the out parameter before assigning to it.
 
Last edited by a moderator:
Back
Top