Chapter 15: Unsafe Code

Chapter 15: Unsafe Code


Unsafe code can access unmanaged memory, which is outside the realm of the Common Language Runtime (CLR). Conversely, safe code is limited to accessing the managed heap. The managed heap is controlled by the CLR under the auspices of the garbage collector (GC). Code that addresses the managed heap is intrinsically safer. The CLR automatically releases unused objects, performs type verification, and conducts other checks on managed memory. Developers can focus on core application development instead of administrative tasks such as memory management. For this reason, safe code improves user productivity and satisfaction.

Pointers to unmanaged memory are available in unsafe code. Like unmanaged memory, pointers are also outside the realm of the CLR. Pointers point to a fixed location in unmanaged memory, whereas reference types point to a moveable location in managed memory. The CLR manages reference types, which includes controlling the lifetime of objects and calling cleanup code. Developers do not delete memory allocated for reference types and are not overly involved in the intricacies of memory management. In C and C++ application development, developers were preoccupied with memory management. Despite this, improper management of pointers is a primary contributor to unsafe code in the unmanaged environment, including memory leaks, access of invalid memory, deletion of pointers, and fence post errors. Abstracting the nuances of pointer management and manipulation with reference types has made managed code considerably safer. However, when needed, you can exempt yourself from secure code and access pointers directly.

When is unsafe code appropriate? Not often. Unsafe code is provided within C# as the exception, not the rule. There are specific circumstances in which unsafe code is recommended:

  • Unmanaged code often relies heavily on pointers. When porting this sort of code to C#, unsafe code is a possible solution. Most nontrivial C and C++ programmers heavily leverage pointers.

  • Implementing a software algorithm, in which pointers are integral to the design, might necessitate unsafe code.

  • Calling an unmanaged function might require function pointers.

  • Pointers might be required when working with binary memory resident data structures.

  • Unmanaged pointers might improve performance and efficiencies in certain circumstances.

Code in an unmanaged module is also considered unsafe. Code in an unmanaged module is shielded from the CLR. Therefore, no code verification, stack tracing, or other checking is performed on the unmanaged code, which makes the code less safe.

Developers sometimes need to call unmanaged code from managed applications. Although the Microsoft .NET Framework class library (FCL) contains most of the code needed for .NET application development, the FCL umbrella does not encompass everything. You might need to call functions (APIs) in operating system libraries for behavior outside the FCL. In addition, proprietary and vendor software might not be available as managed code. Alternatively, you can call managed code from an unmanaged module, such as during a callback. Managed code might also be exposed to unmanaged clients.

Platform invocation services (PInvoke) is the bridge between managed and unmanaged execution. The bridge is bidirectional. From managed code, PInvoke is responsible for locating, loading, and executing a function in an unmanaged module. Marshaling is the primary concern of cross-platform calls and the responsibility of the Interop marshaler. Marshaling converts parameters and return types between unmanaged and managed acceptable formats. Fortunately, marshaling is not always required, which avoids unnecessary overhead. Certain types, such as blittable types, do not require transformation and are the same in managed and unmanaged memory.

You can also build bridges between managed code and COM components, which contain unmanaged code. The Runtime Callable Wrapper (RCW) helps managed code call COM components. The COM Callable Wrapper (CCW) portrays a managed component as a COM component. This makes the managed component indirectly accessible to COM clients. COM components are also available via PInvoke. However, the CCW and RCW are more convenient and are the recommended solutions in most scenarios. COM interoperability is not a topic for this book. COM Programming with Microsoft .NET, by John Paul Mueller and Julian Templeman (Microsoft Press, 2003), is an excellent resource for additional information on COM interoperability and .NET.

Unsafe code is also not trusted code. Code access security does not extend to unsafe code. Type verification, which helps prevent buffer overrun attacks, is not performed. Code verification is not performed. Therefore, the reliability of the code is undetermined. These are some of the reasons why unsafe code is not trusted. Because it is not trusted, elevated permissions are required to call unsafe code from managed code. Applications that rely on unsafe code might not execute successfully in every deployment situation and should be thoroughly tested in all potential scenarios. Managed code requires the SecurityPermission.UnmanagedCode permission to call unsafe code. The SuppressUnmanagedCodeSecurityAttribute attribute disables the stack walk that confirms the SecurityPermission.UnmanagedCode permission in callers. This attribute is a free pass for other managed code to call unsafe code. It is a convenient option, but also dangerous.

Managed applications that include unsafe code must be compiled with the unsafe option. The C# compiler option is simply /unsafe. In Microsoft Visual Studio 2005, this option is found in the project properties. The project properties are accessible from Solution Explorer. Open the context menu on the project name and choose the Properties menu item. In the Build window, choose the Allow unsafe code option, as shown in Figure 15-1.

Image from book
Figure 15-1: The Build window with the unsafe compiler option