📜 ⬆️ ⬇️

.NET in an unmanaged environment - use and generic problems

Managed code and the .NET Framework are absolutely wonderful things from the point of view of a programmer who needs to get the most stable working programs out of his nose. Using .NET allows you to greatly reduce the cost of developing, testing and maintaining software products, especially compared to C ++ or Delphi.

However, the managed code has one very serious birth injury, which directly results from its merits - it is initially incompatible with the unmanaged environment in which it is forced to work. Boxing, memory fields, the lack of direct addressing and other tricks designed to make life easier for the programmer, make the interaction of a managed and unmanaged code a problem.

However, there is no such problem that cannot be solved (even with the help of an ax and a scrap). Today we have a brief overview of the possibilities of organizing the interaction between managed and unmanaged code. Many C # and especially VB.NET programmers are afraid of this, but in reality there is nothing to worry about. We will start with the most primitive methods, which will be interesting only to beginners (therefore, experienced .NET wolves can skip the first part of the article with a clear conscience), and finish with a description of what to do if you want to write a program in .NET, but this is impossible ( and this also happens). Naturally, specific examples will be given for each case, perhaps the habrecheloveki will tell me about my own cycling. In parallel, I will say a few words about the pitfalls when working with VSTO and Windows Shell.

Work with unmanaged-code from managed.


Standard for this situation from the point of view of .NET are three mechanisms offered by Microsoft - this is platform invoke (P / Invoke), COM interop and unsafe.
')

General warnings


All methods of working with unmanaged-code have a set of common flaws, quite serious, which should be paid attention to and should be considered.
  1. Calling unmanaged code is a dangerous operation that requires determining the validity of this operation in the application manifest. This sometimes creates problems with deploing, especially with ClickOnce methods.
  2. Unmanaged code is not secure, does not pass the CLR checks and does not follow the rules of the embedded .NET security system. If you define a “deny access to files” security rule in the method and use the unmanaged function that works with files, it will work without problems.

Keeping in mind this information, we proceed to consider the methods of working with it.

P / Invoke


The procedure procedure invoke is intended for accessing functions contained in DLL files.

When is it necessary?


Despite the abundance of functions in the .NET Framework libraries, it is still impossible to do some things with it. Personally, I needed the P / Invoke ability to control the mandatory level for files and named pipes. The problem was that the .NET access control classes did not understand the SDDL strings that contain the mandatory level descriptors, and I had to access these functions directly.

What does this look like?


Simple enough. We declare a static class, in it we prescribe a static function with the name of the one to be exported, compile the appropriate list of parameters, hang the DllImport attribute with the name of the DLL and enjoy life.

Example: import PlaySound function

public static class Win32
{
[DllImport( "winmm" )]
public static extern int PlaySound( string szSound, IntPtr hModule, int flags);
}


* This source code was highlighted with Source Code Highlighter .


Main problems


  1. The transfer of various descriptors (handles). The problem is solved as follows. Most objects have a Handle field that can be passed to a function. If not, you can try to take the SafeHandle object and request IntPtr from it via Dangerous operation.
  2. Passing structures, function pointers, and other non-trivial things. Indeed, for a non-expert who does not understand how to marshall data, this is a very big problem. Fortunately, there is a site pinvoke.net/ , which contains the basic definitions for frequently used functions. The site is built on a wiki engine, and if you need a system function - by 99%, that you will find its correct definition there.


COM Interop


Really powerful mechanism that allows using COM components in managed code.

When is it necessary?


In serious projects - quite often. The fact is that certain parts of the system can be written (or already be written) in C ++, Delphi, or even (holy-holy) Visual Basic (the one that is not .NET). In addition, Win32 itself is built on COM, and therefore provides access to the functionality including through this mechanism. In order to use it without problems - you should use the component model and COM Interop.

What does this look like?


Simple enough. If your object supports automation, then the interop process will pass without problems, if not, you may have to suffer.

To add a COM object to the assembly, simply connect it via project References. A library that marshals the parameters from unmanaged to managed and back will be created automatically. Naturally, for complex objects, such as MAPI or AD, this interop will not work, in this case, you should use the approach I will write about later.

After you have connected the component, simply create a new instance of the object and use it as if it were a native .NET object.

IComExampleInterface iInt = new ComExampleInterface();

* This source code was highlighted with Source Code Highlighter .


No, we are not creating an interface object. In fact, this interface (as seen in the metadata) has an attribute called default coclass, and it is this object that will be created.
As you can see, it is very simple and not at all scary.

Main problems


  1. Errors in the interopa of complex objects. .NET is not all-powerful, and can not always correctly determine how certain data are marshalling. In this case, again we find ourselves in a situation where it is necessary to prescribe the marshall explicitly.
  2. Absence in .NET of many definitions of standard interfaces. For example, IUnknown, IDispatch, IDictionary, or IStream. These definitions can be written with your hands or ripped out using the Reflector from the core of the .NET kernel (they are there).
  3. Incompatibility of similarly named .NET and Win32 objects. For example, a Dictionary in .NET is not at all the same as a Win32 Scripting.Dictionary object. Similarly with IStream and others, so for successful and fruitful work you will have to write wrappers and export them. How? For example, creating wrappers and exporting them using ComExport methods, which will be discussed later.


Unsafe context.


Personally, it seems to me an unnecessary atavism, but this is my personal opinion.

When is it necessary?


