Funciones callback, interop y copiado de ficheros conociendo el progreso en C++/CLI

Respecto a C++/CLI siempre oimos que es facilisimo el mezclar código manejado con código no manejado. Hasta hoy no había probado a fondo esta posibilidad. Pero a raiz de una pregunta en el grupo de news de C++ de Microsoft me he puesto manos a la obra. C++/CLI, aunque puede usar toda la 'artilleria' de Interop  con la que cuenta el Framework de .Net, cuenta con una tecnología llamada IJW (It Just Works) que permite hacer llamadas entre código manejado y no manejado de manera muchisimo más natural, totalmente integrada en el lenguaje, y lo que es tambien importante, con mucho mayor rendimiento. Podeís encontrar más información sobre esto en Escribir código más rápido con las modernas funciones de lenguaje de Visual C++ 2005

Volviendo a lo que nos ocupa. La pregunta en foro de C++ era ¿Como copiar un archivo en Visual C++/CLI y mostrar el progreso?. Pues bien la respuesta pasa por llamar a la función CopyFileEx del API de Windows, que nos informa mediante una función de callback del progreso de la copia del archivo.

Pero claro ¿cómo se come eso de punteros a función y demás si queremos hacerlo desde una clase manejada (por ejemplo un formulario) en C++/CLI?. Pues de manera muy natural, si veís el código de ejempo que pongo debajo, no hay ni un solo atributo de Interop. Ni siquiera hay que cambiar los tipos nativos de C++!!!. Realmente It Just Works. La función de callback la cambiamos por un delegado, que es el equivalente manejado a los punteros a función y usamos la funcion Marshal::GetFunctionPointerForDelegate para obtener un puntero que podamos pasar a la función no manejada CopyFileEx.

//Este código solo es una prueba de concepto

//No incluye en necesario código de control de errores

//Como mejora habría que añadir un evento a la clase para optener desde 

//fuera de la misma notificaciones sobre el progreso, no lo he hecho por 

//mantener el ejemplo más claro. 

//Requerido para poder usar CopyFileEx

#define _WIN32_WINNT 0x0400

#include <windows.h>

 

#include <vcclr.h> // Requerido para usar PtrToStringChars

 

using namespace System;

using namespace System::Runtime::InteropServices;

using namespace System::Diagnostics;

 

… 

public ref class CopyFileWithProgress

ref class FCopyFileWithProgress : public System::Windows::Forms::Form
 

 

    //Copiar un archivo recibiendo notificaciones de progreso

    public: System::Void CopyFileWithProgress(String^ from, String^ to)

    {

      //Convertir a cadenas no manejadas

      //Es necesario 'pinearlas' para que no las recolecte el recolector de basura

      //mientras las estamos usando

      pin_ptr<const TCHAR> _From = PtrToStringChars(from);

      pin_ptr<const TCHAR> _To = PtrToStringChars(to);

 

      //Crear un delegado a la función que recibe las notificaciones de progreso

      CopyProgressRoutineDelegate ^ progressCallback =

        gcnew CopyProgressRoutineDelegate(this, &CopyFileWithProgress::FCopyFileWithProgress::CopyProgressRoutine);

 

      //Copiado mostrando el progreso

      //Es necesario convertir el delegado a un puntero a función

      //usando Marshal::GetFunctionPointerForDelegate porque CopyFileEx

      //es una función no manejada que espera una función de callback

      //mediante un puntero a función no manejada.

      ::CopyFileEx(_From, _To,

        (LPPROGRESS_ROUTINE)Marshal::GetFunctionPointerForDelegate(progressCallback).ToPointer(),

        NULL, NULL, COPY_FILE_RESTARTABLE);

    };

 

    //Declaración de delegado a una función con la misma firma

    //que la función no manejada de callback que espera CopyFileEx

    private: delegate DWORD CopyProgressRoutineDelegate(

      LARGE_INTEGER TotalFileSize,

      LARGE_INTEGER TotalBytesTransferred,

      LARGE_INTEGER StreamSize,

      LARGE_INTEGER StreamBytesTransferred,

      DWORD dwStreamNumber,

      DWORD dwCallbackReason,

      HANDLE hSourceFile,

      HANDLE hDestinationFile,

      LPVOID lpData);

 

    //Función que recibe las notificaciones de progreso

    private: DWORD CopyProgressRoutine

    (

      LARGE_INTEGER TotalFileSize,

      LARGE_INTEGER TotalBytesTransferred,

      LARGE_INTEGER /*StreamSize*/,

      LARGE_INTEGER /*StreamBytesTransferred*/,

      DWORD /*dwStreamNumber*/,

      DWORD /*dwCallbackReason*/,

      HANDLE /*hSourceFile*/,

      HANDLE /*hDestinationFile*/,

      LPVOID /*lpData*/

    )

    {

      //Calculamos el porcentaje de progreso

      Int64 total = Marshal::ReadInt64((IntPtr)&TotalFileSize);

      Int64 trasferred = Marshal::ReadInt64((IntPtr)&TotalBytesTransferred);

      Byte percent = (Byte)(trasferred / total * 100);

      Debug::WriteLine(String::Format("Porcentaje de progreso de la copia: {0}", percent));

      //TODO: Desde aquí disparariamos un evento para informar del progreso

      //a cualquier otra clase interesada en conocerlo.

      return 0;

    }

… 

};

La técnica usada en este pequeño ejemplo sirve para llamar a cualquier función del API que nos notifique mediante una función de callback, como EnumWindows, FindWindows, etc… (aunque lo lógico es buscar la función correspondiente del .Net Framework antes de lanzarnos de cabeza al API).

Un comentario sobre “Funciones callback, interop y copiado de ficheros conociendo el progreso en C++/CLI”

  1. ¿Volviendo a C++? pues si … me parece que comenzaré a pasar mas seguido por estos excelentes ejemplos de código ya que es muy probable que tenga q volver durante un tiempo a C++. Ademas este ejemplo me viene «justo» para un pequeño servidor de streaming que tengo que implementar y donde los callbacks para el progreso del streaming de archivos son bastantes utiles para la monitorización.

    Saludos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *