Ir al contenido


Foto

[MULTILENGUAJE] Threads con Windows API. Un tutorial sencillo.


  • Por favor identifícate para responder
1 respuesta en este tema

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 17 abril 2012 - 11:05

Tras el hilo comenzado aquí y la contestación que dí para el uso de threads, he pensado desarrollar un ejemplo a partir de la sugerencia que hice.

Creo que el tema de los Threads en muchas ocasiones no está claro y desde el punto de vista de la API de Windows quizás menos aún, así que voy a desarrollar el ejemplo a bajo nivel, desde la API esperando que se entienda y sirva de base para su uso en otras aplicaciones diferentes a la tratada.

El ejemplo lo voy a escribir usando lazarus pero no habrá problema para trasladarlo a delphi o a C.

La utilización de Threads con la API de Windows se centra en la API CreateThread. Esta API, entre otros, precisa de dos parámetros fundamentales y sencillos de entender:

1.- El punto de entrada del hilo, que para nosotros va a representarse por una función del tipo:


delphi
  1. function MyFunc(param: pointer): DWORD; stdcall;



2.- El parámetro pasado a dicha función que será un puntero a una estructura con los datos pertinentes que usará la función del Thread.

Mas información la encontrareis aquí.

El caso del parámetro merece una especial atención:
- Será un puntero a una estructura que diseñaremos a nuestro gusto pero con todos los datos necesarios y sin usar punteros que apunten a elementos externos.
- La estructura (contenido de ese puntero) debe existir durante toda la vida del Thread. Cuando la estructura de crea dentro de una función, su vida es la misma que la de dicha función (se crea en la pila), sin embargo es posible que el thread dure bastante mas o que incluso comience después de terminar la función que lo creó.
- Preferiblemente la estructura será una variable global o perteneciente a una clase perdurable.
- En caso de precisar punteros, éstos apuntarán a datos dentro de la propia estructura.

El Thread termina cuando lo haga su función pero su handle ha de ser cerrado (API CloseHandle).

Vistos estos detalles generales voy a desarrollar el ejemplo que consistirá en crear una función que mueva carpetas, con la API ShFileOperation, mediante Threads. Cada llamada a dicha función creará un nuevo Thread hasta un número máximo que consideremos.

Para controlar los Threrads que están activos voy a aprovechar la estructura parámetro donde coloco una bandera (Runing) que será True mientras viva el Thread. De esta manera sabremos los hilos que terminan para poder ser reutilizados.

La estructura parámetro queda definida como:


delphi
  1. TSHFParam = record
  2.   fos: TSHFileOpStruct;  // Parámetro para la API ShFileOperation
  3.   StrFrom: String;      // Nombre de la carpeta origen
  4.   StrTo: String;        // Nombre de la carpeta destino
  5.   Runing: boolean;      // Bandera
  6. end;
  7. PSHFParam = ^TSHFParam;

La estructura TSHFileOpStruct contiene dos PCHAR a la lista de archivos o carpetas origen y destino. Esos punteros apuntarán a los elementos String StrFrom y StrTo de nuestra TSHFParam asegurando que apuntarán a lugares válidos durante la vida del Thread. La presencia de fos en nuestra estructura es por comodidad en la escritura del código.

Lo siguiente es crear un array de elementos de nuestra estructura. Dicho array será una variable global o estática de nuestra función.

No es conveniente permitir demasiados Threads al tiempo por lo que limitarlos a 5 puede ser suficiente. En el caso de hacer mas llamadas a nuestra función para mover archivos, esta esperará a tener un Thread libre para usarlo, en caso contrario se creará el Thread correspondiente y seguirá sin espera alguna.

Localizamos un Thread libre recorriendo el array y mirando si el semáforo Runing es False.


Creo que la filosofía del planteamiento es fácil de entender y es trasladable a otro tipo de problemática.

Coloco el código completo y comentado del ejemplo:


delphi
  1. uses
  2.   windows, shellapi;
  3.  
  4.  
  5. // Estructura del parámetro a pasar a cada Thread
  6. TSHFParam = record
  7.   fos: TSHFileOpStruct;
  8.   StrFrom: String;
  9.   StrTo: String;
  10.   Runing: boolean;
  11. end;
  12. PSHFParam = ^TSHFParam;
  13.  
  14. const MAX_THREAD = 5;  // Máximo número de thread permitidos al mismo tiempo
  15.  
  16. var
  17. // Array de parámetros para los MAX_THREAD posibles
  18. AParam: array [0..MAX_THREAD-1] of TSHFParam;
  19.  
  20. function ThShFileOperation(Param: PSHFParam): DWORD; stdcall;
  21. begin
  22.   // Ejecuto la función del Thread y al terminar lo m,arco como finalizado
  23.   Result:= ShFileOperation(Param^.fos);
  24.   Param^.Runing:= false;
  25. end;
  26.  
  27. procedure MoveDir(const fromDir, toDir: string);
  28. var
  29.   hThread: Cardinal;
  30.   i: integer;
  31. begin
  32.   // Busco un thread no usado entre los MAX_THREAD permitidos
  33.   i:= 0; while AParam.Runing do i:= (i+1) mod MAX_THREAD;
  34.   // Preparo el parámetro para el nuevo thread
  35.   with AParam[i] do
  36.   begin
  37.     StrFrom:= fromDir + #0;
  38.     StrTo:= toDir + #0;
  39.     ZeroMemory(@fos, SizeOf(TSHFileOpStruct));
  40.     fos.wFunc  := FO_MOVE;
  41.     fos.fFlags := FOF_FILESONLY or FOF_SILENT or FOF_NOCONFIRMATION;
  42.     fos.pFrom  := PChar(StrFrom);
  43.     fos.pTo    := PChar(StrTo);
  44.   end;
  45.   // Creo el nuevo thread y si tengo éxito lo marco como usado
  46.   hThread:= CreateThread(nil, 0, @ThShFileOperation, @AParam[i], 0, PDWORD(0)^);
  47.   AParam[i].Runing:= boolean(hThread);
  48.   // Cierro el Handle
  49.   CloseHandle(hThread);
  50.  
  51. end;

