November 26, 2014
Hot Topics:

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

  • December 17, 2008
  • By Nick Wienholt
  • Send Email »
  • More Articles »

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;
}




Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel