Search This Blog

Sunday, December 15, 2013

Native C++: How to communicate with .NET application

Simple example showing how to implement the interprocess communicate between an unmanaged (native) C++ application and a managed .NET application using Eneter Messaging Framework.

The source code for this example can be downloaded from here:
http://eneter.net/Downloads/Examples/CalculatorNative.zip

Introduction

The example bellow shows how to implement the communication between an unmanaged C++ application and a standalone .NET (managed) application using Eneter Messaging Framework.

To demonstrate this scenario the example implements a simple client-service communucation. The service is a .NET application calculating two numbers and the client is an unmanaged (native) C++ application using the service for the calculation.


The example uses Eneter Messaging Framework. The framework is free for non-commercial use and can be downloaded from http://www.eneter.net/ProductDownload.htm.
More detailed technical info can be found at technical info.

Wrapper Library

The major challenge in this scenario is to use the managed Eneter functionality from the unmanaged C++ code. The idea is to provide a 'bridge' that would connect unmanged C++ with managed Eneter. The bridge would be a library that could be linked and used from the unmanaged C++ code but internally would direclty use managed Eneter functionality.


The bridge can be implemented as managed C++ assembly that wrapps the communication functionality based on the Eneter framework and exposes the C++ API that does not contain any managed types or syntax constructions - only pure types and syntax that can be understood by unmanaged C++.

Here is the C++ API declaration for our example. The API exposes the communication interface IClient which provides methods responsible for the communication with the service.
It also provides the factory method CreateClient(...) which is responsible for the instantiation of the IClient interface and the DestroyClient(...) method that is responsible for correct releasing the client instance from the memory when it is not needed.

#pragma once

#ifdef WRAPPER_EXPORT
#define WRAPPER_API __declspec(dllexport)
#else
#define WRAPPER_API __declspec(dllimport)
#endif

// Example of an interface declaring the communication
// functionality.
class IClient
{
public:
    virtual ~IClient(void){}

    virtual void OpenConnection() = 0;
    virtual void CloseConnection() = 0;
    virtual int SendCalculateRequest(int a, int b) = 0;
};

// And here are factories for creating and destructing.
extern "C" WRAPPER_API IClient* CreateClient(const wchar_t* address);
extern "C" WRAPPER_API void DestroyClient(IClient* client);

And here is the internal implementation:
(declaration)
#pragma once

#include <string>
#include <vcclr.h>
#include "CommunicationApi.h"

#using <..\\Debug\\Eneter.Messaging.Framework.dll>

using namespace System;
using namespace Eneter::Messaging::EndPoints::TypedMessages;

// Request message declared as managed class.
public ref class RequestMessage
{
public:
    int Number1;
    int Number2;
};

// Response message declared as managed class.
public ref class ResponseMessage
{
public:
    int Result;
};


// Client declared as NATIVE class to be able to derive
// from IClient.
class Client : public IClient
{
public:
    Client(const wchar_t* address);
    virtual ~Client();

    virtual void OpenConnection();
    virtual void CloseConnection();
    virtual int SendCalculateRequest(int a, int b);

private:
    gcroot<String^> myAddress;
    gcroot<ISyncDuplexTypedMessageSender<ResponseMessage^, RequestMessage^> ^> mySender;
};

(definition)

#include "StdAfx.h"
#include "Client.h"

#using <..\\Debug\\Eneter.Messaging.Framework.dll>
using namespace Eneter::Messaging::EndPoints::TypedMessages;

using namespace System;
using namespace Eneter::Messaging::MessagingSystems::MessagingSystemBase;
using namespace Eneter::Messaging::MessagingSystems::TcpMessagingSystem;

Client::Client(const wchar_t* address)
{
    myAddress = gcnew String(address);
    IDuplexTypedMessagesFactory^ aSenderFactory = gcnew DuplexTypedMessagesFactory(TimeSpan::FromSeconds(5));
    mySender = aSenderFactory->CreateSyncDuplexTypedMessageSender<ResponseMessage^, RequestMessage^>();
}


Client::~Client(void)
{
    CloseConnection();
}

void Client::OpenConnection()
{
    // Let's use TCP.
    // (But other protocols would work too.)
    IMessagingSystemFactory^ aMessaging = gcnew TcpMessagingSystemFactory();
    IDuplexOutputChannel^ anOutputChannel = aMessaging->CreateDuplexOutputChannel(myAddress);

    // Attach the channel and be able to send request messages
    // and receive responses.
    mySender->AttachDuplexOutputChannel(anOutputChannel);
}