Y su uso:


delphi
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3. MoveDir('D:\Copia (1) de Ejemplo', 'D:\Nueva1');
  4. MoveDir('D:\Copia (2) de Ejemplo', 'D:\Nueva2');
  5. MoveDir('D:\Copia (3) de Ejemplo', 'D:\Nueva3');
  6. MoveDir('D:\Copia (4) de Ejemplo', 'D:\Nueva4');
  7. end; 

Espero que este escueto tutorial sirva de base para la solución de otras cuestiones que requieran el uso de Threads en Windows.



Saludos.

Edito para subir un proyecto lazarus ejemplo

Archivos adjuntos


  • 0

#2 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 19 abril 2012 - 03:41

Como complemento voy a añadir el mismo ejemplo en C++ Builder. No mere mas comentarios que los ya hechos y los propios del código, salvo que el array de parámetros lo uso como una variable estática de la función MoveDir.
 


cpp
  1. #include <Shellapi.h>
  2.  
  3.  
  4. #define MAX_THREAD  5  // Máximo número de thread permitidos al mismo tiempo
  5.  
  6. // Estructura del parámetro a pasar a cada Thread
  7. struct TSHFParam{
  8.   SHFILEOPSTRUCT fos;
  9.   String StrFrom;
  10.   String StrTo;
  11.   bool Runing;
  12. };
  13.  
  14. // Función principar del Thread: Punto de entrada
  15. DWORD __stdcall ThSHFileOperation(TSHFParam* Param)
  16. {
  17.   // Ejecuto la función del Thread y al terminar lo marco como finalizado
  18.   DWORD Result = SHFileOperation(&Param->fos);
  19.   Param->Runing = false;
  20.   return Result;
  21. }
  22.  
  23. void MoveDir(String fromDir, String toDir)
  24. {
  25.   // Array estático para los parémetros y control de los Thread.
  26.   static TSHFParam AParam[MAX_THREAD];
  27.   HANDLE hThread;
  28.  
  29.   // Busco un thread no usado entre los MAX_THREAD permitidos
  30.   int i=0; while (AParam[i].Runing) i = (i+1) % MAX_THREAD;
  31.  
  32.   // Preparo el parámetro para el nuevo thread
  33.   AParam[i].StrFrom = fromDir + " "; AParam[i].StrFrom[AParam[i].StrFrom.Length()] = 0;
  34.   AParam[i].StrTo = toDir + " "; AParam[i].StrTo[AParam[i].StrTo.Length()] = 0;
  35.   ZeroMemory(&(AParam[i].fos), sizeof(SHFILEOPSTRUCT));
  36.   AParam[i].fos.wFunc  = FO_MOVE;
  37.   AParam[i].fos.fFlags = FOF_FILESONLY | FOF_SILENT | FOF_NOCONFIRMATION;
  38.   AParam[i].fos.pFrom  = AParam[i].StrFrom.c_str();
  39.   AParam[i].fos.pTo    = AParam[i].StrTo.c_str();
  40.  
  41.   // Creo el nuevo thread y si tengo éxito lo marco como usado
  42.   hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThSHFileOperation, &AParam[i], 0, NULL);
  43.   AParam[i].Runing = bool(hThread);
  44.  
  45.   // Cierro el Handle
  46.   CloseHandle(hThread);
  47. }

Ejemplo de uso:


cpp
  1. void __fastcall TForm1::Button1Click(TObject *Sender)
  2. {
  3.   MoveDir("D:\\Copia (1) de Ejemplo", "D:\\Nueva1");
  4.   MoveDir("D:\\Copia (2) de Ejemplo", "D:\\Nueva2");
  5.   MoveDir("D:\\Copia (3) de Ejemplo", "D:\\Nueva3");
  6.   MoveDir("D:\\Copia (4) de Ejemplo", "D:\\Nueva4");
  7. }

Subo un proyecto de ejemplo.

 

Archivos adjuntos


  • 0




IP.Board spam blocked by CleanTalk.