Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Abrir la aplicación una sola vez

Iniciado por fiero, 20 de Noviembre de 2007, 12:56:42 AM

« anterior - próximo »

fiero

Hola!

Estoy haciendo que mi aplicación solo se abra una vez, es decir, que al pulsar varias veces el ejecutable solo se abra una instancia. Para ello, registro la clase de la ventana principal, y en las siguientes ejecuciones compruebo con "FindWindow" si una ventana de la misma clase se está ejecutando, si es así, la segunda ventana trae al frente a la primera y termina la ejecución.

Quiero que la cosa funcione al estilo de por ejemplo ACDsee y similares. Es decir, si teneis ADCsee instalado (y configurado para que solo se abra una instancia) y haceis doble click en un JPG, se abrirá el programa. Al hacer click en otro fichero JPG, se cargará el nuevo JPG en la instancia de ACDsee que ya estaba abierta.

He intentado hacerlo pasando parametros de un programa a otro mediante mensajes. Al abrir la segunda instancia del programa, se le pasa a la primera instancia el nombre del nuevo fichero que se debe abrir y la segunda instancia se cierra. Esto funciona muy bien en XP, incluso si ejecutas varias instancias seleccionando varios EXEs a la vez o lo haces muy rápido, nunca falla. El problema es que esto no funciona en Vista (como no), no se si mi sistema de mensajes es una mala idea.

¿Alguien ha hecho esto alguna vez?
Gracias!
www.videopanoramas.com Videopanoramas 3D player

Jare

       case WM_COPYDATA:
       {
           const COPYDATASTRUCT *cd = (const COPYDATASTRUCT *)lParam;
           char buf[1000];
           sprintf(buf, "WM_COPYDATA\n dwData = %d\ncbData = %d\nlpData = '%s'", (int)cd->dwData, cd->cbData, cd->lpData);
           MessageBox(hWnd, buf, "Received", MB_OK);
           break;
       }

...

 HWND hwOther = FindWindow(wc.lpszClassName, NULL);
 if (hwOther)
 {
   COPYDATASTRUCT cd;
   cd.dwData = 0; // Some number identifying your command.
   cd.lpData = "This is a string message";
   cd.cbData = strlen((const char*)cd.lpData) + 1; // trailing \0
   SendMessage(hwOther, WM_COPYDATA, NULL, (LPARAM)&cd);
   return;
 }

Funciona en Vista, pero el metodo para detectar si la aplicacion ya esta corriendo puede fallar si las multiples copias arrancan suficientemente rapido.

fiero

Hola Jare,

Perdón por la tardanza en contestar, parece que la notificación de respuesta no me llegó al correo (quizás al spam) y pensé que este hilo se habia quedado sin contestar.

!Muchas gracias por la info! :)  Es exactamente lo que andaba buscando, voy a cambiar mis viejas funciones de mensajes por este WM_COPYDATA que parece que se inventó exactamente para eso.

saludos!
www.videopanoramas.com Videopanoramas 3D player

shephiroth

Buenas.

Se que probablemente sea mucho liar la perdiz, pero esto no se manejaria mejor con una clase singleton en una dll??

fiero

Hasta lo que yo sé (corregidme si me equivoco), una dll solo son datos, datos ejecutables, pero datos al fin y al cabo. Es decir, un proceso (exe) carga la dll y utiliza la clase singleton, después otro proceso carga la misma dll y utiliza la clase singleton. Los dos procesos son independientes y no tienen ninguna manera de comunicarse entre sí a través del código contenido en la dll, ya que la dll son datos que se cargan y se ejecutan en procesos separados.

Nunca he utilizado ni entendido un singleton. Creo que es una clase que solo se puede instanciar una vez, pero en el mismo proceso.

saludos!
www.videopanoramas.com Videopanoramas 3D player

Jare

Lo de la DLL efectivamente no es buena idea.

"Win32 DLLs are mapped into the address space of the calling process. By default, each process using a DLL has its own instance of all the DLLs global and static variables."

http://msdn2.microsoft.com/en-us/library/h90dkhs0(VS.80).aspx

Por cierto que un metodo mejor para detectar si la aplicacion ya existe, que siempre funcionara sin coñas de cuanto de rapido las arrancas, lo mencionan en la ayuda de WinMain:

http://msdn2.microsoft.com/en-us/library/ms633559.aspx

Me puse a experimentar un poco con el tema, asi que aqui va un tocho con funcionalidades mas avanzadas. Los comentarios deberian explicarlo todo; siento que esten en ingles, pero la costumbre es la costumbre. :)


#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
   switch( msg )
   {
       case WM_COPYDATA:
       {
           const COPYDATASTRUCT *cd = (const COPYDATASTRUCT *)lParam;
           char buf[1000];
           sprintf_s(buf, sizeof(buf), "WM_COPYDATA\n dwData = %d\ncbData = %d\nlpData = '%s'", (int)cd->dwData, cd->cbData, cd->lpData);
           MessageBox(hWnd, buf, "Received", MB_OK);
           OutputDebugString(buf);
           break;
       }
       case WM_DESTROY:
           PostQuitMessage( 0 );
           return 0;
   }
   return DefWindowProc( hWnd, msg, wParam, lParam );
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   static char WndClass[] = "WCTest";

   // Try to locate the other application a few times before giving up
   DWORD err;
   for (int i = 0; i < 10; ++i)
   {
       // "Local" indicates the current session, so multiple users on the same machine
       // will be able to run their own copy of the app.
       // You could use "Global" to enforce one copy of the app PER MACHINE.
       //    In that case, to communicate with the running app from another user
       //    you will likely need additional work with security descriptors (ugh)
       // If you don't specify either, it will behave as if you used "Local".
       // Remember to use a name that is unique to this application!
       HANDLE onlyOneMutex = CreateMutex(NULL, TRUE, "Local\\OnlyOnceMyApp"); // Or just use the WndClass string
       err = GetLastError();

       if (err == ERROR_ALREADY_EXISTS)
       {
           // Another copy of the app is running already and owns the Mutex.
           // Close the handle ASAP because we don't own the Mutex, but our handle would keep it alive
           // if the owner application exits right now.
           CloseHandle(onlyOneMutex);

           HWND hwOther = FindWindow(WndClass, NULL);
           if (hwOther)
           {
               // Show the existing app's window (restore if it's minimized).
               if (IsIconic(hwOther))
                   ShowWindow(hwOther, SW_RESTORE);
               SetForegroundWindow(hwOther);
               // We found the other app's window, send it the command and finish.
               COPYDATASTRUCT cd;
               cd.dwData = 0; // Some number identifying your command.
               cd.lpData = "This is a string message";
               cd.cbData = (DWORD)strlen((const char*)cd.lpData) + 1; // trailing \0
               SendMessage(hwOther, WM_COPYDATA, NULL, (LPARAM)&cd);
               return 0;
           }
       }
       else
       {
           // The app is not currently running, no need to retry
           break;
       }
       Sleep(500); // Wait a bit and retry
   }

   // Verify what really happened
   if (err == ERROR_ALREADY_EXISTS)
   {
       // The app is already running but it doesn't answer (maybe it crashed)
       MessageBox(NULL, "Application is already running but doesn't listen", "Oops", MB_OK);
       return 1;
   }
   else if (err == ERROR_ACCESS_DENIED)
   {
       // The app is already running but we don't have permissions to access it
       MessageBox(NULL, "We lack permissions to access the running application", "Oops", MB_OK);
       return 1;
   }
   else if (err != ERROR_SUCCESS)
   {
       // We can't create the handle, something horrible happens
       MessageBox(NULL, "We suck", "Oops", MB_OK);
       return 1;
   }

   // Use this for testing to force the application to delay creating the main window.
   MessageBox(NULL, "Before creating the window", "delay", MB_OK);

WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, MsgProc, 0L, 0L,
 GetModuleHandle(NULL), NULL, LoadCursor(NULL, IDC_ARROW), NULL,
  NULL, WndClass, NULL };
RegisterClassEx( &wc );

HWND hWnd = CreateWindow( wc.lpszClassName, "WC Test",
 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
 NULL, NULL, wc.hInstance, NULL );
   ShowWindow(hWnd, SW_SHOW);
   UpdateWindow(hWnd);

   MSG msg = { 0 };
   while ( GetMessage( &msg, NULL, 0, 0) )
   {
       TranslateMessage( &msg );
       DispatchMessage( &msg );
   }

   // Use this for testing, to force the application to delay exiting
   MessageBox(NULL, "After destroying the window", "delay", MB_OK);

   return (int)msg.wParam;
}


Hale.






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.