Click here to Skip to main content
15,886,091 members
Articles / Programming Languages / C#

Type Safe Code Validation with Migration to 64-bit OS

Rate me:
Please Sign up or sign in to vote.
3.94/5 (8 votes)
7 Jun 2016CPOL6 min read 17.6K   105   3   2
This post looks at more details with type safe code and migration of .NET application from 32-bit to 64-bit with case study. The troublesome types in the migration are demonstrated. Some terms are clarified. It is for beginners.

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:

  1. Self description of code and data through metadata
  2. Stack walking
  3. Security
  4. Garbage collection
  5. 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.

C#
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;
            
            //test int type causes problem on 64-bit OS
            //after change to IntPtr everything is good
            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:

  1. 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.
  2. 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.

  3. 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.

  4. 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.

  5. 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.

C#
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.

Image 1 Image 2

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].

C#
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

  1. Migrating 32-bit Managed Code to 64-bit
  2. What is managed code?
  3. Managed, Unmanaged, Native: What Kind of Code Is This?
  4. Back to Basics: 32-bit and 64-bit confusion around x86 and x64 and the .NET Framework and CLR
  5. Difference between managed and unmanaged code
  6. Which Platform to Target your .NET Applications?
  7. Just what is an IntPtr exactly?
  8. c# pointers vs IntPtr
  9. IntPtr Structure
  10. Peverify.exe (PEVerify Tool)
  11. Writing Verifiably Type-Safe Code
  12. LR via C# (3rd edition) - Jeffrey Richter
  13. SafeHandle Class

History

  1. 05/30/2016 - Initialized this article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
turns good thoughts into actions...


Comments and Discussions

 
QuestionRegardless of the content... Pin
Richard Algeni Jr6-Jun-16 9:12
Richard Algeni Jr6-Jun-16 9:12 
AnswerRe: Regardless of the content... Pin
Southmountain6-Jun-16 15:48
Southmountain6-Jun-16 15:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.