Mixed managed and unmanaged programming in C#

Implement your more important algorithm in an unmanaged module, and then use the CLR platform interoperability to make the managed code call it, so that the program can still work normally, but it is very difficult to decompile the unmanaged native code.

The most direct way to implement managed and unmanaged programming is to use C++/CLI

introduce

Project archiving has long been an adopted practice in businesses, and it turns out they were right, too! For a programmer, this is thousands of men-days of work. Why not develop a small piece of code to reuse that code, project.
Now provides a new technology that is gradually turning to C#: using managed and unmanaged mixed programming. This is a viable solution in top-down issue (from UI to low-level layers) or bottom-up (from low-level to UI) cases.
The purpose of this article is to illustrate how to use these two technologies together through two simple examples:
* Call managed code in unmanaged.
* Call unmanaged code in managed.

Calling a managed function from unmanaged code

Write picture description here
This example mainly shows how to call a class implemented in managed (C#) code in unmanaged code (C++), and implement a "mixed code" DLL through managed code to export the API.

single unmanaged code

The following is a console program

#include "stdafx.h"
#include <iostream>

using namespace std;

#ifdef _UNICODE
   #define cout wcout
   #define cint wcin
#endif

int _tmain(int argc, TCHAR* argv[])
{
   UNREFERENCED_PARAMETER(argc);
   UNREFERENCED_PARAMETER(argv);

   SYSTEMTIME st = {0};
   const TCHAR* pszName = _T("John SMITH");

   st.wYear = 1975;
   st.wMonth = 8;
   st.wDay = 15;

   CPerson person(pszName, &st);

   cout << pszName << _T(" born ") 
        << person.get_BirthDateStr().c_str()
        << _T(" age is ") << person.get_Age() 
        << _T(" years old today.")
        << endl;
   cout << _T("Press ENTER to terminate...");
   cin.get();

#ifdef _DEBUG
   _CrtDumpMemoryLeaks();
#endif

   return 0;
}

There's nothing special about this code, it's just plain unmanaged code.

single managed code

This is a typical assembler implemented in C#

using System;

namespace AdR.Samples.NativeCallingCLR.ClrAssembly
{
   public class Person
   {
      private string _name;
      private DateTime _birthDate;

      public Person(string name, DateTime birthDate)
      {
         this._name = name;
         this._birthDate = birthDate;
      }

      public uint Age
      {
         get
         {
            DateTime now = DateTime.Now;
            int age = now.Year - this._birthDate.Year;

            if ((this._birthDate.Month > now.Month) ||
                ((this._birthDate.Month == now.Month) &&
                 (this._birthDate.Day > now.Day)))
            {
               --age;
            }

            return (uint)age;
         }
      }

      public string BirthDateStr
      {
         get
         {
            return this._birthDate.ToShortDateString();
         }
      }

      public DateTime BirthDate
      {
         get
         {
            return this._birthDate;
         }
      }
   }
}

As you can see, this is a single CLR

Managed and unmanaged mixed programming part

This part is the most important and also the hardest. The VisualStudio environment provides some header files to help developers link these keywords.

#include <vcclr.h>

However, it doesn't end here. We also need to be careful about some of the pitfalls involved, especially the transfer of data between CLR (managed code) and native (unmanaged code) keywords.
The following is a class header file that outputs a managed section

#pragma once 

#ifdef NATIVEDLL_EXPORTS
   #define NATIVEDLL_API __declspec(dllexport)
#else
   #define NATIVEDLL_API __declspec(dllimport)
#endif

#include <string>

using namespace std;

#ifdef _UNICODE
   typedef wstring tstring;
#else
   typedef string tstring;
#endif


class NATIVEDLL_API CPerson
{
public:
   // Initialization
   CPerson(LPCTSTR pszName, const SYSTEMTIME* birthDate);
   virtual ~CPerson();

   // Accessors
   unsigned int get_Age() const;
   tstring get_BirthDateStr() const;
   SYSTEMTIME get_BirthDate() const;

private:
   // Embedded wrapper of an instance of a CLR class
   // Goal: completely hide CLR to pure unmanaged C/C++ code
   void* m_pPersonClr;
};

Emphasize one point, try to ensure that there are only unmanaged codes in the header files, mixed programming is implemented in cpp, and data transmission. For example: You should try to avoid using the functions in vcclr.h for mixed programming. That's why a void pointer is defined to wrap the CLR object.
A magical door just opened. . .
As I said, the magic starts with including a vcclr.h header file. However, you need to use the CLR coding language and use some complex types (eg: strings, array, etc):

using namespace System;
using namespace Runtime::InteropServices;
using namespace AdR::Samples::NativeCallingCLR::ClrAssembly;

Of course, some native assemblers to use need to be declared.
First, let's look at the constructor of this class:

CPerson::CPerson(LPCTSTR pszName, const SYSTEMTIME* birthDate)
{
   DateTime^ dateTime = gcnew DateTime((int)birthDate->wYear,
                                       (int)birthDate->wMonth,
                                       (int)birthDate->wDay);
   String^ str    = gcnew String(pszName);
   Person^ person = gcnew Person(str, *dateTime);
   // Managed type conversion into unmanaged pointer is not
   // allowed unless we use "gcroot<>" wrapper.
   gcroot<Person^> *pp = new gcroot<Person^>(person);
   this->m_pPersonClr = static_cast<void*>(pp);
}

It is allowed to use a pointer to a managed class in unmanaged code, but we don't want to directly expose a managed API to the user.
Therefore, we use a void pointer to encapsulate this object, and a new problem arises: we are not allowed to directly use unmanaged pointers to point to managed types. That's why we use the gcroot<> template class.
Note how the ^ character is required when using pointers to managed code; this means we use a reference pointer to a managed class. Remember, class objects are considered references in .NET when used as function members.
Also note that there is a keyword for automatic memory allocation in .NET: gcnew. This means that we allocate space in a garbage collector-protected environment, not in the process heap.
Sometimes you need to be careful: the process heap and the garbage collector protection environment are completely different. We'll see that some encapsulation tasks still have to be done: In the class's destructor:

CPerson::~CPerson()
{
   if (this->m_pPersonClr)
   {
      // Get the CLR handle wrapper
      gcroot<Person^> *pp =  static_cast<gcroot<Person^>*>(this->m_pPersonClr);
      // Delete the wrapper; this will release the underlying CLR instance
      delete pp;
      // Set to null
      this->m_pPersonClr = 0;
   }
}

We use standard C++ casts to static_case. Deleting the object frees the underlying CLR object, allowing it to be garbage collected.
Reminder: The reason for declaring a destructor is to implement the IDisposeable interface and its own Dispose() method.
Key: Don't forget to call Dispose() on the CPerson instance. Otherwise, it will cause a memory leak, just as in C++ it cannot be released (the destructor is not called).
Calling basic CLR class members is very easy, similar to above.

unsigned int CPerson::get_Age() const
{
   if (this->m_pPersonClr != 0)
   {
      // Get the CLR handle wrapper
      gcroot<Person^> *pp = static_cast<gcroot<Person^>*>(this->m_pPersonClr);
      // Get the attribute
      return ((Person^)*pp)->Age;
   }

   return 0;
}

However, when we have to return a complex type, it is a little troublesome, as in the following class members:

tstring CPerson::get_BirthDateStr() const
{
   tstring strAge;
   if (this->m_pPersonClr != 0)
   {
      // Get the CLR handle wrapper
      gcroot<Person^> *pp = static_cast<gcroot<Person^>*>(this->m_pPersonClr);

      // Convert to std::string
      // Note:
      // - Marshaling is mandatory
      // - Do not forget to get the string pointer...
      strAge = (const TCHAR*)Marshal::StringToHGlobalAuto(
                         ((Person^)*pp)->BirthDateStr
                        ).ToPointer();
   }

   return strAge;
}

We cannot directly return a System::String object to an unmanaged string. The following steps must be used:
1. Get the System::String object.
2. Use Marshal::StringToHGlobalAuto() to get a global handle. We use the "auto" version here to return a Unicode-encoded string. Then convert it to an ANSI-encoded string as much as possible;
3. Finally, get a pointer to a handle to a potential containing object.
The above 3 steps have achieved the replacement!
Read the recommended book on C++/CLI, you'll see that other special keywords like pin_ptr<> and interna_ptr<> allow you to get pointer hidden objects, read the documentation for more details.

big mix

This is a standard example showing how to create a native console application using MFC and the CLR!

Conclusion (unmanaged calling managed)

Calling managed in unmanaged is a complicated thing, this example is very basic and common. In the example, you can see some very complex considerations. I hope you can encounter more other scenarios and gain more experience in mixed programming in the future.

Managed call unmanaged

Write picture description here
This example shows how to call an unmanaged C++ class library in the CLR (C#), through an intermediary "mixed code" DLL, and export an API to use unmanaged code.

Unmanaged C++ DLL

DLL exports:
1. A C++ class
2. A C-style function
3. A C-style variable
This section introduces object declarations, although they are so simple that comments are not necessary.
C++ class

class NATIVEDLL_API CPerson {
public:
   // Initialization
   CPerson(LPCTSTR pszName, SYSTEMTIME birthDate);
   // Accessors
   unsigned int get_Age();

private:
   TCHAR m_sName[64];
   SYSTEMTIME m_birthDate;

   CPerson();
};

The get_Age() function simply calculates a time period from birth to the present.
export C function

int fnNativeDLL(void);

export C variable

int nNativeDLL;

.NET side

This classic case is not described in detail here.

Note 1:
.NET classes cannot directly inherit from unmanaged C++ classes. Write a managed C++ class embedded inside the C++ entity object.

Note 2:
Declaring a member CPerson_person2; will result in a C4368 compilation error (cannot define 'member' as a member of a managed type: mixed types are not supported)
which is why it is used internally (considered 'unsafe' in C#)
technical documentation That's what it says:
You can't embed an unmanaged data member directly into the CLR. However, you can declare a pointer to a localized type and control its lifetime in constructors, destructors, and free managed classes (see Visual C++ for more information on destructors and finalizers).
And this is the embedded object:

CPerson* _pPerson;

instead of:

CPerson person;

Special information in the constructor.
The public constructor has a System::String string (managed type) and a SYSTEMTIME structure (Win32 API type, but only numeric: obviously a data set).
This unmanaged c++ CPerson constructor uses A pointer of LPCTSTR string type, this managed string cannot be directly converted into an unmanaged object.
Here is the source code of the constructor:

SYSTEMTIME st = { (WORD)birthDate.Year,
                  (WORD)birthDate.Month,
                  (WORD)birthDate.DayOfWeek,
                  (WORD)birthDate.Day,
                  (WORD)birthDate.Hour,
                  (WORD)birthDate.Minute,
                  (WORD)birthDate.Second,
                  (WORD)birthDate.Millisecond };

// Pin 'name' memory before calling unmanaged code
pin_ptr<const TCHAR> psz = PtrToStringChars(name);

// Allocate the unmanaged object
_pPerson = new CPerson(psz, st);

Note that the pin_ptr keyword is used here to protect the string from being used in the CRL.
This is an internal pointer that can protect the object. It is necessary when passing the address of a managed class to an unmanaged function, because the address does not change abnormally when called from unmanaged code.

Summary (calling unmanaged in managed)

If we think that importing an unmanaged in managed is more common than importing a managed in unmanaged, writing an "intermediate assembly" is not easy.
You should determine whether you need to port all the code, that is unreasonable. Consider redesigning this app. Rewriting managed code may be more cost-effective than porting. Moreover, the final application architecture is also very clear.

Guess you like

Origin blog.csdn.net/ayang1986/article/details/121533504