Advanced C++/CLI: Type Forwarding, Pointers, and Variable Argument Lists

Type Forwarding

For large applications and class libraries, managing the dependency hierarchy among the various assemblies that make up the library can be a challenging exercise. To avoid circular references among assemblies, the natural tendency is to place all types in one big assembly. This has a number of issues—bigger assemblies are slower to download and load, rebuild time is slower in development, and allowing a library to collapse into one big entwined mess under the pressure of entropy eventually makes it difficult to use and prevents extensibility. The obvious solution is to factor types into smaller libraries with well-known and well-managed dependency relations.

Even in the best engineered libraries, new technologies and new requirements that make the dependency factoring decisions made in a previous version of the library invalid. Moving types around so that they are again housed in the appropriate assembly is the simplest solution, but this introduces compatibility issues with code compiled against an earlier version, which will fail at runtime when attempting to use a type that is no longer present in an assembly.

To solve this problem, a type can leave a forwarding address that the .NET runtime will use to locate the new location of a particular type. In version x of an assembly, a type called Customer may be present:

public ref class Customer {};

In version x+1 of the assembly, the type may be moved to a new assembly, say NewAssembly.dll. To allow the .NET runtime to find Customer, type forwarding can be used:

#using "NewAssembly.dll"
[assembly:TypeForwardedTo(Customer::typeid)];

The original assembly must have a reference to the assembly where the type has been forwarded (NewAssembly.dll in this case), and contain the assembly:TypeForwardedTo attribute. With these in place, the .NET runtime will be able to forward requests from a type from one assembly to another.

Type forwarding is a technology designed to keep legacy consumers of an assembly running, and when it comes time to recompile these legacy assembly consumers, the role of type forwarding is at an end, and the referencing project should be updated to point to the new assembly where the type has been moved to.

C++/CLI Pointers

As covered in one of my previous articles, C++/CLI use object handle syntax (^) for variables that hold a reference to a managed object:

Object^ o = gcnew Object();

A reference is much like a C/C++ pointer, but by not holding a physical address to an object, the location of an object on the managed heap can be moved during a garbage collection cycle without affecting the validity of the object handle. At times, code will want to hold a pointer to a member variable of an object rather than the object itself, and that is where the C++/CLI interior_ptr template becomes useful. The interior_ptr template offers the same functionality as a native pointer—pointer arithmetic is possible, and an implicit conversion to bool offers the same nullness check as a native pointer in an if statement. By using the interior_ptr template, fast access to array elements is possible:

int quick_sum(array<int>^ arr){
   int sum = 0;
   cli::interior_ptr<int> pArrayElement = &arr[0];
   for(int ix=0; ix < arr->Length; ++ix){
      if (pArrayElement){
         sum += *pArrayElement;
         ++pArrayElement;
      }
   }
   return sum;
}


int main(array<System::String ^> ^args)
{
   array<int>^ arr = gcnew array<int>{1, 2, 3, 4};
   int sum = quick_sum(arr);
   return 0;
}

An interior_ptr can only be declared on the stack, and pointer arithmetic on an interior_ptr cannot be used in a source code file compiled with the /clr:safe compiler switch.

Because interior_ptr allows the memory address of the data that it is pointing to be changed during a garbage collection cycle, it is not suitable when passing the address of a member variable to a native function, because a change in data location will result in the native instruction operating on an incorrect memory location. To support a fixed pointer, C++/CLI has the pin_ptr template, which fixes the address on an object in memory and prevents the compacting algorithm of the garbage collector moving it. This allows the interior_ptr sample to be re-written using a native function:

#pragma unmanaged
int quick_sum(int cnt, int* pInt){
   int sum = 0;
   for(int ix=0; ix < cnt; ++ix){
      ++pInt;
   }
   return sum;
}

#pragma managed
int main(array<System::String ^> ^args)
{
   array<int>^ arr = gcnew array<int>{1, 2, 3, 4};
   cli::pin_ptr<int> pElement = &arr[0];
   int sum = quick_sum(arr->Length, pElement);
   return 0;
}

Variable Argument Lists

The syntax for specifying a variable argument list in C++/CLI is much the same as the standard C/C++ equivalent. As a quick recap, a standard C++ variable argument list is specified using the ellipsis (...) syntax, and the function consuming the variable argument list iterates over the argument list using the CRT va_arg and va_start macos:

