Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#

Hosting .NET Core Components in Unmanaged C/C++ Process in Windows and Linux

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
4 Feb 2019CPOL7 min read 26.7K   1.7K   21   13
Compact infrastructure for custom hosting of .NET Core component in unmanaged C/C++ code with reciprocal methods calls between the parts running in Windows and Linux. Section Threads and Processes

Why?

When writing your first .NET Core application, the thing that strikes you is how the application is executing. Even .NET Core console application is built to a DLL file, not to EXE one. It is run with dotnet command providing a standard host for our DLL execution. The question may arise: would it be beneficial to create our custom host for .NET Core application execution? Apart from hypothetical optimization and performance enhancement at the moment, I see two benefits of the custom hosting.

The first one is protection of .NET Core binaries against reflection. As you may see, ordinary .NET Framework reflectors cannot reflect .NET Core DLL over. But it is relatively easy to write a dedicated reflector for .NET Core similarly to what Lutz Roeder did in the early days of .NET for the Framework binaries. .NET Core reflector would not be so popular since in a vast majority of the cases, .NET Core code is running in server side and therefore is not available for code finding reflection. But in cases when .NET Core DLLs are running on the client side, anti-reflection protection may be quite useful. In the simplest case, such a protection may be achieved with custom hosting as follows. Bytes in the DLL saved in disk are reshuffling according to some algorithm, and the custom host places the bytes in proper order in memory. Similar idea for .NET Framework for Windows was implemented in my article Anti-Reflector .NET Code Protection.

The second and IMHO more important benefit is connecting with upgrading of legacy software. Indeed, there are piles of old code around. Millions of lines written in ANSI C are built in both Windows and Linux and successfully serve customers to the day. And there is a trap with this code: huge resources and time are required to rewrite it, it is extremely difficult to find people to support these dinosaurs, and those creatures are still very much in use. Recently a friend of mine noted production C file written in 1993. This reminded me my childhood when in 1975 I attended a metallurgical plant where my father worked and saw there functioning steam engine on which was written "Birmingham 1895" (that was the last day of its service). But unlike the steam engine, the C file is still operational and its product is still in high demand. One of the promising ways to get out of the legacy code trap is to use new programming approaches / languages for development of new features and gradually and carefully rewrite the problematic old parts. This implies close and seamless collaboration between the old and new components. And custom .NET Core hosting can provide such a collaboration. Recently, I came across this article providing explanation and code sample for .NET Core custom hosting. Based on this code in this work, I try to present more general infrastructure for .NET Core custom hosting.

Compact software sample of this article is capable of hosting .NET Core component in unmanaged C/C++ code and ensure reciprocal methods calls between the parts. It runs in Windows and Linux alike.

Managed - Unmanaged Reciprocal Calls Mechanism

In order to host NET Core components, unmanaged C/C++ native application includes a C++ gateway class GatewayToManaged which header file GatewayToManaged.h is given below:

C++
#pragma once

#include "coreclrhost.h"

using namespace std;

// Function pointer types for the managed call and unmanaged callback
typedef bool (*unmanaged_callback_ptr)(const char* actionName, const char* jsonArgs);
typedef char* (*managed_direct_method_ptr)(const char* actionName, 
      const char* jsonArgs, unmanaged_callback_ptr unmanagedCallback);

class GatewayToManaged
{
public:
    GatewayToManaged();
    ~GatewayToManaged();

    bool Init(const char* path);
    char* Invoke(const char* funcName, const char* jsonArgs, unmanaged_callback_ptr unmanagedCallback);
    bool Close();

private:
    void* _hostHandle;
    unsigned int _domainId;
    managed_direct_method_ptr _managedDirectMethod;

    void BuildTpaList(const char* directory, const char* extension, string& tpaList);
    managed_direct_method_ptr CreateManagedDelegate();

#if WINDOWS
    HMODULE _coreClr;
#elif LINUX 
    void* _coreClr;
#endif
};

