Pinning and Boxing


This section discusses two managed C++ keywords, __pin and __box, and shows you how they’re used in code.

Pinning Pointers

In .NET, you normally leave the CLR to manage all the details of memory allocation and management, and the CLR assumes that it can move managed objects around in the managed heap whenever it wants. At times, however, you might have to tell the CLR to leave objects where they are. For example, if you want to pass a pointer to a managed object to unmanaged function, you don’t want the CLR to move the object around in memory while the object is being used by the unmanaged code.

Pinning gives you a way to tell the CLR to leave an object where it is in memory: the object is pinned in place until the pin is removed. The __pin keyword lets you create a pinning pointer, which can be used to pin an object in memory; any object referred to through a pinning pointer won’t be moved until the pinning pointer no longer refers to that object. The pinning pointer would no longer refer to an object when the pinning pointer goes out of scope, or if 0 is explicitly assigned to the pointer.

You can use pinning on all or part of a managed object. Pinning a member of a managed object results in the whole object being pinned. The following code fragment shows the creation and use of a pinning pointer:

// A class that contains an integer __gc class MyClass { public: int val; }; // Create a managed object MyClass* pObject = new MyClass(); // Create a pinning pointer MyClass __pin *pPinned = pObject; // Pass the integer member to an unmanaged function someFunc(&pPinned->val); // Zero out the pinning pointer pPinned = 0;

Once the object has been pinned, you can pass the address of the int member to an unmanaged function, confident that the int won’t be moved around in memory. Assigning 0 to the pinning pointer frees up the MyClass object so that it can be moved.

Boxing and Unboxing

Boxing and unboxing, which will be discussed in a moment, allow value types to be treated as objects. We talked about value types in Chapter 9, where you learned that they are fundamentally different from reference types. To reiterate, value types have three particular properties:

  • Value types are stored on the stack, unlike references, which are stored on the run-time heap.

  • Instances of value types are always accessed directly, unlike reference types, which are accessed through references. This means that you don’t use the new operator when creating instances.

  • Copying value types copies the value rather than the reference.

Anything that wraps around a simple value, such as a Boolean or an integer, and that is less than about 16 bytes in size is a good candidate for making a value type. Because value types aren’t accessed via references they can be far more efficient than the equivalent reference types, but can’t be regarded as objects in the same way that reference types can. This becomes a problem when you want to use a value type in a context where an object reference is needed. For example, consider the overload of the Console::WriteLine function that performs formatted output, whose prototype is shown here:

static void WriteLine(String*, Object*);

The String* parameter is the format string, and the second is a pointer to any .NET reference type. Since value types aren’t accessed by references, you can’t directly specify a value type. Suppose you try this:

int foo = 12; Console::WriteLine(S"foo is {0}", foo);

You’ll get the following compiler error:

error C2665: ’System::Console::WriteLine’ : none of the 19 overloads can convert parameter 2 from type ’int’

Boxing

Boxing wraps a value type in an object box so that it can be used where an object reference is needed. In managed C++, this wrapping is done using the __box keyword.

Note

You’ve already seen this __box keyword used many times in the book, so I won’t present an exercise here.

You can fix the WriteLine code like this:

int foo = 12; // Box the value so it can be printed Console::WriteLine(S"foo is {0}", __box(foo));

When you use the __box keyword, three things happen:

  • A managed object is created on the CLR heap.

  • The value of the value type is copied bit by bit into the managed object.

  • The address of the managed object is returned.

Note that the managed object contains a copy of the value type; this means that any modifications you might make to the managed wrapper don’t propagate back to the original value.

Note

C# supports implicit boxing, in which the compiler boxes values when it determines that boxing is necessary. This isn’t supported in managed C++ for performance reasons, so you have to use the __box keyword explicitly in order to box values.

Unboxing