int max(int argCount, ...){
   va_list args;
   va_start(args, argCount);
   int retVal = va_arg(args, int);

   for (int ix = 1; ix < argCount; ++ix){
      int nextVal = va_arg(args, int);
      if (nextVal > retVal){
         retVal = nextVal;
      }
   }
   va_end(args);    //reset list
   return retVal;
}

int _tmain(int argc, _TCHAR* argv[])
{
   int maxVal = max(5, 2, 5, 3, 7, 1);
   return 0;
}

The standard C/C++ mechanism for variable argument lists lacks type safety and can result in stack corruption as the parameter count is passed into the variable-argument function with no checks on its validity, raising the prospect of an incorrect number of arguments popped off the call stack. These problems mean that a direct translation of the standard variable-argument mechanism is not suitable for C++/CLI, which has verifiable type safety as one of the language's requirements. In fact, the nature of standard C/C++ variable argument routines is so incompatible with the .NET runtime that the C++ compiler will generate native instructions for variable-argument list functions even if the /clr compiler switch is used.

The C++/CLI language attempts to offer familiar syntax to the C++ developer in situations where using standard C++ syntax doesn't end up causing unnecessary confusion. In some situations, introducing new syntax such as the object handle (^) to replace the pointer (*) was chosen to clearly differentiate the different concepts. With variable argument lists, the C++/CLI language designers chose to offer a slightly modified form of the standard syntax—the ellipsis is maintained, and type safety is introduced by adding a managed array signature after the ellipsis. Re-writing the earlier code sample as a C++/CLI equivalent, the result is:

using namespace System;
int max(...array<Int32>^ arr ) {
   int retVal = int::MinValue;
   for (int ix = 0; ix < arr->Length; ++ix){
      if (arr[ix] > retVal){
         retVal = arr[ix];
      }
   }

   return retVal;
}

int _tmain(int argc, _TCHAR* argv[])
{
   int maxVal = max(2, 5, 3, 7, 1);
   return 0;
}

The C++/CLI implementation of the max function is actually easier to author and call—the ability to program against an array rather than the va_* macros makes for a simpler implementation, and on the calling side, the removal of the need to specify the parameter count makes calling the max function simpler.

Other than the slightly different syntax of specifying an array after the ellipsis, the C++/CLI variable argument list experience is much the same as the standard C++ ancestor—only one variable argument list can be specified for a particular function, and it must be the last parameter in the function's parameter list. C++/CLI variable argument lists can be used in applications compiled with /clr:safe, and can be used seamlessly from other managed languages.

Conclusion

The C++/CLI language is designed to allow a C++ developer to use the familiar syntax of C++ to target the .NET runtime. By providing features that look and feel like 'real' C++ but map seamlessly to the behavior of .NET, the C++ developer gets the best of both worlds. C++/CLI variable argument lists and the pointer templates deliver a syntax and feel very close to equivalents in the native world, yet are fully compatible with the requirements of the .NET runtime.

Type forwarding is a compatibility feature that allows developers creating large class libraries to refactor which assemblies that types live in without breaking existing clients. Type forwarding was originally developed to support internal Microsoft teams working on the .NET Framework Libraries in .NET 2, but was exposed in .NET and C++/CLI to allow the same flexibility for third-party class library developers.

About the Author

Nick Wienholt is an independent Windows and .NET consultant based in Sydney. He is the author of Maximizing .NET Performance and co-author of A Programmers Introduction to C# 2.0 from Apress, and specialises in system-level software architecture and development, with a particular focus of performance, security, interoperability, and debugging.

Nick is a keen and active participant in the .NET community. He is the co-founder of the Sydney Deep .NET User group and writes technical articles for Australian Developer Journal, ZDNet, Pinnacle Publishing, CodeGuru, MSDN Magazine (Australia and New Zealand Edition) and the Microsoft Developer Network. An archive of Nick's SDNUG presentations, articles, and .NET blog is available at www.dotnetperformance.com. In recognition of his work in the .NET area, he was awarded the Microsoft Most Valued Professional Award from 2002 through 2007.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

  • Corporate e-Learning technology has a long and diverse pedigree. As far back as the 1980s, companies were adopting computer-based training to supplement traditional classroom activities. More recently, rich web-based applications have added streaming audio and video, real-time collaboration and other new tools to the e-Learning mix. At the same time, the growing availability of informal learning tools--a category that includes everything from web searches to social media posts--are having a major impact on …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds