Basic Concepts/Definitions
From [2], we have the following definitions:
Managed code is simply "code" that provides enough information to allow the .NET Common Language Runtime (CLR) to provide a set of core services, including:
- Self description of code and data through metadata
- Stack walking
- Security
- Garbage collection
- Just-in-Time compilation
More details can be seen in [2].
Type safe code—code that uses only managed data, and no unverifiable data types or unsupported data type conversion/coercion operations (that is, non-discriminated unions or structure/interface pointers). C#, Visual Basic .NET, and Visual C++ code compiled with /clr:safe generate type safe code.
Another definition is given here from [11] on type-safe code:
Type-safe code is code that accesses types only in well-defined, allowable ways. For example, given a valid object reference, type-safe code can access memory at fixed offsets corresponding to actual field members. However, if the code accesses memory at arbitrary offsets outside the range of memory that belongs to that object's publicly exposed fields, it is not type-safe.
Unsafe Code—code that is permitted to perform such lower-level operations as declaring and operating on pointers, performing conversions between pointers and integral types, and taking the address of variables. Such operations permit interfacing with the underlying operating system, accessing a memory-mapped device, or implementing a time-critical algorithm. Native code is unsafe.
Now a question comes up: if an assembly is managed, and also 100% pure IL, does it mean "100% pure IL" = "100% type safe"? What does "100% managed code" mean?
Background
If a .NET application is 100% type safe code, it is easy for you to migrate your .NET application to run on 32-bit machine and 64-bit system. Inside Visual Studio 2015, you can compile it to target anyCPU by setting in project property tab.[6]
100% Managed code is a big deal. It is harder to keep your code 100% managed code.
In the following cases, we plan to use PEVerify.exe to verify if they are type safe code.
PEVerify.exe Tool
PEVerify.exe[10] is a tool to determine whether the compilation-generated MSIL code and associated metadata meet type safety requirements. We will use this tool to test type safe-ness of the examples here.
To test all following cases, we use PEVerify
in the format of: peverify /md /il filename.exe
Case Study - Int and IntPtr Types
Not to re-invent the wheel, I use an example from Scott Hanselman [4] to verify the type-safe of using pointer type.
using System;
using System.Runtime.InteropServices;
namespace TestGetSystemInfo
{
public class WinApi
{
[DllImport("kernel32.dll")]
public static extern void GetSystemInfo([MarshalAs(UnmanagedType.Struct)]
ref SYSTEM_INFO lpSystemInfo);
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
internal _PROCESSOR_INFO_UNION uProcessorInfo;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public int lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort dwProcessorLevel;
public ushort dwProcessorRevision;
}
[StructLayout(LayoutKind.Explicit)]
public struct _PROCESSOR_INFO_UNION
{
[FieldOffset(0)]
internal uint dwOemId;
[FieldOffset(0)]
internal ushort wProcessorArchitecture;
[FieldOffset(2)]
internal ushort wReserved;
}
}
public class Program
{
public static void Main(string[] args)
{
WinApi.SYSTEM_INFO sysinfo = new WinApi.SYSTEM_INFO();
WinApi.GetSystemInfo(ref sysinfo);
Console.WriteLine("dwProcessorType ={0}", sysinfo.dwProcessorType.ToString());
Console.WriteLine("dwPageSize ={0}", sysinfo.dwPageSize.ToString());
Console.WriteLine("lpMaximumApplicationAddress ={0}",
sysinfo.lpMaximumApplicationAddress.ToString());
}
}
}
Testing results are:
- When the statement is
public int lpMaximumApplicationAddress;
, compile it to target 32-bit. Use PEVerify
and the result is "all class and methods in pointer1.exe is verified
". It means type safe. When runs on 32-bit, it runs well. -
When the statement is public int lpMaximumApplicationAddress;
, compile it to target 64-bit. use PEVerify
and the result is "all class and methods in pointer1.exe is verified
". It means type safe. When runs on 64-bit, get problem on value of lpMaximumApplicationAddress
.
-
When the statement is public IntPtr lpMaximumApplicationAddress;
, compile it to target 32-bit. Use PEVerify
and the result is "all class and methods in pointer1.exe is verified
". It means type safe. When runs on 32-bit, it works well.
-
When the statement is public IntPtr lpMaximumApplicationAddress;
, compile it to target 64-bit. Use PEVerify
and the result is "all class and methods in pointer1.exe is verified
". It means type safe. When runs on 64-bit, it works well.
-
When the statement is public IntPtr lpMaximumApplicationAddress;
, compile it to target anyCPU. Use PEVerify
and the result is "all class and methods in pointer1.exe is verified
". It means type safe. When run it on both 32-bit and 64-bit OS, all works well.
IntPtr Type
From [7], we know IntPtr
is a platform-specific type to represent a pointer or a handle. It is designed to be an integer whose size is platform-specfic. It is 4 bytes on 32-bit hardware and OS and 8 bytes on 64-bit hardware and OS.
Only IntPtr
type is CLS-compliant and UIntPtr
type is not. Only the IntPtr
type is used in the common language runtime.
A more clear statement is from [6]:IntPtr is a "native (platform-specific) size integer." It's internally represented as void* but exposed as an integer. You can use it whenever you need to store an unmanaged pointer and don't want to use unsafe code. IntPtr.Zero is effectively NULL (a null pointer).
Case Study - Pointer Type
C# support direct memory manipulation via pointer type. Pointer types are primarily useful for interoperability with C API. it can also be used to access memory outside the managed heap or for performance-critical hotspots. Here is a simple demo to show the concept of managed assembly and type-safe code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pointer3
{
class Program
{
static void Main(string[] args)
{
int test = 10;
unsafe
{
int* p=null;
p = &test;
Console.WriteLine("the current value of *p is:{0}", *p);
Console.ReadLine();
}
}
}
}
Using assembly checker[6] or corsflag tool, we know this assembly is a managed assembly with 100% pure IL, but using PEVerify too, we see it is not type-safe assembly because stack is messed out. Please see the screenshots to proof this claim.
Case Study - dynamic type
C# is a type-safe programming language. In compile-time all expressions resolve into an instance of a type and the compiler will generate only code that attempts to perform the valid operation on this type. When we need to use C# to communicate with components that are not implemented in C#,C# compiler offers dynamic type to help. please see following example from [12].
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dynamic
{
class Program
{
static void Main(string[] args)
{
for(Int32 demo=0; demo < 2; demo++)
{
dynamic arg = (demo == 0) ? (dynamic)5 : (dynamic)"A";
dynamic result = Plus(arg);
M(result);
}
}
private static dynamic Plus(dynamic arg) { return arg + arg; }
private static void M(Int32 n) { Console.WriteLine("M(Int32): " + n); }
private static void M(string s) { Console.WriteLine("M(String): " + s); }
}
}
using Assembly checker[6] or corflags to check the compiled assembly, it is managed with 100% pure IL. using PEVerify tool to verify, it is 100% type safe.
Case Study - SafeHandle Type
Here we use an example from [13]. Copy the source into Visual Studio Console project in C#and compile it.
Using assembly checker[6] or corFlags tool, we find out it is a managed assembly with 100% pure IL. Using PEVerify.exe tool, we know it is 100% type safe. So SafeHandle class lives up to what it is designed for.
Migration Considerations/Strategy
From this example, if our managed assembly is not 100% type safe code, we need to review our application and its dependencies to determine its issues with migrating to 64-bit. Many of the issues can be addressed through programming changes. In many cases, we can update or re-write our code in order to run correctly in both 32-bit and 64-bit environments.
When moving from 32 to 64-bit, the major type that changes is the pointer and derived data types, like handles. In Windows 64-bit, the pointers and derived types are now 64-bit long. So we need to review Pointer Casting.
In C#, Pointer Arithmetic is not allowed and this is not an issue. From .NET 2.0, a safehandle class[11] is created to represents a wrapper class for operating system handles. It is used to replace IntPtr
to represent a handle.
Points of Interest
Though for all of these sceanarios, PEVerify.exe result shows they are type safe assembly, but we still see problems when run these executables. So to migration our .NET application from 32-bit to 64-bit, we need to review source code to resolve these integer pointers. We cannot completely depend on PEVerify.exe.
References
- Migrating 32-bit Managed Code to 64-bit
- What is managed code?
- Managed, Unmanaged, Native: What Kind of Code Is This?
- Back to Basics: 32-bit and 64-bit confusion around x86 and x64 and the .NET Framework and CLR
- Difference between managed and unmanaged code
- Which Platform to Target your .NET Applications?
- Just what is an IntPtr exactly?
- c# pointers vs IntPtr
- IntPtr Structure
- Peverify.exe (PEVerify Tool)
- Writing Verifiably Type-Safe Code
- LR via C# (3rd edition) - Jeffrey Richter
- SafeHandle Class
History
- 05/30/2016 - Initialized this article
turns good thoughts into actions...