By David Stutz, Ted Neward, Geoff Shilling
Price: $34.95 USD
£24.95 GBP
Cover | Table of Contents | Colophon
Echo. The Echo type has a single property named EchoString, and a single method, DoEcho.
public class Echo
{
private string toEcho = null;
public string EchoString {
get { return toEcho; }
set { toEcho = value; }
}
public string DoEcho( )
{
if (toEcho == null)
throw new Exception("Alas, there is nothing to echo!");
return toEcho;
}
}
Echo. The Echo type has a single property named EchoString, and a single method, DoEcho.
public class Echo
{
private string toEcho = null;
public string EchoString {
get { return toEcho; }
set { toEcho = value; }
}
public string DoEcho( )
{
if (toEcho == null)
throw new Exception("Alas, there is nothing to echo!");
return toEcho;
}
}
http://www.ondotnet.com is one good place to start.Echo component, we need to prepare Rotor for first use.
public class MainApp {
public static void Main( ) {
try {
Echo e = new Echo( );
e.EchoString = "Echo THIS!";
System.Console.WriteLine("First echo is: {0}", e.DoEcho( ));
e.EchoString = null;
System.Console.WriteLine("Second echo is: {0}", e.DoEcho( ));
} catch {
System.Console.WriteLine("Caught and recovered from bad Echo.");
}
}
}
% csc -t:exe -r:echo.dll -debug main2.cs
Microsoft (R) Visual C# Shared Source CLI Compiler version 1.0.0003
for Microsoft (R) Shared Source CLI version 1.0.0
Copyright (C) Microsoft Corporation 2002. All rights reserved.
% clix main2.exe
First echo is: Echo THIS!
Caught and recovered from bad Echo.
#if defined(_DEBUG) || defined(LOGGING)
const char *szDebugMethodName;
const char *szDebugClassName;
szDebugMethodName = compHnd->getMethodName(info->ftn, &szDebugClassName );
#endif
#ifdef _DEBUG
static ConfigMethodSet fJitBreak;
fJitBreak.ensureInit(L"JitBreak");
if (fJitBreak.contains(szDebugMethodName, szDebugClassName,
PCCOR_SIGNATURE(info->args.sig)))
_ASSERTE(!"JITBreak");
// Check if need to print the trace
static ConfigDWORD fJitTrace;
if ( fJitTrace.val(L"JitTrace") )
printf( "Method %s Class %s \n",szDebugMethodName, szDebugClassName );
#endif
using System;
namespace SampleEcho {
public enum EchoVariation { Louder, Softer, Indistinct }
public struct EchoValue {
public string theEcho;
public EchoVariation itsFlavor;
}
public interface IEchoer {
void DoEcho(out EchoValue[] resultingEcho);
}
public class Echo : IEchoer {
private string toEcho = null;
private static int echoCount = 0;
private const System.Int16 echoRepetitions = 3;
public delegate void EchoEventHandler(string echoInfo);
public event EchoEventHandler OnEcho;
public Echo(string initialEcho) {
toEcho = initialEcho;
}
public string EchoString {
get { return toEcho; }
set { toEcho = value; }
}
public void DoEcho(out EchoValue[] resultingEcho) {
if (toEcho == null) {
throw(new Exception("Alas, there is nothing to echo!"));
}
resultingEcho = new EchoValue[echoRepetitions];
for (sbyte i = 0; i < echoRepetitions; i++) {
resultingEcho[i].theEcho = toEcho;
switch (i) {
case 0:
resultingEcho[i].itsFlavor = EchoVariation.Louder;
break;
case 1:
resultingEcho[i].itsFlavor = EchoVariation.Softer;
break;
default:
resultingEcho[i].itsFlavor = EchoVariation.Indistinct;
break;
}
}
if (OnEcho != null) {
OnEcho(System.String.Format("Echo number {0}", echoCount));
}
echoCount++;
return;
}
}
}
using System;
namespace SampleEcho {
public enum EchoVariation { Louder, Softer, Indistinct }
public struct EchoValue {
public string theEcho;
public EchoVariation itsFlavor;
}
public interface IEchoer {
void DoEcho(out EchoValue[] resultingEcho);
}
public class Echo : IEchoer {
private string toEcho = null;
private static int echoCount = 0;
private const System.Int16 echoRepetitions = 3;
public delegate void EchoEventHandler(string echoInfo);
public event EchoEventHandler OnEcho;
public Echo(string initialEcho) {
toEcho = initialEcho;
}
public string EchoString {
get { return toEcho; }
set { toEcho = value; }
}
public void DoEcho(out EchoValue[] resultingEcho) {
if (toEcho == null) {
throw(new Exception("Alas, there is nothing to echo!"));
}
resultingEcho = new EchoValue[echoRepetitions];
for (sbyte i = 0; i < echoRepetitions; i++) {
resultingEcho[i].theEcho = toEcho;
switch (i) {
case 0:
resultingEcho[i].itsFlavor = EchoVariation.Louder;
break;
case 1:
resultingEcho[i].itsFlavor = EchoVariation.Softer;
break;
default:
resultingEcho[i].itsFlavor = EchoVariation.Indistinct;
break;
}
}
if (OnEcho != null) {
OnEcho(System.String.Format("Echo number {0}", echoCount));
}
echoCount++;
return;
}
}
}Echo component that uses two different kinds of value types.
public struct EchoValue {
public string theEcho;
public EchoVariation itsFlavor;
}struct keyword. Since we are dealing with "real data," value types have features that can be used for interop with data structures that already exist—it is possible to designate with great precision how to lay out a value type in memory, both in terms of ordering and alignment. In general, developers will not want or need to do this—layout is something best left to the JIT compiler unless interop with unmanaged code is needed, but it is definitely possible to take fine-grained control over this. (To be complete, it should be mentioned that it is possible to do explicit layout for nonvalue types, but value types are by far and away the most common use for this feature.)Echo component of Example 3-1. Enumerating these elements, the Echo class itself is an object type that implements an interface, contains a delegate, and uses a managed pointer to pass an out parameter.Person object to a Department reference unless the type Person inherits from Department (an unlikely scenario).null, which is a reference literal value that points nowhere.System.Int32 represents a 4-byte signed integer. These types are commonly mapped directly to types that the microprocessor implements in hardware by a given CLI implementation. In the ECMA specification, these mappings and the semantics associated with them are termed the "virtual execution system."
static const BYTE map[] = {
CORINFO_TYPE_UNDEF,
CORINFO_TYPE_VOID,
CORINFO_TYPE_BOOL,
CORINFO_TYPE_CHAR,
CORINFO_TYPE_BYTE,
CORINFO_TYPE_UBYTE,
CORINFO_TYPE_SHORT,
CORINFO_TYPE_USHORT,
CORINFO_TYPE_INT,
CORINFO_TYPE_UINT,
CORINFO_TYPE_LONG,
CORINFO_TYPE_ULONG,
CORINFO_TYPE_FLOAT,
CORINFO_TYPE_DOUBLE,
CORINFO_TYPE_STRING,
CORINFO_TYPE_PTR, // PTR
CORINFO_TYPE_BYREF,
CORINFO_TYPE_VALUECLASS,
CORINFO_TYPE_CLASS,
CORINFO_TYPE_CLASS, // VAR (type variable)
CORINFO_TYPE_CLASS, // MDARRAY
CORINFO_TYPE_BYREF, // COPYCTOR
CORINFO_TYPE_REFANY,
CORINFO_TYPE_VALUECLASS, // VALUEARRAY
CORINFO_TYPE_INT, // I
CORINFO_TYPE_UINT, // U
CORINFO_TYPE_DOUBLE, // R
// put the correct type when we know our implementation
CORINFO_TYPE_PTR, // FNPTR
CORINFO_TYPE_CLASS, // OBJECT
CORINFO_TYPE_CLASS, // SZARRAY
CORINFO_TYPE_CLASS, // GENERICARRAY
CORINFO_TYPE_UNDEF, // CMOD_REQD
CORINFO_TYPE_UNDEF, // CMOD_OPT
CORINFO_TYPE_UNDEF, // INTERNAL
};System.Reflection family of types) or from unmanaged code (using the unmanaged APIs described in clr/src/inc/metadata.h that are outside the CLI specification). Type descriptions can be used to defer decisions until runtime, enabling looser linkages between components and more robust load-time adaptations.Employee is a Person, whereas Department is not), this in turn begs the argument for multiple inheritance within the system, a road the C++ community already went down and discovered significant issues with.Launch, appears without error handling in Example 4-5.
DWORD Launch(WCHAR* pRunTime, WCHAR* pFileName, WCHAR* pCmdLine)
{
HANDLE hFile = NULL;
HANDLE hMapFile = NULL;
PVOID pModule = NULL;
HINSTANCE hRuntime = NULL;
DWORD nExitCode = 1;
DWORD dwSize;
DWORD dwSizeHigh;
IMAGE_DOS_HEADER* pdosHeader;
IMAGE_NT_HEADERS32* pNtHeaders;
IMAGE_SECTION_HEADER* pSectionHeader;
WCHAR exeFileName[MAX_PATH + 1];
// open the file & map it
hFile = ::CreateFile(pFileName, GENERIC_READ, FILE_SHARE_READ,
0, OPEN_EXISTING, 0, 0);
hMapFile = ::CreateFileMapping(hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL);
pModule = ::MapViewOfFile(hMapFile, FILE_MAP_COPY, 0, 0, 0);
dwSize = GetFileSize(hFile, &dwSizeHigh);
// check the DOS headers
pdosHeader = (IMAGE_DOS_HEADER*) pModule;
if (pdosHeader->e_magic != IMAGE_DOS_SIGNATURE ||
pdosHeader->e_lfanew <= 0 ||
dwSize <= pdosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS32)) {
// Error logic here
}
// check the NT headers
pNtHeaders = (IMAGE_NT_HEADERS32*) ((BYTE*)pModule + pdosHeader->e_lfanew);
if ((pNtHeaders->Signature != IMAGE_NT_SIGNATURE) ||
(pNtHeaders->FileHeader.SizeOfOptionalHeader !=
IMAGE_SIZEOF_NT_OPTIONAL32_HEADER) ||
(pNtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)) {
// Error logic here
}
// check the COR headers
pSectionHeader = (PIMAGE_SECTION_HEADER)
Cor_RtlImageRvaToVa(pNtHeaders, (PBYTE)pModule,
pNtHeaders->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_COMHEADER]
.VirtualAddress,
dwSize);
if (pSectionHeader == NULL) {
// Error logic here
}
// load the runtime and go
hRuntime = ::LoadLibrary(pRunTime);
_ _int32 (STDMETHODCALLTYPE * pCorExeMain2)(
PBYTE pUnmappedPE, // -> memory mapped code
DWORD cUnmappedPE, // Size of memory mapped code
LPWSTR pImageNameIn, // -> Executable Name
LPWSTR pLoadersFileName, // -> Loaders Name
LPWSTR pCmdLine); // -> Command Line
*((VOID**)&pCorExeMain2) = ::GetProcAddress(hRuntime, "_CorExeMain2");
nExitCode = (int)pCorExeMain2((PBYTE)pModule, dwSize,
pFileName, // -> Executable Name
NULL, // -> Loaders Name
pCmdLine); // -> Command LineFileIOPermission, the EnvironmentPermission, and the UIPermission. There is also support for code identity permissions based on strongname. Finally, there is very basic skeletal support for generic user identities and authorization, as well as role-based identities. To see how these are implemented and to learn about others, look in the
EEClass and its related MethodTable have been laid out, all of the type information necessary for compilation and most of the runtime structures necessary for execution are finished. At this point, the execution engine is ready to compile and execute the code for the type. But what is it that triggers JIT compilation?MethodTable will eventually contain pointers to the native functions that implement its method bodies, every SLOT is initially loaded with a thunk
that will trigger both JIT compilation and backpatching of the MethodTable when it is called. This tiny, method-specific piece of code is called a stubcall. With each SLOT holding a pointer to a stubcall, any call via the
jump instruction (which is not verifiable, and so we won't cover it here) and three flavors of the call opcode: call, calli, and callvirt. Each of these has slightly different semantics, and each can additionally be modified to be a tailcall
(which reuses the same activation record during recursive calls rather than profligately generating new records). The call instruction is nonvirtual, executing precisely the method targeted by the instruction, versus System.Reflection namespace, it also provides the ab