Its method Init() takes path to executing application and performs the following:

  • loads CoreCLR components,
  • constructs Trusted Platform Assemblies (TPA) list,
  • defines main CoreCLR properties,
  • starts the .NET Core runtime and creates the default (and only) AppDomain, and finally
  • creates an object managed_direct_method_ptr _managedDirectMethod permitting to call managed delegate.

In order to have uniform mechanism for any direct unmanaged-to-managed calls and for managed-to-unmanaged callbacks, we define one type (signature) for each case. Presented above header file GatewayToManaged.h provides those types: managed_direct_method_ptr for direct managed delegate and unmanaged_callback_ptr for unmanaged callback.

So, to call managed methods from unmanaged code, we need:

  • create object of class GatewayToManaged
  • call its method Init() and then
  • perform actual managed call with method Invoke() which takes name of managed function and its arguments as stringified JSON object, pointer to unmanaged callback and returns char*.

In the managed side component (DLL), GatewayLib contains public static class Gateway which provides method ManagedDirectMethod() shown below:

C++
// This method is called from unmanaged code
[return: MarshalAs(UnmanagedType.LPStr)]
public static string ManagedDirectMethod(
    [MarshalAs(UnmanagedType.LPStr)] string funcName,
    [MarshalAs(UnmanagedType.LPStr)] string jsonArgs,
    UnmanagedCallbackDelegate dlgUnmanagedCallback)
{
    _logger.Info($"ManagedDirectMethod(actionName: {funcName}, jsonArgs: {jsonArgs}");

    string strRet = null;
    if (_worker.Functions.TryGetValue(funcName, 
           out Func<string, string, UnmanagedCallbackDelegate, string> directCall))
    {
        try
        {
            strRet = directCall(funcName, jsonArgs, dlgUnmanagedCallback);
        }
        catch (Exception e)
        {
            strRet = $"ERROR in \"{funcName}\" invoke:{Environment.NewLine} {e}";
            _logger.Error(strRet);
        }
    }

    return strRet;
}

This method is called from unmanaged code, as it was shown above.

Method ManagedDirectMethod() activates actual method specified by unmanaged caller using object IWorker _worker. Interface IWorker provides dictionary getter Dictionary<string, Func<string, string, UnmanagedCallbackDelegate, string>> Functions { get; } Required unmanaged call handler is retrieved from the dictionary by funcName key and is called with directCall(). We assume that dictionary _worker.Functions is filled on construction of the object and is not changed later - so it is thread safe.

Argument dlgUnmanagedCallback is passed on to the handler and may be later used to asynchronous calls of unmanaged code. In our sample, processing of managed-to-unmanaged call is implemented with plain C function bool UnmanagedCallback(const char* actionName, const char* jsonArgs) in file callback.c . To keep things uniform, arguments for both sides calls are given as JSON strings. For parsing JSON in C code, I took this open source JSON parser.

With provided simple infrastructure, it will be easy to write code in both sides. Unmanaged part should create object of class GatewayToManaged and run its method Init(). After this, the gateway object is ready for invocation of managed methods. Unmanaged code should also provide appropriate callbacks if it expects they will be called by managed part. And managed part should simply implement methods to be called from its unmanaged host. Those methods will use unmanaged callbacks (which they got as argument) for host notification.

How to Run Code Sample

The sample was built and tested with .NET Core 2.1.7. But I don't see any reason why it wouldn't work in version 2.2 or any other versions of 2.1.

Important note. In the sample, Core CLR directory is hardcoded. It is defined as:

  • C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7 for Windows and
  • /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.7 for Linux

If location in your machine is different, then the demo will not work, and source should be rebuilt with proper location (that is definition of CORECLR_DIR in file GatewayToManaged.cpp).

