Llamar código C# desde C++

Syed Hassan Sabeeh Kazmi 12 octubre 2023
  1. Utilice C++/Cli como una DLL intermedia para llamar al código C# desde C++
  2. Utilice la tecnología Reverse P/Invoke para llamar al código C# desde C++
  3. Utilice el sistema COM para llamar al código C# desde C++
  4. Utilice el alojamiento CLR ICLRRuntimeHost::ExecuteInDefaultAppDomain() para llamar al código C# desde C++
  5. Use IPC: comunicación entre procesos para llamar al código C# desde C++
  6. Aloja un servidor HTTP para llamar al código C# desde C++
Llamar código C# desde C++

C++ ofrece varias formas de invocar las funciones de C# para ofrecer a los programadores una solución multiplataforma perfecta. Este tutorial le enseñará todos los enfoques posibles para llamar código C# desde C++.

Puede usar C++/CLI como una DLL intermedia, revertir P/Invoke, usar COM, CLR Hosting, comunicación entre procesos (IPC) o alojar un servidor HTTP e invocar a través de verbos HTTP para llamar a código/funciones C# desde C++. El alojamiento CLR es una excelente manera de hacerlo, pero limita al programador a invocar solo métodos que tengan el formato int method(string arg).

La invocación de plataforma, la interoperabilidad C++, la interoperabilidad COM y los tipos de interoperabilidad integrados son algunos de los mecanismos para lograr la interoperabilidad. Los programadores deben reconocer que el código de interoperabilidad C#/C++ puede ser bastante difícil de depurar, especialmente en el límite entre código no administrado/administrado.

Utilice C++/Cli como una DLL intermedia para llamar al código C# desde C++

En general, puede ordenar un delegado como un puntero de función y pasarlo al lado no administrado, lo que requiere que el lado administrado comience la interacción o usar C++/CLI como la capa de mediación, que es una mejor manera de lograr un rendimiento óptimo al llamar a C#. código de C++.

La heterogeneidad es la regla, no la excepción, cuando se trata de desarrollo de software en un entorno profesional, ya que es necesario interactuar con sistemas desarrollados con otras tecnologías, y C++ nativo puede requerir la recuperación de datos mediante la API orientada a objetos de otro sistema diseñado en .NET con C#.

