Mixing the Managed and the Native Type System
Using the step-by-step approach explained in Chapter 7, you can inject managed code parts into a C project. Due to the source code compatibility, you can easily call from native code to managed code by just declaring the functions you want to call. However, in the samples discussed so far, the managed functions called from native code and the native functions called from managed code have had void as a return type, and no arguments. For nontrivial scenarios, functions with less simplistic...
Virtual Functions
Extending a base class with virtual functions can problems are in the nature of public inheritance for virtual functions leaves room for ambiguities. exists in C code cause even more problems. Some of these other problems exist because the C syntax The following code shows an ambiguity that If you see just this class declaration, you cannot say whether the function f is a virtual function or not. If Base defines or inherits a virtual function f with the same and signature as Derived f, then...
CRT Initialization in clr DLLs
So far I have discussed only the initialization of the CLR. When DLLs are used, the CRT is at least as important. In contrast to the CLR, the CRT cannot be delay-loaded, because native code may depend on it. For example, in the preceding code, there is a global variable used to hold the interface implementation in Lib3.cpp Interface impl as a global variable InterfaceImpl impl An address to this variable is returned by the exported function GetInterface, which is a native function. Without the...
Type Initialization
Since static initonly fields are resolved when the code that uses the field is JIT-compiled, it is not necessary to specify the value of a static initonly field at build time. However, the value of a static initonly field must be determined before the JIT compiler resolves the field. To achieve this, .NET comes with a new construct called a type initializer. According to the CLI specification, a type initializer is called by the runtime at or before first access of any static field of that...
vtentry 1 1 1
Figure 9-8 shows the opposite direction of managed-unmanaged transitions and virtual function calls. Here, a managed caller main invokes a native virtual function F2 . compiled w o clr include SampleClass.h SampleClass p p new SampleClass p- gt F2 compiled without clr include SampleClass.h Figure 9-8. Calling managed virtual functions from native code To call the native virtual function F2, the managed caller uses the IL instructions for virtual memory access discussed in Chapter 8 to determine...
CRT Initialization in clr[pure Assemblies
The CRT has been extended so that it can also be used from managed code. This is essential for extending existing applications with managed code. For mixed-code as well as native applications, the CRT provides many more features and services than many programmers think of. Here are some of them as follows The CRT implements the heap malloc free . The CRT implements the C free store new delete . This is usually done in terms of the heap. The CRT supports throwing and catching C exceptions. The...
Acknowledgments
Writing this book was only possible because I got a lot of help from people that deserve to be named here. From the team at Apress I would like to thank Elizabeth Seymour, Lori Bring, Jim Huddleston, and Damon Larson. I would also like to thank Stan Lippman, the technical reviewer of my book. His feedback constantly pushed me to make the book better. Without his feedback, the book would have been less correct and much less readable. I also got valuable reviews from Richard Dutton. Several...
General Hints Regarding gcroot and autogcroot
There are a few subtle pitfalls that you should know about when using the gcroot and auto_gcroot templates. As discussed already, in many cases you can use a gcroot or auto_gcroot variable as if it were of the wrapped tracking handle type. However, in a few cases, this is not possible. For example, you cannot invoke a default indexed property via a gcroot or auto_gcroot variable. The following code shows an example List lt int gt A ints GetIntListFromSomeWhere Console WriteLine ints 0 legal...
OutOfMemoryException
Another asynchronous exception that needs attention is System OutOfMemoryException. At first, it seems that an OutOfMemroyException is not an asynchronous exception at all, because according to the official MSDN documentation, an OutOfMemoryException can be thrown by the IL instructions newobj, newarr, and box. It is obvious that a gcnew operation which is translated to the IL instructions newobj or newarr can result in an OutOfMemoryException. Boxing can also cause an OutOfMemoryException...
Avoid pragma unmanaged
Visual C allows you to change the compilation model even within a single file. If you compile with clr, you can use pragma unmanaged to specify that the methods following that directive should be compiled to native code. Consequently, pragma managed marks the beginning of a section of functions compiled to managed code. void fManaged managed compilation is the default if clr is used pragma unmanaged can only be used if you compile with clr In some other code samples, you may also find the...
Passing Managed Memory to a Native Function
Native pointers should not refer into the managed heap, because a managed object that a native pointer would refer to could be relocated. So far, I have discussed two concepts for referring into the managed heap. These are tracking handles and tracking references that refer to a managed object's header, as well as interior pointers that refer to a part of the managed object's state. The runtime is able to automatically update all tracking handles and interior pointers. However, the runtime is...
Custom Startup Logic and LoadTime Deadlocks
DllMain in native as well as mixed-code DLLs must be implemented with special care. If you do not follow certain rules, a deadlock can occur while a DLL is being loaded. DllMain is called either directly or indirectly via the DLL's PE entry point. Before calling the PE entry point of a DLL, the operating system's image loader acquires a special critical section with the famous name loader lock. This loader lock can affect the PE entry point itself as well as all functions that are directly or...
Defining Custom Events
Instead of waiting for a key from the console, the FileDumper can do a callback to leave it up to the client what should happen when a full page has been dumped to the console. To do this, the FileDumper component class can provide a PageDumped event. A client can implement the event handler to wait for a keystroke like before, but it would also be an option to display a message box instead. It would even be possible to not handle the event. In this case, one page after the other would be...
Step 2 Creating a Second Precompiled Header
Before starting to write code that uses managed types and constructs, I recommend further preparation steps. If your project has a precompiled header typically called stdafx.pch , you will not be able to use that one for files compiled to managed code. A source file can only use a precompiled header that was created with the same compilation model. Since stdafx.pch was created without any of the clr switches, it can only be used by files compiled to native code. To create a second precompiled...
Assemblies and Metadata
.NET assemblies can be defined as byte streams containing metadata, managed code, and managed resources.1 The format of the byte stream is an extension of the PE file format the file format of DLL and EXE files on Windows NT and later Windows operating systems . In fact, most assemblies are stored as files however, it is also possible to store assemblies in other locations. As an example, assemblies implementing stored procedures can be stored in SQL Server 2005 databases. All managed services...
autohandle
When the implicitly dereferenced syntax is used, the compiler automatically generates code for creating and disposing an instance. Such a strict coupling of variable declaration and object creation is not always desired. If you want to provide deterministic disposal for an object that is not instantiated by you, but passed to you by foreign code as a return value of a function or in any other way, the implicitly dereferenced syntax cannot be used. The following expression cannot be compiled...
Step 3 Building and Testing
Your project is now configured to produce a mixed-code assembly. If you build a mixed-code EXE file and the linker property Configuration Properties gt Linker gt General gt Register Output is set to true, your project will fail with the following error message RegAsm error RA0000 Attempt to load an unverifiable executable with fixups IAT with more than 2 sections or a TLS section. Exception from HRESULT 0x80131019 Project error PRJ0050 Failed to register output. Please ensure you have the...
Event Handler Delegates
Before an event can be defined, you must either choose an existing event handler delegate or define a new one. The choice of an event handler delegate depends on the information that should be passed with the callback. If no special information is passed, the delegate System EventHandler can be used public delegate void EventHandler ObjectA sender, EventArgsA ea The type EventArgs does not have instance-specific properties or fields. Therefore, no information is passed with an EventArgs object....
Reducing the Overhead of gcroot and autogcroot
Operations based on gcroot or auto_gcroot variables are slower than operations based on a tracking handle. For a detailed description of the overhead that is caused by each gcroot or auto_gcroot instance, as well as for each usage of operator- gt , read the following sidebar, entitled How Is gcroot Implemented To minimize the overhead caused by gcroot and auto_gcroot, there are two optimization strategies as follows Avoid defining gcroot variables whenever possible. There are many situations in...
Interaction Between Managed and Unmanaged Code
Linking native code and managed code into one assembly is only useful if native code can call managed code and vice versa. Here is an example that shows how easy this is. Assume you have a file like this one compile with CL c EHs MD UnmanagedCode.cpp include lt iostream gt using namespace std cout lt lt Hello again from unmanaged code. n lt lt endl If you compile this file with the command mentioned in the comment, you will get an unmanaged object file named UnmanagedCode.obj. Obviously,...
Assembly Identity
The preceding output shows that three assemblies have been loaded. All assemblies have an identity FullName , which consists of the following four parts Simple name The simple name is the name of the assembly without the file extension. In theory, it is possible to have assemblies with names that differ from the file name, but this aspect is ignored here. Version .NET assemblies have a built-in support for versioning. Therefore, the second part of an assembly name is the version number. It...
Initialization of Global and Static Variables
The Win32 SDK documentation for DllMain contains another important statement If your DLL is linked with the C runtime library CRT , the entry point provided by the CRT calls the constructors and destructors for global and static C objects. Therefore, these restrictions for DllMain also apply to constructors and destructors and any code that is called from them. This statement is especially important if you implement mixed-code DLLs. Constructors of native types can be executed...
Using C Structures in Managed Code
To properly use native structures and classes in managed code, the C CLI compiler generates managed proxy types. As an example, consider the following function from the Win32 API The return type HWND is defined by the Win32 API as a pointer to a structure named HWND_ The structure HWND_is defined as follows To use the return type HWND of the function GetDesktopWindow in managed code, the C CLI compiler generates a managed proxy type .class private sequential ansi sealed beforefieldinit HWND_...
LoadTime Dependencies to Other DLLs
Since assemblies built with clr pure do not have native code, dependent DLLs are loaded differently. This difference is another aspect that must be considered when choosing a compilation model. To get some more information about this difference, consider the following simple application compile with CL clr DependentDLLs.cpp or with CL clr pure DependentDLLs.cpp by including windows.h, the function Beep is declared include lt windows.h gt this line adds kernel32.lib the import lib for...
Mapping SEH Exceptions to NET Exceptions
Win32 SEH exceptions can also be caught as .NET exceptions. In the following code, a managed function main calls a native function f , which throws the SEH exception EXCEPTION_INT_DIVIDE_BY_ZERO. In main, this exception is caught in a catch block that handles exceptions of type System ExceptionA. compile with cl clr ExceptionHandling2.cpp As I will discuss later, pargma managed is not recommended it is only used to show exceptions thrown across managed unmanaged boundaries without using two...
Converting Between Managed and Native Strings
C programmers often have to deal with a variety of string types. From its C roots, an array of char and wchar_t is often treated as a null-terminated string. The C standard library provides the type std string for convenient string handling. Many other libraries also have string implementations. For example, the MFC and ATL use the type CString. Last but not least, COM has its own string definition, the BSTR. When you write interoperability code, you often have to convert between native strings...
gcnew FileSystemEventHandlerthis ChangedFileDumperOnChanged error C3364 invalid
void OnChanged ObjectA sender, FileSystemEventArgsA e StreamReader sr name Console WriteLine sr.ReadToEnd The event handler must match the requirements of delegate target functions. Unfortunately, ChangedFileDumper OnChanged as well as all member functions of native classes cannot be used as a delegate target. Only global functions and static and non-static methods of managed types can act as delegate targets. To solve this problem, you could create a managed proxy class that provides a target...
Default Arguments Are Not Supported
A few features that are supported for member functions of C classes are not supported for methods in the CTS. For example, you cannot define a method with default parameters. The following code is illegal void f int i 10, int j 20 To mimic the behavior of default parameters, you have to implement separate functions that forward to the real implementation
Why Should You Use clrpure at All
After all these arguments against clr pure, you probably want to ask the question, Why should clr pure be used at all Well, there are two special restrictions of mixed-code assemblies that can be bypassed with clr pure. These restrictions are as follows Mixed-code assemblies must be stored in files. Mixed-code EXE files cannot be loaded dynamically into a process. At first glance, both restrictions sound strange. It seems to be obvious that all assemblies are stored in files. However, it is in...
size 41
custom attributes elided for clarity here end of class In native C , these string literals are internally placed into a data section. To access this data via a value type, the C CLI compiler generates metadata for global variables that map to the native string literal data. This is shown in the following ILDASM excerpt valuetype ' A0x567a3bdb.unnamed-global-0' at D_00003120 valuetype ' A0x567a3bdb.unnamed-global-1' at D_0000314C
Components
Visual C , as well as other .NET languages integrated into the Visual Studio .NET IDE, have a sophisticated support for software development based on has-a relationships. Using designers, you can easily add a new field to a class by dragging an item from the Toolbox window which contains a palette of available components and dropping it onto the designer. In this context, Visual Studio and the FCL use the misleading term component. For most developers, a component is a deployable unit of...
Compilation Models
So far, I have discussed the following two major features, sometimes summarized as C CLI interoperability Existing C source code can be compiled to managed code source code compatibility . Native code and managed code can be linked into a mixed-code assembly object file compatibility . Compared to the interoperability features that other .NET languages provide, C CLI interoperability is much more powerful. It is a significant simplification for interoperating with native code, and it enables...
DLLs with Managed Entry Points
You can also factor managed code out into a separate DLL so that your existing projects remain completely unmanaged. Figure 1-4 shows a simple example of such a scenario. extern C declspec dllexport voidfO extern C delspec dllimport void fQ C gt LINK.EXE TheApp.obj TheLib.lib Figure 1-4. Separating managed code in DLLs In this simple scenario, TheApp.cpp shall represent your existing project. To extend it with managed features, a new DLL is created from the source file TheLib.cpp. Notice that...
DLL Startup
Often, the majority of the executed code is not written in the application itself, but in various DLLs that the application loads. There are significant differences between application startup and DLL startup. When a mixed-code EXE file is loaded to start an application, the CLR is automatically initialized. In mixed-code DLLs, this can be different. Mixed-code DLLs can be used to delay-load the CLR. This means that the CLR is initialized only when managed code is executed. In addition to that,...
Object File Compatibility
Partial migration obviously depends heavily on source code compatibility. Once existing C source code is compiled to managed code, you can straightforwardly and seamlessly integrate other .NET components and benefit from the many features the .NET Framework offers. However, there is a second pillar that you must understand to use C CLI efficiently. I like to refer to this feature as object file compatibility. Like source code compatibility, the object file compatibility feature of C CLI has an...
The Managed Entry Point
In addition to the PE entry point, which is always mscoree _CorExeMain, an EXE assembly has a managed entry point. In contrast to the PE entry point, the managed entry point is a managed function. After the module constructor has initialized the assembly, the managed entry point is executed. Technically, the managed entry point is a managed function with the IL metadata .entrypoint, as shown in the following sample compileWith ILASM HelloWorld.il .assembly HelloWorld .assembly extern mscorlib...
Referencing Assemblies
Like native libraries and COM servers, assemblies are used to share code. Allowing your C CLI code to use another assembly requires certain steps that differ from the way native libraries and COM servers are made available to your projects. To understand these differences and the reasons behind them, it makes sense to take a step back and look at the way the compiler is enabled to call foreign code in the old ways of code sharing. Using a native library requires including the library's header...
StackOverflowException
The second asynchronous exception that is important for reliable resource management is System StackOverflowException. The managed stack in the CLR is heavily based on the native stack. Elements pushed on the managed stack exist either on the native stack or in processor registers. A System StackOverflowException occurs as a result of a native stack overflow exception, which is a Win32 SEH exception with the exception code EXCEPTION_STACK_OVERFLOW 0xC00000FD . A stack overflow exception can be...
Handling Exceptions Across ManagedUnmanaged Boundaries
When you mix native and managed code, you often face the situation that an exception thrown in native code must be handled in managed code and vice versa. In native code, there are two exception models C exception handling and Win32 SEH. In mixed code, you also have to care about managed exceptions. The exception handling architecture in .NET has remarkable similarities to the Win32 SEH model. This enables managed code to catch native C exceptions as well as SEH exceptions. In addition to these...
Linking the CRT in MixedCode Assemblies
As discussed in Chapter 7, only the DLL variant of the CRT can be used to produce mixed-code assemblies. In the following sections, I will explain how the compiler and the linker work together to integrate the CRT into an assembly. Depending on the compilation model and CRT variant multithreaded DLL MD or multithreaded debug DLL MDd , the compiler automatically ensures that certain linker options are effective. To achieve this, the compiler uses linker directives. Linker directives can be used...
Internals of the Delegate Map
The preceding code shows the simple steps necessary to handle events in native types. If you extend existing code with .NET features, you will likely use the delegate map quite often. To understand how the delegate map is implemented, it is necessary to see what these macros expand to. This is shown in the following code Created by delegate_proxy_factory lt ChangedFileDumper gt m_delegate_map_proxy ChangedFileDumper m_p_native_target public pNativeTarget m_p_native_target pNativeTarget void...
Catching Managed Exceptions in Native Code
Even though it is possible to catch managed exceptions in native code, it is seldom useful. Since native code cannot use any managed types, your exception handler cannot get information about the managed exception that it catches. Managed exceptions are caught as Win32 SEH exceptions with the exception code 0xE0434F4D. The following code catches a System Exception in native code compile with cl clr ExceptionHandling3.cpp include lt excpt.h gt include lt windows.h gt include lt iostream gt using...
SafeHandle
There is a helper class called SafeHandle that allows you to benefit from all the reliability features discussed so far. SafeHandle is an abstract class that can be used to write a wrapper class. The following code shows how you can modify the XYZHandle class from the previous examples build with CL LD clr ManagedWrapper4.cpp MT outputresource ManagedWrapper4.dll 2 continued in next line manifest ManagedWrapper4.dll.manifest pragma comment lib, XYZLib.lib using namespace System using namespace...
Calling Local Native Functions from Managed Code
The first scenario that I will discuss is a call of a native function that is invoked by a managed function from the same assembly. Figure 9-3 shows this scenario. Figure 9-3. Managed code calling local native code Figure 9-3. Managed code calling local native code To call fNative in main, the compiler emits a CALL instruction as if a managed function were called .method assembly static int32 main cil managed There is a managed function in the metadata of the assembly to allow such a CALL...
Calling C Classes Across ManagedUnmanaged Boundaries
Developers new to C CLI sometimes confuse managed classes and native classes whose member functions are compiled to managed code. Even when all member functions of a C class are compiled to managed code, the resulting class is still a C type, not a managed type. However, compiling all member functions of a native class to managed code can imply a native-managed transition if native code calls a member function of the native class. The application shown in Figure 9-4 gives an example SampleClass...
Calling Native Functions Imported from DLLs
P Invoke metadata is also generated if you call a native function that is imported from a DLL. The following code shows an example of this scenario compile with CL clr AutoPInvoke.cpp or with CL clr pure AutoPInvoke.cpp by including windows.h, the function Beep is declared include lt windows.h gt this line adds the import lib for kernel32.dll to the list of like inputs pragma comment lib, kernel32.lib When you compile the AutoPInvoke sample shown previously with clr, the resulting P Invoke...
vtentry 2 1
call void Both functions in this ILDASM output have a line starting with .vtentry. This .vtentry instruction is used to define data structures that are needed for method calls from native code to managed code. The term vtentry stands for vtable entry, because these data structures, like vtables for C classes, are arrays of function pointers. In further explanations, I will refer to these data structures as interoperability vtables. For each of the global functions fManaged and fManaged2, a...
Managed Callers Only
The following code shows another special case. Even though the function fManaged has a native calling convention, it is not called by native functions build with cl clr OnlyManagedCallers.cpp using namespace System Console WriteLine fManaged called When the compiler can be sure that a managed function with a native calling convention is called only from managed code, there is no need for interoperability metadata. Therefore, in the preceding code, the compiler and the linker do not emit vtfixup...
Virtual Functions and Double Thunking
The double-thunking problem described in the context of native function pointers can also occur when managed virtual functions are called from managed code. Figure 9-9 shows an example. compiled with clr include SampleClass.h SampleClass p p new SampleClass p- gt F2 compiled with clr include SampleClass.h Figure 9-9. Calling managed virtual functions from native code In this sample, both main and the virtual functions of SampleClass are compiled to managed code. Since native callers could also...
[assembly CLSComplianttrue
public types are CLS-compliant unless otherwise stated public ref class SampleCipher sealed SampleCipher is CLS-compliant because of assembly level attribute M2 is marked as not CLS-compliant, because it has an argument of a not CLS-compliant type CLSCompliant false void M2 unsigned int Unfortunately, the C CLI compiler does not emit warnings when a type or a function is marked as CLS-compliant even if it does not conform to one or more of the CLS rules. To decide whether you should mark a...