Most often, the use of unsafe methods is motivated by the fact that such an approach reduces the execution time of the code, especially in the case of working with large amounts of data of simple types - in this case, the costs for boxing / unboxing become quite large. However, as practice shows, in reality, programmers working with .NET increasingly operate with complex objects like the DataSet, moreover, unsafe requires the definition in the application manifest of special powers to execute unsafe code. Personally, I never needed to use unsafe - even marshalling tasks of complex data types are easily performed using the Marshal static class using IntPtr.

What does this look like?


The unsafe keyword is added to the method description.

public static unsafe void UnsafeMethod( char * chararray)

* This source code was highlighted with Source Code Highlighter .


At the same time, in the method, the operations of taking the address and the mechanism of working with links become available, just as in C ++.
It should be remembered that the unsafe-code remains managed, the number of execution time checks is simply reduced. Thus, it does not quite relate to our topic, but it will not be superfluous to mention this way of working.

Main problems


  1. The appearance of many inherited unmanaged code problems, including the main one, is a buffer overflow.


Work with managed code from unmanaged.


If the work with unmanaged code is organized more or less well, then the reverse process - accessing .NET assemblies from unmanaged code creates a huge number of problems. To understand their essence, consider the mechanism proposed by Microsoft to implement such an approach.

This mechanism is called ComExport and allows you to export objects in a managed environment as if they were ordinary COM objects. To export an object, define the attribute ComVisible (true) for it, set the CLSID, ProgID, DefaultInterface, perform a similar operation for all exported interfaces. Especially important is the definition of CLSID and IID for all exported interfaces, as well as their versions, hard in the code - otherwise when developing an application, any interface change will lead to the definition of a new CLSID and IID, and the version will constantly jump.

[ComVisible( true )]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[ Guid ( "5095474B-5273-44ae-A220-9E3820D2EEDC" )]
[ProgId( "COM.Export.Test.1" )]
[ComDefaultInterface( typeof (IDefaultInterface))]
public class Coclass {...}


* This source code was highlighted with Source Code Highlighter .


However, if everything was so rosy - there would be no problems. The reality is much more sad and there are a number of problems that can make it difficult to use such an approach.
  1. COM written using .NET is not required to register using regsvr32, but using regasm, which makes the development of installers somewhat difficult.
  2. The created components need to be signed (at least with a temporary key), or registered in the GAC using gacutil. Registration with the GAC requires administrator privileges in Windows Vista.
  3. With the ComExport mechanism, Out-Process COM cannot be created.
  4. When using such components, the CLR is loaded into the address space of the process that invokes it.

Let us dwell on the last two points in more detail. Why can't out-process COM be created?

Speaking quite simply, the fact is that any .NET application is also an assembly, and allows you to connect yourself as a simple assembly DLL, without performing the one when it is directly registered in the main function. And if when writing out-process COM, for example, in C ++, the characteristic behavior of the system is to wait for the application to load, then wait until it registers the exported objects in ROT, and then access it with a request from the class factory, then in the case of .NET application does not start - the assembly simply fits into the current process as a DLL through an intermediary. Thus, even if you define a singleton or static class in the assembly, it will be different for each process, since the processes in win32 are isolated from each other.

As far as I know, DCOM can be used to solve this problem, but the creation of such components on .NET is associated with considerable difficulties, many other problems appear, and therefore I personally did not use this approach.

And what is the problem of loading CLR into the address space of the process, you ask? After all, .NET supports versioning of builds, and in any case, the version of mscorlib that corresponds to the library will be used.

Not so simple. The problems will start when you try to load COM written using the .NET Framework 1.1 in an application written in the .NET Framework 3.5. Two version of the same library can not simultaneously exist in the same address process. Most likely, everything will just fall.

This moment, in particular, directly prohibits writing such things as a shell extension or namespace extension using .NET. After all, the file saving or opening dialog box can be called from any code - unmanaged or managed, written using any version of the Framework.

In addition, in all honesty, ComExport is not the fastest mechanism. CLR loading is a long process. This is especially obvious in the case of developing add-in to Microsoft Office applications using Visual Studio Tools for Office. The tool, which was planned as a panacea for all development problems, got stuck in one simple problem - with the connected VSTO add-in, the application load time increases on average from 20 to 100 seconds. And if for MS Outlook it is not too critical, then for an increase in download time of Word or Excel, customers will chase you with pissing rags for a long time :)

Just remember - you can't implement shell extensions using .NET. Do not listen to Microsoft, which, in MSDN, doesn’t gently recommend it - it’s impossible. Never. Point.

There are two solutions to this problem:
  1. Using proxy objects that do not release their own CLR limits.
  2. Using custom transports for communication between managed and unmanaged code.

When I had the task of writing a namespace extension that would appeal to a server written in ASP.NET, which exported the API through the WCF mechanism, the problem for me was really acute. There was no desire to establish connections through sockets, to implement Windows authentication and Windows SOAP parsing, and Managed C ++ prohibited the use of the problems described above. Then the following architecture was born: namespace extension, written in C ++, which implements all the interfaces of the Windows Shell, and for data it calls the application running on the local (or remote) machine using the named pipe. From this approach, a common architecture of this kind of applications was born, called .NET Pipe RPC. The approach is really quite general, but I have already 5 pages in Word, so I’m finishing, and if Habracheloveks are interested in this topic, write down in the comments, and I will tell you in detail about this approach with examples of implementation.

Source: https://habr.com/ru/post/58031/


All Articles