La plataforma C++/CLI tiene la capacidad única de combinar código administrado (C#) y código nativo (C++) en un solo lugar y funciona como una herramienta ideal para construir puentes entre estos dos lenguajes mediante envoltorios simples.

Es un enfoque sencillo que sigue siendo un proceso tedioso y propenso a errores, excepto que la complejidad puede ocurrir cuando se necesita un código de plomería para convertir hacia y desde tipos administrados.

La biblioteca de C#:

using System;  // contains the definition of random class

namespace _stockManager {
  public struct _recruite {
    public double _tactics { get; set; }
    public double _mind { get; set; }
    public double _agility { get; set; }
    public double _experience { get; set; }
  }

  public class API {
    private Random _random = new Random();

    public _recruite _manager(string symbol) {
      double mind = _random.Next(95, 105);

      return new _recruite { _tactics = mind - 0.1, _mind = mind, _agility = mind + 0.1,
                             _experience = mind * 10e6 };
    }
  }
}

El contenedor C++/CLI (archivo de encabezado):

class stockmanager_APIWrapper_private;

struct __declspec(dllexport) _recruite {
  double tactics;
  double mind;
  double agility;
  double experience;
};

class __declspec(dllexport) APIWrapper_stockmanager {
 private:
  stockmanager_APIWrapper_private* _private;

 public:
  APIWrapper_stockmanager();

 public:
  ~APIWrapper_stockmanager();

 public:
  const _recruite _manager(const char* symbol);
};

Archivo de definición de C++:

#using "StockMarketAPI.dll"

#include <msclr\auto_gcroot.h>

#include "APIWrapper_stockmanager.h"

class stockmanager_APIWrapper_private {
 public:
  msclr::auto_gcroot<StockMarket::API ^> API;
};

APIWrapper_stockmanager::APIWrapper_stockmanager() {
  _private = new stockmanager_APIWrapper_private();
  _private->API = gcnew _stockManager::API();
}

const _recruite APIWrapper_stockmanager::_manager(const char* symbol) {
  _stockManager::_recruite managedQuote =
      _private->API->_manager(gcnew System::String(symbol));

  _recruite nativeQuote;
  nativeQuote.tactics = managedQuote._tactics;
  nativeQuote.mind = managedQuote._mind;
  nativeQuote.agility = managedQuote._agility;
  nativeQuote.experience = managedQuote._experience;

  return nativeQuote;
}

APIWrapper_stockmanager::~APIWrapper_stockmanager() { delete _private; }

La aplicación nativa de C++:

#include <iostream>

#include "APIWrapper_stockmanager.h"

int main() {
  const char* stock = "GOOG";

  APIWrapper_stockmanager API;

  _recruite get_recruite = API._manager(stock);

  std::cout << "Tactics: " << get_recruite.tactics << std::endl;
  std::cout << "Mind: " << get_recruite.mind << std::endl;
  std::cout << "Agility: " << get_recruite.agility << std::endl;
  std::cout << "Experience: " << get_recruite.experience << std::endl;
}

Producción :

test.exe
Tactics: 103.9
Mind: 104
Agility: 104.1
Experience: 1.04e+09

Utilice la tecnología Reverse P/Invoke para llamar al código C# desde C++

Permite a los programadores de C++ acceder a estructuras, devoluciones de llamada y funciones en bibliotecas administradas desde su código no administrado o viceversa. En C#, los espacios de nombres System y System.Runtime.InteropServices contienen las API P/Invoke y le permiten comunicarse con los componentes nativos.

Puede realizar ambas cosas, ya sea escribiendo código C# o C++ y el tiempo de ejecución permite que esta comunicación fluya en ambas direcciones. Le permite volver a llamar al código administrado desde funciones nativas mediante el uso de punteros de función (delegado: lo más parecido a los punteros de función) para permitir las devoluciones de llamada desde el código nativo al código administrado.

En este método, define un delegado para una devolución de llamada en vivo que coincide con la firma y lo pasa al método externo, y el tiempo de ejecución se encargará de todo. Es importante revisar las firmas de las funciones no administradas antes de ejecutar el código C++ que contiene funciones C#; por ejemplo, el BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam); es una llamada de función para enumerar todas las ventanas y el BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam); es una firma de devolución de llamada.

using System.Runtime.InteropServices;

public class _synch_doc {
  public delegate void callback_synch(string str);

  public static void doc_call(string str) {
    System.Console.WriteLine("Managed: " + str);
  }

  public static int Main() {
    caller("Hello World!", 10, new callback_synch(_synch_doc.doc_call));
    return 0;
  }

  [DllImport("nat.dll", CallingConvention = CallingConvention.StdCall)]
  public static extern void caller(string str, int _algebra, callback_synch call);
}
#include <stdio.h>
#include <string.h>

typedef void(__stdcall *callback_synch)(wchar_t *str);
extern "C" __declspec(dllexport) void __stdcall caller(wchar_t *input,
                                                       int _algebra,
                                                       callback_synch call) {
  for (int x = 0; x < _algebra; x++) {
    call(input);
  }
}

Producción :

Hello World!

Utilice el sistema COM para llamar al código C# desde C++

El modelo de objetos de componentes: COM (como un sistema distribuido, orientado a objetos e independiente de la plataforma para crear componentes de software binario interactivos) puede ayudar a llamar a la función de C# cuando los componentes de .NET están expuestos a ella. Un programador puede usar COM escribiendo un tipo .NET y consumiendo ese tipo del código no administrado.

Hay algunos requisitos previos que se sabe que son importantes antes de usar COM, y estos incluyen; todos los tipos, propiedades, eventos, métodos y campos administrados que se van a exponer a COM deben ser públicos, los atributos personalizados dentro del código administrado deben ser capaces de mejorar la interoperabilidad de un componente, y el desarrollador debe resumir los pasos involucrados en la referencia y la implementación. las asambleas

Además, registre ensamblados, haga referencia a tipos .NET y llame a un objeto .NET con COM para consumir un tipo administrado de COM, así como un ensamblado con nombre seguro que se puede instalar en la caché de ensamblados global y requiere una firma de su editor. .

El siguiente ejemplo demostrará la interoperación de un cliente COM y un servidor .NET que realiza cálculos de hipotecas. El cliente creará y llamará a una instancia de una clase administrada y pasará cuatro argumentos a la instancia para mostrar los cálculos.

Cliente COM:

// ConLoan.cpp : Defines the entry point for the console application.
#import "..\LoanLib\_azythro_call.tlb" raw_interfaces_only
#include "stdafx.h"
using namespace _azythro_call;

int main(int argc, char* argv[]) {
  HRESULT hr = CoInitialize(NULL);

  ILoanPtr pILoan(__uuidof(_azythro));

  if (argc < 5) {
    printf("Usage: ConLoan Balance Rate Term Payment\n");
    printf("    Either Balance, Rate, Term, or Payment must be 0\n");
    return -1;
  }

  double acc_initial_balance = atof(argv[1]);
  double acc_balance_rate = atof(argv[2]) / 100.0;
  short acc_payment_term = atoi(argv[3]);
  double acc_bal_payment = atof(argv[4]);

  pILoan->put_OpeningBalance(acc_initial_balance);
  pILoan->put_Rate(acc_balance_rate);
  pILoan->put_Term(acc_payment_term);
  pILoan->put_Payment(acc_bal_payment);

  if (acc_initial_balance == 0.00)
    pILoan->calculate_balance_opening(&acc_initial_balance);
  if (acc_balance_rate == 0.00) pILoan->calculate_rate(&acc_balance_rate);
  if (acc_payment_term == 0) pILoan->calculate_term(&acc_payment_term);
  if (acc_bal_payment == 0.00) pILoan->calculate_payment(&acc_bal_payment);

  printf("Balance = %.2f\n", acc_initial_balance);
  printf("Rate    = %.1f%%\n", acc_balance_rate * 100);
  printf("Term    = %.2i\n", acc_payment_term);
  printf("Payment = %.2f\n", acc_bal_payment);

  VARIANT_BOOL extra_PMTs;
  double user_acc_balance = 0.0;
  double Principal = 0.0;
  double user_acc_interest = 0.0;

  printf("%4s%10s%12s%10s%12s\n", "Nbr", "Payment", "Principal", "Interest",
         "Balance");
  printf("%4s%10s%12s%10s%12s\n", "---", "-------", "---------", "--------",
         "-------");

  pILoan->get_1stPMT_dist(acc_bal_payment, &user_acc_balance, &acc_principal,
                          &user_acc_interest, &extra_PMTs);

  for (short PmtNbr = 1; extra_PMTs; PmtNbr++) {
    printf("%4i%10.2f%12.2f%10.2f%12.2f\n", PmtNbr, acc_bal_payment,
           acc_principal, user_acc_interest, user_acc_balance);

    pILoan->get_nextPMT_dist(acc_bal_payment, &user_acc_balance, &acc_principal,
                             &user_acc_interest, &extra_PMTs);
  }

  CoUninitialize();
  return 0;
}

Servidor .NET:

using System;
using System.Reflection;

[assembly:AssemblyKeyFile("sample.snk")]
namespace _azythro_call {

  public interface loan_call {
    double balance_opening { get; set; }
    double balance_rate { get; set; }
    double user_payment { get; set; }
    short payment_term { get; set; }
    String per_risk_rate { get; set; }

    double calculate_payment();
    double calculate_balance_opening();
    double calculate_rate();
    short calculate_term();
    bool get_1stPMT_dist(double cal_PMT_AMT, ref double user_acc_balance, out double acc_principal,
                         out double user_acc_interest);
    bool get_nextPMT_dist(double cal_PMT_AMT, ref double user_acc_balance, out double acc_principal,
                          out double user_acc_interest);
  }

  public class _azythro : loan_call {
    private double acc_initial_balance;
    private double acc_balance_rate;
    private double acc_bal_payment;
    private short acc_payment_term;
    private String rating_calculatedRisk;

    public double balance_opening {
      get { return acc_initial_balance; }
      set { acc_initial_balance = value; }
    }

    public double balance_rate {
      get { return acc_balance_rate; }
      set { acc_balance_rate = value; }
    }

    public double user_payment {
      get { return acc_bal_payment; }
      set { acc_bal_payment = value; }
    }

    public short payment_term {
      get { return acc_payment_term; }
      set { acc_payment_term = value; }
    }

    public String per_risk_rate {
      get { return rating_calculatedRisk; }
      set { rating_calculatedRisk = value; }
    }

    public double calculate_payment() {
      user_payment = Util.Round(
          balance_opening * (balance_rate / (1  Math.Pow((1 + balance_rate), -payment_term))), 2);
      return user_payment;
    }

    public double calculate_balance_opening() {
      balance_opening = Util.Round(
          user_payment / (balance_rate / (1 - Math.Pow((1 + balance_rate), -payment_term))), 2);
      return balance_opening;
    }

    public double calculate_rate() {
      double acc_payment_calculated = user_payment;

      for (balance_rate = 0.001; balance_rate < 28.0; balance_rate += 0.001) {
        user_payment = Util.Round(
            balance_opening * (balance_rate / (1  Math.Pow((1 + balance_rate), -payment_term))),
            2);

        if (user_payment >= acc_payment_calculated)
          break;
      }
      return balance_rate;
    }

    public short calculate_term() {
      double acc_payment_calculated = user_payment;

      for (payment_term = 1; payment_term < 480; payment_term++) {
        user_payment = Util.Round(
            balance_opening * (balance_rate / (1  Math.Pow((1 + balance_rate), -payment_term))),
            2);

        if (user_payment <= acc_payment_calculated)
          break;
      }

      return payment_term;
    }

    public bool get_1stPMT_dist(double cal_PMT_AMT, ref double user_acc_balance,
                                out double acc_principal, out double user_acc_interest) {
      user_acc_balance = balance_opening;
      return get_nextPMT_dist(cal_PMT_AMT, ref user_acc_balance, out acc_principal,
                              out user_acc_interest);
    }

    public bool get_nextPMT_dist(double cal_PMT_AMT, ref double user_acc_balance,
                                 out double acc_principal, out double user_acc_interest) {
      user_acc_interest = Util.Round(user_acc_balance * balance_rate, 2);
      acc_principal = Util.Round(cal_PMT_AMT - user_acc_interest, 2);
      user_acc_balance = Util.Round(user_acc_balance - acc_principal, 2);

      if (user_acc_balance <= 0.0)
        return false;

      return true;
    }
  }

  internal class Util {
    public static double Round(double value, short digits) {
      double factor = Math.Pow(10, digits);
      return Math.Round(value * factor) / factor;
    }
  }
}

Producción :

[load] sample.snk

Balance: 300000
Rate: 24000
Term: 1200
Payment: 175000

Utilice el alojamiento CLR ICLRRuntimeHost::ExecuteInDefaultAppDomain() para llamar al código C# desde C++

El método ICLRRuntimeHost::ExecuteInDefaultAppDomain llama al método especificado del tipo especificado en el ensamblado administrado especificado. Su sintaxis es algo así como HRESULT ExecuteInDefaultAppDomain ([in] LPCWSTR pwzAssemblyPath, [in] LPCWSTR pwzTypeName, [in] LPCWSTR pwzMethodName, [in] LPCWSTR pwzArgument, [out] DWORD *pReturnValue ); donde cada parámetro representa respectivamente la ruta, el tipo, el nombre, el parámetro de cadena para pasar el método y el valor entero devuelto por el método invocado.

Además, el método invocado debe tener la firma static int pwzMethodName (String pwzArgument) donde pwzArgument representa un valor de cadena pasado como parámetro a ese método de invocación. En general, puede incrustar cualquier ensamblado CLR en un programa C++ nativo utilizando CLR Hosting, y es la razón por la cual los programas nativos como SQL Server admiten extensiones de código .NET como SQL CLR y pueden cargarse en los procesos masivos utilizando CorBindToRuntimeEx() o CLRCreateInstance() depende de la versión 2.0 o 4 de .NET Framework, respectivamente.

Memoria Compartida.h:

#pragma const_onetime_impl

#ifdef SHAREDMEM_EXPORTS
#define SHAREDMEM_API __declspec(export_dll)
#else
#define SHAREDMEM_API __declspec(import_dll)
#endif

#define conv_call_sharedmemory __cdecl

extern "C" {
SHAREDMEM_API BOOL conv_call_sharedmemory SetSharedMem(ULONGLONG _64bitValue);
SHAREDMEM_API BOOL conv_call_sharedmemory GetSharedMem(ULONGLONG* p64bitValue);
}

Memoria Compartida.cpp:

#include "SharedMemory.h"
#include "custom_made.h"  // a user created/defined library | customizable according to your preference

HANDLE object_mapfile_handling = NULL;  // an object to handle mapped file
LPVOID ponter_sharedMemory =
    NULL;  // a pointer to the shared memory (SharedMemory)
const int size_type_sharedMemory =
    sizeof(ULONGLONG);  // the size type of shared memory

BOOL sharedMemory_init_create() {
  // named mapped_file object creation
  object_mapfile_handling = CreateFileMapping(
      INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size_type_sharedMemory,
      TEXT("file_sharedMemory")  // initialize the name of shared memory file
  );

  if (object_mapfile_handling == NULL) {
    return FALSE;
  }

  BOOL memory_access_information = (ERROR_ALREADY_EXISTS != GetLastError());

  // access / get a pointer to the shared memory
  ponter_sharedMemory =
      MapViewOfFile(object_mapfile_handling, FILE_MAP_WRITE, 0, 0, 0);

  if (ponter_sharedMemory == NULL) {
    return FALSE;
  }

  if (memory_access_information)  // First time the shared memory is accessed?
  {
    ZeroMemory(ponter_sharedMemory, size_type_sharedMemory);
  }

  return TRUE;
}

BOOL set_type_sharedMemory(ULONGLONG _64bitValue) {
  BOOL check_set = sharedMemory_init_create();

  if (check_set) {
    ULONGLONG* new_sharedMemory_pointer = (ULONGLONG*)ponter_sharedMemory;
    *new_sharedMemory_pointer = _64bitValue;
  }

  return check_set;
}

BOOL get_sharedMemory(ULONGLONG* p64bitValue) {
  if (p64bitValue == NULL) return FALSE;

  BOOL check_get = sharedMemory_init_create();

  if (check_get) {
    ULONGLONG* new_sharedMemory_pointer = (ULONGLONG*)ponter_sharedMemory;
    *p64bitValue = *new_sharedMemory_pointer;
  }

  return check_get;
}
namespace shared_memory_csharp {
  delegate void _void_init();

  static public class command_line_interface {
    [import_dll("SharedMemory.dll")]
    static extern bool set_type_sharedMemory(Int64 value);

    static GCHandle delegate_cust_handle;

    public static int prog_point_entry(string ignored) {
      IntPtr primary_function = IntPtr.Zero;
      Delegate new_function_delegate = new VoidDelegate(_custom_method);
      delegate_cust_handle = GCHandle.Alloc(new_function_delegate);
      primary_function = Marshal.GetFunctionPointerForDelegate(new_function_delegate);
      bool bool_set_ok = set_type_sharedMemory(primary_function.ToInt64());
      return bool_set_ok ? 1 : 0;
    }

    public static void primary_method() {
      MessageBox.Show("Hello from C# primary_method!");
      delegate_cust_handle.Free();
    }
  }
}

principal.cpp:

#include "SharedMemory.h"
typedef void (*VOID_FUNC_PTR)();

void execute_charp_code() {
  ICLRRuntimeHost *pointer_CLR_host = NULL;
  HRESULT cor_bind_hr =
      CorBindToRuntimeEx(NULL, L"wks", 0, CLSID_CLRRuntimeHost,
                         IID_ICLRRuntimeHost, (PVOID *)&pointer_CLR_host);

  HRESULT process_start_hr = pointer_CLR_host->Start();

  DWORD retVal;
  HRESULT process_execute_hr = pointer_CLR_host->ExecuteInDefaultAppDomain(
      szPathToAssembly, L"shared_memory_csharp.command_line_interface",
      L"prog_point_entry", L"",
      &retVal  // returns `1` for successful execution | returns `0` for failed
               // execution
  );

  if (process_execute_hr == S_OK && retVal == 1) {
    ULONGLONG new_shared_memory_value = 0;
    BOOL bool_shMem_value = get_sharedMemory(&new_shared_memory_value);
    if (bool_shMem_value) {
      VOID_FUNC_PTR function_csharp = (VOID_FUNC_PTR)new_shared_memory_value;
      function_csharp();
    }
  }
}

Producción :

Hello from C# primary_method!

Use IPC: comunicación entre procesos para llamar al código C# desde C++

El mundo de la comunicación entre procesos tiene muchos métodos para realizar esta tarea, que incluyen; canalizaciones con nombre, RPC, memoria compartida, etc. En pocas palabras, el IPC ejecuta su programa C++ dentro de un proceso para activar su código C#.

Los procesos de IPC pueden comunicarse entre sí e invocar los métodos de los demás o utilizar los datos de los demás. Durante la ejecución, crea dos procesos, cada uno con código de diferente tecnología.

En el caso de C# y C++, creará dos procesos y creará una conexión para compartir archivos. Además, puede usar sockets en la máquina local para conectar procesos para IPC usando canalizaciones con nombre o establecer una comunicación dúplex entre un servidor de canalización y uno o más clientes de canalización con un flujo que puede usarse para enviar/recibir mensajes entre procesos.

Las canalizaciones con nombre tienen un comportamiento FIFO (primero en entrar, primero en salir), y encontrará dos en el siguiente ejemplo.

using System;       // essential
using System.Text;  // essential

namespace csharp_namedpipes {
  class namedpipe_server {
    static void Main(string[] args) {
      NamedPipeServer pipe_server_one = new NamedPipeServer(@"\\.\pipe\myNamedPipe1", 0);
      NamedPipeServer pipe_server_two = new NamedPipeServer(@"\\.\pipe\myNamedPipe2", 1);

      pipe_server_one.Start();
      pipe_server_two.Start();

      string _message = "Start";
      do {
        Console.WriteLine("Enter the message: ");
        _message = Console.ReadLine();
        pipe_server_two.SendMessage(_message, pipe_server_two.clientse);
      } while (_message != "Quit");

      pipe_server_one.StopServer();
      pipe_server_two.StopServer();
    }
  }
}
void execute_csharp_code() {
  LPTSTR lpsz_pipe_name1 = TEXT("\\\\.\\pipe\\myNamedPipe1");
  LPTSTR lpsz_pipe_name2 = TEXT("\\\\.\\pipe\\myNamedPipe2");
  do {
    printf("Enter your message: ");
    scanf("%s", buf);
    if (strcmp(buf, "Quit") == 0)
      Write_St = FALSE;
    else {
      WriteFile(hPipe1, buf, dwBytesToWrite, &cbWritten, NULL);
      memset(buf, 0xCC, 100);
    }
  } while (Write_St);
}

Producción :

[C# app]

Enter your message: this is a test
C++ App: Received 17 bytes: test sending back

[C++ app]

Enter the message:
C# App: Received 5 Bytes: this
C# App: Received 2 bytes: a
test sending back
Enter the message:

Aloja un servidor HTTP para llamar al código C# desde C++

El curlpp permite a los programadores de C++ solicitar HTTP alojando un servidor HTTP e invocando a través de verbos HTTP como una API de estilo REST. En C++, curlpp es natural y ayuda a obtener el contenido de una URL y acceder al código de C#.

Por otro lado, POCO es otra forma de alojar un servidor HTTP y, a diferencia de curlpp, tiene una rica documentación y admite HTTPs. Como POCO es gratuito y de código abierto, puede ser un gran comienzo para los principiantes y, en los siguientes ejemplos, aprenderá cómo implementar estos enfoques.

#include <string.h>
#include <windows.h>
#include <winsock2.h>

#include <iostream>
#include <locale>
#include <sstream>
#include <vector>

using namespace std;

#pragma comment(lib, "ws2_32.lib")

int main(void) {
  WSADATA data_wsa;
  SOCKET server_socket;
  SOCKADDR_IN server_socket_add;
  int count_line = 0;
  int count_row = 0;
  struct hostent *server_host;
  locale host_local;
  char var_buffer[10000];
  int i = 0;
  int data_length_new;
  string get_website_HTML;

  // website URL
  string website_URL = "www.google.com";

  // HTTP GET
  string get_website_HTTP = "GET / HTTP/1.1\r\nHost: " + website_URL +
                            "\r\nConnection: close\r\n\r\n";

  if (WSAStartup(MAKEWORD(2, 2), &data_wsa) != 0) {
    cout << "WSAStartup failed.\n";
    system("pause");
    // return 1;
  }

  server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  server_host = gethostbyname(website_URL.c_str());

  server_socket_add.sin_port = htons(80);
  server_socket_add.sin_family = AF_INET;
  server_socket_add.sin_addr.s_addr = *((unsigned long *)server_host->h_addr);

  if (connect(server_socket, (SOCKADDR *)(&server_socket_add),
              sizeof(server_socket_add)) != 0) {
    cout << "Could not connect";
    system("pause");
    // return 1;
  }

  // send GET / HTTP
  send(server_socket, get_website_HTTP.c_str(),
       strlen(get_website_HTTP.c_str()), 0);

  // receive HTML
  while ((data_length_new = recv(server_socket, var_buffer, 10000, 0)) > 0) {
    int x = 0;
    while (var_buffer[x] >= 32 || var_buffer[x] == '\n' ||
           var_buffer[x] == '\r') {
      get_website_HTML += var_buffer[x];
      x += 1;
    }
  }

  closesocket(server_socket);
  WSACleanup();

  // Display HTML source
  cout << get_website_HTML;
  cout << "source displayed!" << endl;

  // pause
  cout << "\n\nPress ANY key to close.\n\n";
  cin.ignore();
  cin.get();

  return 0;
}

Producción :

source displayed!
Press ANY key to close.

Una característica de los lenguajes .NET es que hacen uso del código administrado donde CLR asume la responsabilidad de administrar la memoria y los recursos del programa mediante la recolección de elementos no utilizados, el control de la vida útil de los objetos, la mejora de la funcionalidad de depuración y mucho más.

Los sistemas de tiempo de ejecución saben poco sobre la memoria y los recursos utilizados por el programa en código no administrado como el de C++ y pueden proporcionar servicios mínimos; La parte difícil de llamar al código C# con C++ es saber cómo mover los datos a través del límite entre el código administrado y el no administrado, ya que este proceso se conoce como marshaling.

Syed Hassan Sabeeh Kazmi avatar Syed Hassan Sabeeh Kazmi avatar

Hassan is a Software Engineer with a well-developed set of programming skills. He uses his knowledge and writing capabilities to produce interesting-to-read technical articles.

GitHub