The compiler may generate some special classes for optimization. Some of these classes are generated by Roslyn (such as the classes for Lambda expressions or the state machines for asynchronous code). Others are generated by the X# compiler.
Below are some examples of these classes (that you can see if you open a X# compiler assembly with a tool such as IlSpy)
Class |
Purpose |
Xs$PSZLiteralsTable |
This class is generated by the compiler if you have code in your application that looks like this:
Since we cannot "know" at compile time what the lifetime of the PSZ must be we create a static field in this class and assign the generated PSZ value (a value type) to this field. As a result this PSZ will be "alive" during the whole lifetime of your application. If you know that the PSZ will not be needed after the call to the WIN32 api then you are better off replacing the PSZ(_CAST with a String2Psz(). This will ensure that the PSZ value is destroyed when the function that creates it finishes. FUNCTION TestMe() AS VOID generates the following public unsafe static void TestMe() and the following PSZ table: internal static class Xs$PSZLiteralsTable As you can see the PSZ value is stored in the table. Please note that every PSZ variable contains a pointer to static memory allocated with the String2Mem function in the runtime. So these static memory blocks are allocated for the whole lifetime of your application.
If you change the code to use String2Psz() instead FUNCTION TestMe() AS VOID then the result will be:
public unsafe static void TestMe() as you can see the compiler has now generated a local variable (a list of IntPtr) which is passed to a runtime function at the end that takes care of deleting the allocated memory when the function finishes. To ensure that a try .. finally was added. |
Xs$SymbolTable |
This class is generated by the compiler if you are using literal symbols in your code. For each symbol in your app there will be a field in the class. Inside the System classes there are 21 literal symbols as you can see when you decompile its code: internal static class Xs$SymbolTable { This is very similar to the way how symbols are handed in Visual Objects. |
<AssemblyName>.Functions |
Dotnet does not know the concept of functions or global variables. The X# compiler is therefore creating a static class in each assembly that contains static methods for each of the functions or procedures in your code. The name of this class is derived from the name of your output assembly: MyFile.DLL will contain a class MyFile.Functions MyFile.EXE will contain a class MyFile.Exe.Functions If your output assembly name contains embedded dots then these dots will be replaced with underscore characters in the functions class name: MyApp.Main.EXE will contain a classname MyApp_Main.EXE.Functions |
Functions$<ModuleName>$ |
Whenever your code uses STATIC FUNCTION, STATIC DEFINE, STATIC GLOBAL (whose visibility is within the same file only) then the compiler generates a separate class for each modulde (PRG file) where the name of the PRG file is used for the <Modulename>, so the file Start.Prg in Application1.exe will result in a class name Application1.Exe.Functions$Start$ |
$PCall$<FunctionName>$<suffix> |
If your code contains PCALL() constructs then the compiler will generate a special delegate with a name based on the method/function name and will make this delegate a nested object inside the type where the PCALL() is used. So a PCALL() inside a function will result in a nested delegate inside the Functions class and a PCALL() in a method of the Window class will result in a nested delegate inside the Windows class. The return type and parameter names and types of the delegates are derived from the function declaration for the typed pointer that you are passing to PCall(). IF !PCALL(gpfnInitCommonControlsEx, @icex) The resulting code looks like this:
if (!$PCallGetDelegate<$PCall$__InitFunctionPointer$28>(gpfnInitCommonControlsEx)(&icex))
The $PCallGetDelegate function is a special compiler generated function that looks like this: [CompilerGenerated] In short: it takes a function pointer (p) and Gets a delegate of type T. This delegate is then used to call the API function. Please don't worry if you don't get this. It took us a while to create this ourselves too !
|
$PCallNative$<FunctionName>$<suffix> |
This is a delegate generated for PCallNative constructs. The return type is the type of the generic argument and the parameter types are derived from the types of the arguments. The parameter names are $param1, $param2 etc. So the following code inside a Test function LOCAL p AS IntPtr Will generate a delegate like this: [CompilerGenerated] |
Functions.$Init1 Functions.$Init3 Functions.$Exit |
These special methods inside the function class are generated to call Init and Exit procedures. See the topic about startup code for more information about this |
<Module>.RunInitProcs() |
This special method inside the <Module> class is generated by the compiler and will be called at runtime when you are dynamically loading assemblies using the XSharpLoadLibrary() function. This takes care of calling all init procedures when a DLL is loaded dynamically. |
<>ClassName |
Special classes that start with a <> prefix are generated by the Roslyn compiler for lambda expressions and codeblocks. If you look at the VORDDClasses assembly you will find many examples of these. You may have to set ILSpy to show IL instead of C# or XSharp code, because otherwise these classes will be hidden by the tool. If you look at the RDD classes in C# mode it will looks like this:
If you switch ILSpy to IL mode it looks like this:
As you can see there are now quite some nested classes inside the DbServer class. The <>c class contains codeblocks that do not need to access local variables from functions or methods. In the DbServer class this class has some 25 methods, each of which is a codeblock. The classes with the name <>c_DisplayClass<nn> contain codeblocks that need access to local variables from the function or method where they are defined. The compiler has detected this and has moved the local variables out of the function/method and made them fields in a compiler generated class, so the codeblocks can access them. In Clipper and VO these were called "detached locals".
For example DisplayClass56_0 has the variables for the Average function: .class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass56_0' The codeblocks inside the Average method apparently access 5 locals variables (iCount, acbExpr, aResults, cbKey and uValue).
If you look inside the Average() method of DbServer you will see a codeblock such as
If you look in the decompiled code for Average() (in C# mode) you will see something like this: __DbServerEval(new The whole {} after the new is an anonymous codeblock expression The CB$Src field in this expression includes the source for the codeblock so at runtime you will be able to see the source of the compiler time codeblock (this was introduced in build 2.3.0)
The actual body of the codeblock (the part from iCount++ until return default(__Usual)) is in reality stored as a method of <>c__DisplayClass56_0. And all the variables that are needed inside this codeblock are not really stored as variables inside Average() but they are stored as fields of <>c__DisplayClass56_0.
Please don't worry if you don't get this. It took us a while to understand and create this ourselves too ! |
Xs$Args |
Whenever your code contains functions or methods with the so called CLIPPER calling convention, then X# compiler will create code that handles the parameters in a special way: For example the function Str() in the runtime. This is declared with the following parameters:
The compiler sees this as CLIPPER calling convention because all 3 of the parameters are optional.
The C# version of the IL code generated for this function is:
[ClipperCallingConvention(new string[] { "nNumber", "nLength", "nDecimals" })] As you can see the function now has a single argument, an array of usuals. Inside the body of the generated method the compiler now declares a variable that has the length of the array (the number of arguments passed, which you can also request at runtime with PCount()). The compiler also generates a local variable with the same name as the parameter and initializes each variable with either the value passed (0 based array elements) or with NIL. In the body of the method you will see a try finally. In the finally clause there is the following code: finally The reason for this code is that somewhere inside Str() nLength has been assigned. Str() does not know if the variable was passed by value or by reference. If the value was passed by reference then the array element inside Xs$Args must be updated, which is exactly what happens here. The code that calls Str() is now responsible for assigning back the value from the array to its local, when that value is passed by reference
|