Visual C Concerns

team lib

Visual C# Concerns

Although C and C++ support the use of pointers, most .NET languages do not. Instead, they use references to access instances of reference types, which only give indirect access to an object and leave the garbage collector free to move the object around in memory.

C# provides a way to work with pointers because Microsoft recognizes several reasons why pointers are useful in C# code:

  • You might need to pass pointers to unmanaged functions or COM method calls.

  • You might need to interact with a memory-mapped device, which requires I/O to use a particular address.

  • References use an extra layer of indirection, so for code that needs maximum efficiency you might want to use pointers.

  • You might be converting some code from C or C++ to C# and want to keep as close to the original as possible.

Visual C# code that uses pointers is called unsafe code and requires the use of two keywords: unsafe and fixed . These two keywords were introduced briefly in Chapter 3, and they'll be more thoroughly explained here.

Important 

Unsafe code results in compiler errors unless the /unsafe compiler flag is used. If you're using Visual Studio .NET, you can set the unsafe code option on the Configuration Settings tab of the project's Properties Pages dialog box. Command-line compilations should include the /unsafe flag.

The unsafe Keyword

The unsafe keyword is used to declare an unsafe context . Any operation in Visual C# that involves pointers must take place in an unsafe context. You can use the unsafe keyword at several levels in Visual C# code. First you can apply unsafe to a class declaration, in which case all the class members are taken to be unsafe. This means that pointers can freely be used within all class methods :

 publicunsafeclassUnsafeType { //... } 

You can also apply the keyword to one or more methods within a class:

 publicunsafevoidUnsafeMethod() { //... } 

The scope of the unsafe keyword is from the point of declaration up to the end of the function; this means you can use pointers as method arguments because the argument list is within the unsafe context.

The third option is to apply unsafe to a block of code within a method:

 unsafe { //Usepointerswithinthisblock } 

Finally, you can use unsafe on Platform Invoke prototype declarations. This is particularly useful when you're calling functions that take pointer arguments because you can specify the pointer type directly and don't have to work out the equivalent managed type. The example in Listing 12-3 shows how to use unsafe in this manner. You can find this sample in the Chapter12\CsUnsafe folder of the book's companion content.

Listing 12-3: Using unsafe on Platform Invoke prototype declarations
start example
 usingSystem; usingSystem.Runtime.InteropServices; publicclassWinApi{ //FlagsforusewithMessageBox publicstaticintMB_OK=0x00000000; publicstaticintMB_OKCANCEL=0x00000001; publicstaticintMB_ABORTRETRYIGNORE=0x00000002; publicstaticintMB_YESNOCANCEL=0x00000003; //Notetheuseof'unsafe'ontheprototype [DllImport("User32.dll",CharSet=CharSet.Ansi)] publicexternstaticunsafeintMessageBox(inthWnd,char*txt, char*caption,inttype); } classClass1 { [STAThread] staticvoidMain(string[]args) { unsafe{ stringsText= "CalledfromUnsafeCode"; IntPtrpTxt=Marshal.StringToHGlobalAnsi(sText); stringsCaption= "Testing..."; IntPtrpCaption=Marshal.StringToHGlobalAnsi(sCaption); intnRet=WinApi.MessageBox(0,(char*)pTxt.ToPointer(), (char*)pCaption.ToPointer(),WinApi.MB_OK); } } } 
end example
 

The WinApi class contains the declaration of the Platform Invoke prototype for MessageBox , along with some useful constants. Note how the declaration uses the unsafe keyword and how DllImport specifies the ANSI character mapping. You'll see why this is necessary very shortly. The second and third arguments for the function can be specified as char* because the function has been declared as unsafe .

Because the function is declared as unsafe , it can be called only from within an unsafe context. It would be possible to declare the whole of Class1 as unsafe , but I've chosen to add an unsafe code block to the place where MessageBox is actually called. There is a certain amount to be done before the function can be called because of the need to marshal the string arguments. By using pointers, I'm assuming the burden of converting arguments to the right type (for example, char* ) before calling the function; if I had chosen not to use unsafe code, I could have provided a string argument and marshaling would have been performed automatically.

String data is passed to this unmanaged function using a pointer. All .NET memory is allocated on the managed heap, which unmanaged applications don't have access to. The unmanaged function expects a pointer to memory it can access, so the string data has to be copied from the managed heap to the unmanaged heap, and this can be done using the Marshal.StringToHGlobalAnsi function. This function will take a .NET string, dynamically allocate an appropriately sized amount of unmanaged memory using the Windows GlobalAlloc function, and then copy the content of the string into the unmanaged memory.

Note the Ansi on the end of the function name . There are also StringToHGlobalAuto and StringToHGlobalUni versions, but for this example I'm choosing to use the function that converts a string to ANSI (single byte) characters . Using this version means that I need to use the CharSet parameter to DllImport to tell the interop marshaler to pick the ANSI version of MessageBox . If you don't use CharSet to specify the ANSI version, the marshaler will pick the Unicode version by default, and this won't display the characters properly.

The fixed Keyword

Using references allows the .NET garbage collector to move objects around when it compacts memory during a collection. If you're passing a pointer to a managed object to an unmanaged function, you need to ensure that the garbage collector is not going to move the object while its address is being used through the pointer. This process of fixing an object in memory is called pinning , and it's accomplished in Visual C# using the fixed keyword. The example in Listing 12-4 shows how to use fixed . You can find this example in the Chapter12\CsFixed folder of the book's companion content.

Listing 12-4: Class1.cs from the CsFixed project
start example
 usingSystem; classClass1 { //Functiontosumtheelementsinanarray staticunsafeintSum(int[]arr) { intresult=0; //Pinthearray fixed(int*pa=&arr[0]) { for(inti=0;i<arr.Length;i++) result+=*(pa+i); } returnresult; } [STAThread] staticvoidMain(string[]args) { int[]arr=newint[]{2,3,4,5,6,7}; Console.WriteLine("Sum={0}",Sum(arr)); } } 
end example
 

The Sum function takes a managed array of int as an argument. The fixed keyword is used to declare a pointer to a managed variable; declaring a pointer in this way pins the managed variable until the end of the fixed block. Once the end of the block has been reached, the managed variable is unpinned, and the garbage collector is free to move it again.

Note 

Pinning one element of a managed array will pin the entire array. In more general terms, pinning one member of a type results in the entire instance being pinned. You should, of course, keep objects pinned for as short a time as possible.

The normal C/C++ operators are used to work with pointers: & to take the address of a variable or array element, and * to dereference pointers. Since these operators work with pointers, it is only valid to use them in unsafe contexts. You can also use C-style pointer arithmetic, as shown in the line that increments the result variable.

More Info 

Pointer arithmetic is used to access memory locations in terms of an offset from a starting point. The + and - operators are used to specify whether to move forward or back from the starting point, and the size of the increment or decrement is determined by the pointer type. For example, if pa is a pointer to a 4-byte integer, the expression pa+2 defines an address 8 bytes on from pa .

The stackalloc Keyword

The stackalloc keyword is sometimes used in conjunction with unsafe . It allows you to allocate a block of memory on the stack rather than on the heap; because stackalloc returns a pointer to the allocated memory, it can be used only in an unsafe context.

Allocating memory on the stack has two effects:

  • The memory will automatically be freed at the end of the enclosing block.

  • Because the memory is located on the stack, it's not managed by the garbage collector, so it does not have to be pinned.

One advantage of using stackalloc is that you can allocate memory to hold an array and access the array elements directly using pointer arithmetic. This will result in much faster array access than using managed array objects. The following function shows how you could declare and use an array using stackalloc , and you'll see that you use it in the same way as the new operator:

 publicunsafevoidMyFunction() { //Stackalloccanonlybeusedinanunsafecontext int*pa=stackallocint[100]; //Usethearray pa[0]=10; //Thememoryisdeallocatedattheendofthefunction } 
 
team lib


COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140

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