To run demo, please unzip it and run host application file that is Host.exe for Windows and Host.out for Linux. Application will output in console. Messages from unmanaged code will be written in your console standard color, whereas managed code messages will by written in cyan. Both sides transfer objects of Device class and its unmanaged peer struct Device in C code. Unmanaged host calls (with mediation of Gateway.ManagedDirectMethod() ) methods GetDevice(), SubscribeForDevice() and UnsubscribeFromDevice() of Worker component. On its execution, the first method calls unmanaged callback once, while the second method starts timer which calls unmanaged callback repeatedly providing streaming of device data from managed part to unmanaged one. The third method is called to unsubscribe from the device data streaming (it actually stops the timer to keep things simple).

Sources should be built and run as follows.

Windows

After you defined proper location of Core CLR directory, please build the entire solution.

Important note. Bitness (32- or 64-bit) of CoreCLR should match with bitness in which the host was built. In our case the entire solution should be built in x64 mode.

Result will be placed to your $(SolutionDir)\x64 directory. Run Host.exe from there.

Linux

Since my development environment is Windows, I will describe how to move appropriate files to Linux (Ubuntu 18.04 in my case) and perform there only additional actions. Create directory Hosting and copy there all *.h, *.c and *.cpp files from your Windows directory $(SolutionDir)\Host and all [managed] DLL files from Windows directory $(SolutionDir)\x64. Managed DLLs will be used as they are, and unmanaged code should be built. The latter is performed with the following command:

g++ -o Host.out -D LINUX jsmn.c GatewayToManaged.cpp SampleHost.cpp -ldl

It will produce file Host.out that should run with ./Host.out command.

Conclusions

This work provides compact infrastructure for custom hosting of .NET Core component in unmanaged C/C++ code and ensures reciprocal methods calls between the parts. It runs in Windows and Linux alike. Such an approach may be useful for anti-reflect protection of .NET Core code and, most important, for upgrade of legacy code with new features written in .NET Core.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Israel Israel


  • Nov 2010: Code Project Contests - Windows Azure Apps - Winner
  • Feb 2011: Code Project Contests - Windows Azure Apps - Grand Prize Winner



Comments and Discussions

 
QuestionBad luck with .net core 5.0.3 Pin
Peter Hric19-Feb-21 4:38
Peter Hric19-Feb-21 4:38 
AnswerRe: Bad luck with .net core 5.0.3 Pin
Igor Ladnik7-Jan-22 10:08
professionalIgor Ladnik7-Jan-22 10:08 
QuestionEnums and constant values Pin
Peter Hric19-Feb-21 0:31
Peter Hric19-Feb-21 0:31 
AnswerRe: Enums and constant values Pin
Igor Ladnik7-Jan-22 10:28
professionalIgor Ladnik7-Jan-22 10:28 
QuestionLoad net core assebly from memory Pin
hanie.arce24-Oct-20 2:54
hanie.arce24-Oct-20 2:54 
AnswerRe: Load net core assebly from memory Pin
Igor Ladnik7-Jan-22 10:38
professionalIgor Ladnik7-Jan-22 10:38 
QuestionYou are awesome Pin
Zz9uk325-Aug-20 10:26
Zz9uk325-Aug-20 10:26 
AnswerRe: You are awesome Pin
Igor Ladnik8-Sep-20 17:12
professionalIgor Ladnik8-Sep-20 17:12 
QuestionIs there a similar guide for net framework? Pin
Tuấn Minh7-Nov-19 7:33
Tuấn Minh7-Nov-19 7:33 
Can i use it for net framework?
and when I use a wrapper library of c or c ++ does it work?
AnswerRe: Is there a similar guide for net framework? Pin
Igor Ladnik4-May-20 7:08
professionalIgor Ladnik4-May-20 7:08 
Questionfailed to test the sample on windows machine. Pin
Vijay Kumbhani12-Mar-19 1:19
Vijay Kumbhani12-Mar-19 1:19 
AnswerRe: failed to test the sample on windows machine. Pin
Igor Ladnik23-Apr-19 6:47
professionalIgor Ladnik23-Apr-19 6:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.