void Client::CloseConnection()
{
    // Detach the output channel.
    // Note: It releases the thread listening to responses.
    mySender->DetachDuplexOutputChannel();
}

int Client::SendCalculateRequest(int a, int b)
{
    RequestMessage^ aRequest = gcnew RequestMessage;
    aRequest->Number1 = a;
    aRequest->Number2 = b;

    ResponseMessage^ aResponse = mySender->SendRequestMessage(aRequest);

    return aResponse->Result;
}

And here is the implementation of the factory method:

#include "Stdafx.h"
#include "CommunicationApi.h"
#include "Client.h"

IClient* CreateClient(const wchar_t* address)
{
    // Native C++ representing the communication client.
    IClient *aClient = new Client(address);
    return aClient;
}

void DestroyClient(IClient* client)
{
    delete client;
}

Unmanaged C++ Client

The unmanaged C++ client is a simple console application using the bridge library that wraps the Eneter communication lfunctionality.
It opens the connection with the service and sends the calculation request to the service. Then it closes the connection and displays the result.

The implementation is very simple:


#include "stdafx.h"
#include <iostream>
#include "../CommunicationWrapper/CommunicationApi.h"


int _tmain(int argc, _TCHAR* argv[])
{
    // Create the communication wrapper.
    IClient *aClient = CreateClient(L"tcp://127.0.0.1:4502/");

    // Open the connection.
    aClient->OpenConnection();

    // Send the request and get the response.
    int aResult = aClient->SendCalculateRequest(10, 20);

    // Close the connection.
    aClient->CloseConnection();

    // Release the client from the memory.
    DestroyClient(aClient);

    // Display the result.
    std::cout << "Result = " << aResult << std::endl;

    return 0;
}

Managed C# Service

The managed service is a simple .NET console application listening to requests. When it receives a requests it calculates it and sends beck the response.

Here is the whole implementation:

using System;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;

namespace CalculatorService
{
    // Request message.
    public class RequestMessage
    {
        public int Number1 { get; set; }
        public int Number2 { get; set; }
    }

    // Response message.
    public class ResponseMessage
    {
        public int Result { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Create message receiver.
            IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory();
            IDuplexTypedMessageReceiver<ResponseMessage, RequestMessage> aReceiver =
                aReceiverFactory.CreateDuplexTypedMessageReceiver<ResponseMessage, RequestMessage>();

            // Subscribe to process request messages.
            aReceiver.MessageReceived += OnMessageReceived;

            // Use TCP for the communication.
            IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
            IDuplexInputChannel anInputChannel =
                aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:4502/");

            // Attach the input channel to the receiver and start listening.
            aReceiver.AttachDuplexInputChannel(anInputChannel);

            Console.WriteLine("The calculator service is running. Press ENTER to stop.");
            Console.ReadLine();

            // Detach the input channel to stop listening.
            aReceiver.DetachDuplexInputChannel();
        }

        private static void OnMessageReceived(object sender,
                                TypedRequestReceivedEventArgs<RequestMessage> e)
        {
            // Calculate numbers.
            ResponseMessage aResponseMessage = new ResponseMessage();
            aResponseMessage.Result = e.RequestMessage.Number1 + e.RequestMessage.Number2;

            Console.WriteLine("{0} + {1} = {2}", e.RequestMessage.Number1,
               e.RequestMessage.Number2, aResponseMessage.Result);

            // Send back the response message.
            var aReceiver = (IDuplexTypedMessageReceiver<ResponseMessage, RequestMessage>)sender;
            aReceiver.SendResponseMessage(e.ResponseReceiverId, aResponseMessage);
        }
    }
}

4 comments:

  1. Can you give an example to communication between C++/CLI (server) to C#(client)... it is not working throwing time out exception ..code is here http://stackoverflow.com/questions/24927543/timeout-while-making-rpc-call-form-c-sharp-to-c-cli

    ReplyDelete
    Replies
    1. Hi Yogender, I am not 100% sure but I think the problem in your case could be missing attribute [ProtoContract] on message declarations.

      E.g. on service side:
      [ProtoContract]
      public ref class RequestMessage
      {
      public:
      [ProtoMember(1)]
      Int32 Number1;
      [ProtoMember(2)]
      Int32 Number2;
      };

      and then on the client side:
      [ProtoContract]
      public class RequestMessage
      {
      [ProtoMember(1)]
      public int Number1 { get; set; }
      [ProtoMember(2)]
      public int Number2 { get; set; }
      }

      Anyway, here is the link to working example. I hope it will help:
      http://www.eneter.net/Downloads/Examples/CalculatorCppClrService.zip

      Delete