What if you want to copy the value from a boxed object? In C# this is done automatically for you by the compiler, but in managed C++ you have to do it yourself. There is no __unbox keyword that corresponds to __box, so you must unbox manually. The following exercise shows you how to get the value back out of a boxed object using a dynamic cast.

  1. Create a new Visual C++ Console Application (.NET) project named Boxing.

  2. Edit the _tmain function to create an integer and box it:

    int _tmain() { Console::WriteLine(S"Boxing Example"); // Create an int int foo = 12; // Box it Object* pFoo = __box(foo); // Use the boxed object Console::WriteLine(S"Value of foo is {0}", pFoo); return 0; } 
  3. Add the following code to get the value back out of the box:

    // Unbox the value int fooTwo = *dynamic_cast<__box int*>(pFoo); Console::WriteLine(S"fooTwo is {0}", __box(fooTwo));

    The dynamic_cast checks to see whether a boxed int is on the other end of the pFoo pointer, and if it is, it returns an int pointer, which is dereferenced by the first asterisk. See the following sidebar in this chapter, “Casting in C++,” for more details on the dynamic_cast operator.

start sidebar
Casting in C++

The dynamic_cast operator is a mechanism for casting variables in C++. Casting has been around in C for many years as a means for the programmer to explicitly convert between types, and it is usually used to perform conversions that the compiler otherwise wouldn’t do. Here’s an example:

int width = 640; int height = 480; double ratio = width / height;

Dividing two integers gives an integer result, so the answer is 0, which wasn’t intended. This code will give the correct result:

double ratio = double(width) / height;

The cast tells the compiler to treat width as a double, which results in a floating- point division operation. There are two equivalent syntaxes for casting, both of which have exactly the same effect:

double ratio = (double)width / height; // older style C cast double ratio = double(width) / height; // C++ function-style cast

Explicit C-style casting like this is dangerous, because it enables the programmer to perform risky and inappropriate conversions, as shown in this example:

char* pc = ...; int* pi = (int*)pc;

Here, a character pointer is being cast to an integer pointer. Since they’re both pointers and simply contain addresses, this conversion will work, but is it likely that the address of a character or string can also be used as the address of an integer? This is probably an error on the programmer’s part.

Casting is often used with object pointers, especially when inheritance is involved. Consider the following code:

Car* pc = new Car(); Vehicle* pv = pc;

If Car derives from Vehicle, it is quite safe to assign pc to pv. Car inherits all the members of Vehicle, so you can use a Car through a Vehicle pointer. However, what if later in the program you want to go back the other way?

Car* pMyCar = pv;

The compiler will complain if you try this, typically giving you error C2440: “Cannot convert from Vehicle* to Car*.” It’s fine to go from Car to Vehicle, because a car is a vehicle, but if you try to assign from a Vehicle to a Car, how does the compiler know that the object on the other end of the Vehicle pointer is a Car? It could be any kind of Vehicle, such as a Bus, Truck, or Motorcycle. Casting up the hierarchy—that is, upcasting from Car to Vehicle—is inherently safe, whereas downcasting from Vehicle to Car is inherently unsafe.

You can use an explicit cast to tell the compiler what to do, like this:

Car* pMyCar = (Car*)pv;

This code gets rid of the error message, but it is error-prone: What if the object on the other end of pv isn’t a Car? There’s no way the compiler can warn you, and your program can get into serious trouble later on. C++ provides four casting operators for those situations: const_cast, static_cast, dynamic_cast, and reinterpret_cast. These are designed to make casting more explicit and safer.

The const_cast operator is used to remove const from pointers; if pcc is a const char* pointer, the following two lines of code are equivalent:

char* pc = (char*)pcc; char* pc = const_cast<char*>(pcc);

Const_cast checks that the type of the pointer and the type given in the angle brackets differ only by const. If they’re any more different than that—for example, int* and const char*—you’ll get a run-time error.

The static_cast operator converts between types in an expression using only the type information available in the expression. This operator is often used for converting between built-in types (such as int to double), and might not be safe. Here’s an example of a static cast:

double d = static_cast<double>(anInt);

You can use static_cast to cast between derived and base types (for example, from Car* to Vehicle*), but you don’t usually have to because the compiler will do this automatically as required.

The dynamic_cast operator converts between objects in a hierarchy when the check needs to be done at run time.

Car* pc = dynamic_cast<Car*>(pVehicle);

A check is done at run time to see whether the object on the other end of the pVehicle pointer is indeed a Car. If it is, a Car* pointer is returned, but if it isn’t, the result is a null pointer. The dynamic_cast operator gives you a simple way to check whether the wrong pointer has been used:

Car* pc = dynamic_cast<Car*>(pVehicle); if (pc == 0) // pVehicle wasn’t pointing to a Car

The final operator, reinterpret_cast, allows any pointer type to be converted to any other pointer type and allows integer types to be converted to and from pointers. Here’s an example:

unsigned int ui = reinterpret_cast<unsigned int>(pVehicle);

The address in the Vehicle pointer will be used to initialize an unsigned integer. Although there are legitimate reasons for needing to do this, you won’t want to do it very often, if at all!

If you need to use casting in C++ code, make sure you use the C++ casting operators rather than the traditional C-style casts. Using C-style casts leaves you open to all sorts of errors, many of which can be caught by the more modern C++ cast operators.

end sidebar




Microsoft Visual C++  .NET(c) Step by Step
Microsoft Visual C++ .NET(c) Step by Step
ISBN: 735615675
EAN: N/A
Year: 2003
Pages: 208

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net