Writing Verifiably Type-Safe Code in Visual C++
Code that is verifiably type-safe accesses memory only through object references and associated features such as fields and properties. By accessing memory through well-defined paths, the CLR can verify that code is not accessing memory locations to which it should not have access. If code uses pointer arithmetic to access memory, it is not possible to verify whether that code is reading from or writing to memory locations that wouldn't normally be accessible to it. Code that is not verifiably type-safe is also referred to as unsafe code. Because this code is still managed and still runs under the control of the CLR, it is important to note that unmanaged and unsafe are two very distinct concepts.
Some compilers produce only verifiably type-safe code. Visual Basic .NET is a prime example of a compiler in this class. Other compilers, such as C#, have the option of producing code in either category depending on the language and compiler options you use. Regardless of language and compiler settings, Visual C++ .NET 2002 could not produce managed code that was verifiably type-safe. The situation improved marginally with Visual C++ .NET 2003. You had to follow a detailed series of instructions and run a tool against the assembly after it was produced to produce verifiably type-safe code. Kate Gregory covered the steps to produce type-safe code in a previous article and asserted that any real-world Managed C++ application will fail to meet these requirements.
The inability to produce type-safe code isn't a real problem in today's .NET world. Under the default security policy, code loaded off the local disk does not undergo verification; this includes code loaded by ASP.NET and executed on the server. Code deployed in an intranet or Internet scenario must be verifiably type-safe, but modifying the default security policy with an MSI-based installer is generally an easy work-around. However, as more applications begin to host the CLR, verifiability will move from a nice-to-have feature into a must-have in many scenarios.
The two big drivers of verifiability will be SQL Server 2005 and the Longhorn operating system. Although Longhorn is still a fair way off, Microsoft very likely will remove the current default policy of allowing local code to skip verification. As administrators become more aware of the .NET Code Access Security (CAS) Model, simply modifying the security policy so that an unverifiable assembly will run will get harder in many organizations.
SQL Server 2005 will be here much quicker than Longhorn, and, as most developers know, the ability to run managed code within the confines of the database is one of the key features of the product. Visual C++ has always been the first-choice tool for enabling deep integration with SQL Server, and extended stored procedures written in Visual C++ are the preferred option for enabling deep integration with SQL Server 2000.
The database is often at the heart of e-commerce and other mission-critical applications that keep companies running. The possibility of buggy code bringing down the entire database server is a very serious risk to an organization (and to the jobs of the IT support staff charged with keeping systems operational). By lowering the bar of entry and promoting the benefits of compiled code running inside the database server's main process, Microsoft has increased the possibility of buggy code finding its way into the database server. To counter this risk, Redmond has put in a huge amount of work to extend the hosting interfaces that SQL Server uses to load and control the CLR, which can now be used to control such things as the memory allocation of code at a very granular level and the ability of managed code to display UI elements. In addition to increasing the hosting functionality, Microsoft has employed the existing .NET security infrastructure—such as verification and CAS—to protect the database from poorly written or malicious code.
SQL Server 2005 in the current Beta 2 version has three permission sets that can control how managed code runs: SAFE, EXTERNAL-ACCESS, and UNSAFE. The SAFE permission set essentially confines a programmer to working with the managed SQL Server objects and the features of the particular managed language that the programmer is using. EXTERNAL-ACCESS expands on the SAFE permission set and allows access to external managed libraries, but it still has programming model restrictions related to features such as the UI and threading. You can't call unmanaged code, and verification of type-safety is still required. The UNSAFE permission set places the entire onus on the developer to avoid programming techniques or bugs that may adversely affect the performance or stability of the database server. This is the only permission set that allows unverifiable code, so you will need this permission set to use Visual C++ 2003 assemblies inside SQL Server 2005. SAFE is the default security setting, and convincing a DBA that it is OK to grant a permission named UNSAFE just so you can use C++ isn't an appealing proposition.
Visual C++ 2005 to the Rescue
Along with providing a binding to the CLR (in the form of C++/CLI) that is a lot more natural than the current Managed C++ binding, Visual C++ 2005 provides the ability to produce code that is verifiably type safe. The compiler enforces the verifiable nature of the code, in contrast to Visual C++ 2003 where the developer has to remember to follow a series of complex rules to arrive at a verifiable state.
To provide a quick re-cap, the Visual C++ .NET compiler has a /clr switch that causes the compiler to produce managed methods if possible. It will produce managed code unless #pragma unmanaged directives or programming constructs that cannot be expressed in managed code, such as function pointer comparison, are used. The /clr directive relies on __gc class or ref class keywords to produce managed types. In the absence of these keywords, the directive produces native types with managed methods. These native types may also have a number of native methods, as described previously.
Visual C++ 2005 extends /clr with two new options: /clr:pure and /clr:safe. The /clr:pure compiler switch prevents the compilation of native code within an assembly, but it does allow the production of managed code that is not verifiably type safe. The CLR provides full support for unverifiable code that conducts operations like pointer arithmetic, but, as mentioned, this imposes restrictions on where the code will be able to run. Visual C++ 2005 ships with a managed compilation of the C Run-Time (CRT) Libraries in a DLL called msvcm80.dll. It automatically uses the CRT when /clr:pure is chosen as a compile option.
To use an analogy to the other compilers Microsoft produces, /clr:pure is the same as C# in unsafe mode. The two options are equivalent all the way down to the techniques available for calling native code: COM Interop and P/Invoke are the only options available. C++ Interop (which is known as It Just Works (IJW) in Visual C++ 2003) is not available if /clr:pure is used.
Continuing the analogy, the /clr:safe option is the same as C# code that does not make any native calls and does not use the /unsafe compiler option. It would be fair to state that /clr:safe is C++ in Visual Basic .NET mode. The requirements of /clr:safe are fairly obvious: no native calls, no CRT, SCL, STL, ATL, or MFC, and no pointer arithmetic. C++ code compiled with /clr:safe is free to run anywhere the equivalent C# or VB.NET code can run. Figure 1 shows the Visual C++ properties page for setting these various compiler switches.
Figure 1. Visual C++ 2005 /clr Compiler Options
As a final note, be aware that verifiability is only an entry-level requirement for managed code security. The key benefit of verifiability is that it allows other managed code security features such as CAS to do their jobs in a reliable manner. Managed code hosts, like SQL Server 2005, place higher compliance requirements on code above verifiability, but the main point for C++ programmers is that verifiable code at least allows them in the door, and producing managed assemblies that meet any security requirement will be no harder in C++ that any other language.
BiographyNick Wienholt is an independent Windows and .NET consultant based in Sydney, Australia. He is the author of Maximizing .NET Performance from Apress, and specialises in system-level software architecture and development with a particular focus on performance, security, interoperability, and debugging. Nick can be reached at NickW@dotnetperformance.com.