// Programación en C++ para Ingenieros, Ed. Thomson Paraninfo, 2006 // Apéndice B: La Biblioteca CDAudioLib // Fichero cdaudio.cpp // Implementación CDAudioLib #include <cstdlib> #include "cdaudio.h" #include <windows.h> // Definición de un tipo denominado RECURSOSCDAUDIO // que permite mantener los recursos necesarios para // manipular el dispositivo CD de audio. Donde: // VentanaMensajes: mantiene la ventana a la que // se han de enviar los mensajes del dispositivo. // // wpCDAudio: mantiene la función de atención a los // mensajes de la ventana proporcionada por la // biblioteca CDAudioLib. // // wpOriginal: mantiene la función de atención original // de la aplicación que utiliza la biblioteca. // // hThread y hThreadID: mantienen la información del // hilo de ejecución que se crea cuando la aplicación // que utiliza la biblioteca no tiene ninguna ventana o // la función de atención está protegida y no se puede // sustituir por la de la biblioteca. // También se definen dos tipos aputador a un objeto de tipo // RECURSOSCDAUDIO. typedef struct tagPARAMCREATEREMOTO { HWND VentanaMensajes; CDAUDIOINTPROCEDURE intProcedure; WNDPROC wpCDAudio; WNDPROC wpOriginal; HANDLE hThread; DWORD hThreadID; } RECURSOSCDAUDIO,*PRECURSOSCDAUDIO,*LPRECURSOSCDAUDIO; // Variables para mantener la información de los // recursos, para almacenar los mensajes de error y // para recojer los resultados de los comandos enviados // al dispostivo. static RECURSOSCDAUDIO rcCDAudio; static char mensajeError[256]; static char resultado[256]; // Declaración de las funciones privadas para crear // y destruir, respectivamente, los recursos necesarios. bool DestruirRecursos(PRECURSOSCDAUDIO rcCDAudio); bool CrearRecursos(PRECURSOSCDAUDIO rcCDAudio); // Declaración de dos funciones privadas auxiliares que, respectivamente, // permiten consultar el resultado de un MCI Command String y la longitud // de un track en el formato de tiempo establecido. const char* Resultado(); bool LongTrack(int numTrack); // Declaración de la función de atención a los mensajes de ventana de // la biblioteca CDAudioLib. LRESULT CALLBACK CDAudioMensajesProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); // Declaración de dos funciones auxiliares necesarias para el caso // en que la aplicación que utiliza la biblioteca no tiene ventana // o la función de atención a los mensajes no se puede sustituir por // la de la biblioteca. // Funciones para crear la ventana y fución de atención a los mensajes. HWND CrearVentanaMensajes(HINSTANCE hinstance, HWND hwndParentWindow); bool CrearClaseVentanaMensajes(HINSTANCE hinstance, WNDPROC wp); LRESULT CALLBACK CDAudioWindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); // Thread que crea la ventana y procesa los mensajes. Recibe como // parámetro un apuntador a la variable que mantiene los recursos. DWORD WINAPI ThreadMensajes(LPVOID lpParameter); // Función para enviar un MCI Command String al dispositivo. bool EnviarComando(char *Comando, HWND hwnd) { MCIERROR error; // Se envia el comando utilizando la función mciSendString() error = mciSendString(Comando, resultado, sizeof resultado, hwnd); // Si se produce un error se obtiene el mensaje de error y se // almacena en la variable mensajeError para que pueda ser // consultado por el usuario utilizando la función MensajeError(). if(error) mciGetErrorString(error, mensajeError, sizeof mensajeError); return error; } // Implementación de las funciones del API, sus funcionalidades están // comentadas en el fichero de cabecera cdaudio.h bool OpenCDAudio(void) { if(rcCDAudio.VentanaMensajes != NULL) { // Si hay una ventana que procesa los mensajes del dispositivo // quiere decir que el dispositivo ya está abierto. wsprintf(mensajeError, "El dipositivo ya está abierto"); return true; } // El dispositivo no está abierto. // Se intenta crear los recursos necesarios. if(!CrearRecursos(&rcCDAudio)) { // Si por algún motivo no se pueden crear los recursos se // devuelve error. wsprintf(mensajeError, "No se han podido crear los recursos \ necesarios"); return true; } // Se han creado los recursos necesarios. // Se envía al dispositivo el comando de apertura. if(EnviarComando("open cdaudio wait", NULL)) { // Si se produce algún error en la apertura del dispositivo // se recoje el mensaje de error, se destruyen los recursos // y se devuelve error. wsprintf(mensajeError, "No se ha podido abrir el dispositivo"); DestruirRecursos(&rcCDAudio); return true; } // El dispositivo ya está abierto. // Se establece el formato de tiempo a tt:mm:ss:ff return EnviarComando("set cdaudio wait time format tmsf",NULL); } bool CloseCDAudio(void) { if(rcCDAudio.VentanaMensajes == NULL) { // Si no hay una ventana que procesa los mensajes del dispositivo // quiere decir que el dispositivo no está abierto. wsprintf(mensajeError, "El dipositivo no está abierto"); return true; } // El dispositivo está abierto. // Se detiene la reproducción. Stop(); // Se envía al dispositivo el comando para cerrarlo. if(EnviarComando("close cdaudio wait", NULL)) { // Si se produce algún error en el cierre del dispositivo // se recoje el mensaje de error y se devuelve error. wsprintf(mensajeError, "No se ha podido cerrar el dispositivo"); return true; } // El dispositivo se ha cerrado correctamente. // Se intenta liberar los recursos utilizados. if(!DestruirRecursos(&rcCDAudio)) { // Si por algún motivo no se pueden liberar los recursos se // devuelve error. wsprintf(mensajeError, "No se han podido liberar los \ recursos necesarios"); return true; } return false; } // El resto de funciones del API solamente crean el MCI Command String indicado // para la función y lo envían utilizando la función EnviarComando(), sus // funcionalidades están comentadas en el fichero de cabecera cdaudio.h. En el // caso de las funciones asíncronas se añade o no la opción notify dependiendo // de si el parámetro correspondiente a la función de atención es NULL o no. bool OpenDoorCD(void) { bool error; return EnviarComando("set cdaudio door open wait", NULL); } bool CloseDoorCD(void) { return EnviarComando("set cdaudio door closed", NULL); } bool Stop(void) { bool error; error = EnviarComando("stop cdaudio wait", NULL); if(!error) error = EnviarComando("seek cdaudio to start wait",NULL); } bool Pause(void){ return EnviarComando("pause cdaudio wait", NULL); } bool Play(CDAUDIOINTPROCEDURE intPro) { char Comando[128]; bool error; HWND hwnd = NULL; if(intPro != NULL) { rcCDAudio.intProcedure = intPro; wsprintf(Comando, "play cdaudio notify"); hwnd = rcCDAudio.VentanaMensajes; } else { wsprintf(Comando, "play cdaudio"); } error = EnviarComando(Comando,hwnd); return error; } bool PlayTrack(int numTrack, CDAUDIOINTPROCEDURE intPro) { char Comando[128]; HWND hwnd = NULL; bool error; error = LongTrack(numTrack); if(error) return error; if(intPro != NULL) { rcCDAudio.intProcedure = intPro; wsprintf(Comando, "play cdaudio from %u to %u:%s notify", numTrack,numTrack,Resultado()); hwnd = rcCDAudio.VentanaMensajes; } else { wsprintf(Comando, "play cdaudio from %u to %u:%s",numTrack, numTrack,Resultado()); } error = EnviarComando(Comando,hwnd); return error; } bool PlayTracksDesdeA(int primerTrack,int ultimoTrack, CDAUDIOINTPROCEDURE intPro){ char Comando[128]; HWND hwnd = NULL; bool error; error = LongTrack(ultimoTrack); if(error) return error; if(intPro != NULL) { rcCDAudio.intProcedure = intPro; wsprintf(Comando, "play cdaudio from %u to %u:%s notify",primerTrack, ultimoTrack,Resultado()); hwnd = rcCDAudio.VentanaMensajes; } else { wsprintf(Comando, "play cdaudio from %u to %u:%s",primerTrack, ultimoTrack,Resultado()); } return EnviarComando(Comando,hwnd); } int NumeroDeTracks(void) { bool error; error = EnviarComando("status cdaudio number of tracks", NULL); if(!error) return atoi(resultado); return 0; } int TrackActual(void) { bool error; error = EnviarComando("status cdaudio current track", NULL); if(!error) return atoi(resultado); return 0; } bool IsOpen(void) { return rcCDAudio.VentanaMensajes != NULL; } bool IsReady(void) { EnviarComando("status cdaudio mode", NULL); return strcmpi("not ready", Resultado()) != 0; } bool IsPlaying(void) { EnviarComando("status cdaudio mode", NULL); return strcmpi("playing", Resultado()) == 0; } bool IsStopped(void) { EnviarComando("status cdaudio mode", NULL); return strcmpi("stopped", Resultado()) == 0; } bool IsPaused(void) { EnviarComando("status cdaudio mode", NULL); return strcmpi("paused", Resultado()) == 0; } bool EsCDMedia(void) { EnviarComando("status cdaudio media present", NULL); return strcmpi("true", Resultado()) == 0; } bool Play(void) { return EnviarComando("play cdaudio wait",NULL); } bool PlayTrack(int numTrack) { char Comando[128]; bool error; error = LongTrack(numTrack); if(error) return error; wsprintf(Comando, "play cdaudio from %u to %u:%s wait",numTrack, numTrack,Resultado()); return EnviarComando(Comando,NULL); } bool PlayTracksDesdeA(int primerTrack, int ultimoTrack) { char Comando[128]; bool error; error = LongTrack(ultimoTrack); if(error) return error; wsprintf(Comando, "play cdaudio from %u to %u:%s wait",primerTrack, ultimoTrack,Resultado()); return EnviarComando(Comando,NULL); } bool Resume(CDAUDIOINTPROCEDURE intPro) { char Comando[128]; bool error; HWND hwnd = NULL; if(intPro != NULL) { rcCDAudio.intProcedure = intPro; wsprintf(Comando, "resume cdaudio notify"); hwnd = rcCDAudio.VentanaMensajes; } else { wsprintf(Comando, "resume cdaudio"); } error = EnviarComando(Comando,hwnd); return error; } const char* MensajeError() { return mensajeError; } // Implementación de las funciones privadas de la biblioteca CDAudioLib. // Función que permite consultar el resultado de un MCI Command String. const char* Resultado() { return resultado; } // Función que envía un MCI Command String al dispositivo para obtener la // longitud de un track en el formato de tiempo establecido. Para obtener el // resultado se ha de utilizar la función Resultado() después de llamar a // LongTrack(); bool LongTrack(int numTrack) { bool error; char Comando[128]; wsprintf(Comando, "status cdaudio length track %u", numTrack); error = EnviarComando(Comando, NULL); return error; } // Función para crear los recursos necesarios para el control y manipulación // del dispositivo. Devuelve true cuando se han podido crear todos los // recursos. En caso contrario devuelve false bool CrearRecursos(PRECURSOSCDAUDIO prcCDAudio) { // Se consultan la ventana activa y la última ventana utilizada de la // aplicación que utiliza la biblioteca. HWND hwndActiveWindow = GetActiveWindow(); HWND hwndForegroundWindow = GetForegroundWindow(); // Se escoje, como ventana a enviar los mensajes del dispositivo, en primer // lugar la activa y si no hay ventana activa la última. Si no hay ninguna // no se pueden crear los recursos. if(hwndActiveWindow != NULL) prcCDAudio->VentanaMensajes = hwndActiveWindow; else if(hwndForegroundWindow != NULL) prcCDAudio->VentanaMensajes = hwndForegroundWindow; else return false; // Se sustituye la función original de atención a los mensajes de ventana por // la de la biblioteca. prcCDAudio->wpOriginal = NULL; prcCDAudio->wpCDAudio = CDAudioMensajesProcedure; prcCDAudio->wpOriginal =(WNDPROC)SetWindowLongPtr(prcCDAudio->VentanaMensajes, GWL_WNDPROC, (LONG)prcCDAudio->wpCDAudio); // Si se ha podido realizar la sustitución ya no se necesitan más recursos y // se devuelve true. if(prcCDAudio->wpOriginal != NULL) return true; // Si no se ha podido realizar la sustitución se crea un hilo de ejecución // (thread) que creará una ventana para procesar los mensajes del // dispositivo y procesará los mensajes. prcCDAudio->hThread = CreateThread(NULL,0,ThreadMensajes,prcCDAudio,0, &prcCDAudio->hThreadID); // Esperamos que el thread cree la ventana y devolvemos si se ha podido // crear o no. WaitForSingleObject(prcCDAudio->hThread,200); return prcCDAudio->VentanaMensajes!=NULL; } // Función de la biblioteca CDAudioLib para atender los mensajes de la // ventana que sustituye a la función original. Si el mensaje es del // dispositivo se ejecuta la función de atención en caso de que se // haya definido en la petición asíncrona. Cuando el mensaje no es // del dispositivo llama a la función de atención original de la ventana. LRESULT CALLBACK CDAudioMensajesProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if(message == MM_MCINOTIFY) { if(rcCDAudio.intProcedure != NULL) { rcCDAudio.intProcedure(wParam); } return 0; } return CallWindowProc(rcCDAudio.wpOriginal,hwnd,message,wParam,lParam); } // Función de atención de los mensajes de ventana de la ventana que se crea // cuando no se puede sustituir la función original. LRESULT CALLBACK CDAudioWindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_PAINT: return 0; case MM_MCINOTIFY: if(rcCDAudio.intProcedure != NULL) { rcCDAudio.intProcedure(wParam); } return 0; case WM_DESTROY: PostQuitMessage (0); return 0; default: return DefWindowProc(hwnd,message,wParam,lParam); } } // Hilo de ejecución (thread) que se crea cuando no se puede sustituir la // función original de atención a los mensajes de ventana por la de la // biblioteca. Crea una ventana de mensajes (ventana oculta que solo sirve // para recibir mensajes) y procesa los mensajes hasta que recibe el mensaje // WM_DESTROY (que envía a la ventana la función DestruirRecursos()). Una vez // recibido el mensaje WM_DESTROY destruye la ventana y finaliza. DWORD WINAPI ThreadMensajes(LPVOID lpParameter) { PRECURSOSCDAUDIO prcCDAudio = (PRECURSOSCDAUDIO) lpParameter; MSG msg; HINSTANCE hinst; bool quit = false; hinst = (HINSTANCE) GetClassLong(prcCDAudio->VentanaMensajes,GCL_HMODULE); if(hinst == NULL) { prcCDAudio->VentanaMensajes = NULL; return 1; } if(!CrearClaseVentanaMensajes(hinst,CDAudioWindowProcedure)) { prcCDAudio->VentanaMensajes = NULL; return 2; } prcCDAudio->VentanaMensajes = CrearVentanaMensajes(hinst,HWND_MESSAGE); HWND hwnd = prcCDAudio->VentanaMensajes; if(hwnd == NULL) { UnregisterClass("VentanaMensajesClass",hinst); prcCDAudio->VentanaMensajes = NULL; return 3; } SendMessage(hwnd, WM_PAINT, (LPARAM)prcCDAudio, 1); while(GetMessage(&msg,NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } bool ok = DestroyWindow(prcCDAudio->VentanaMensajes); if(ok) { ok = UnregisterClass("VentanaMensajesClass",0); CloseHandle(prcCDAudio->VentanaMensajes); prcCDAudio->VentanaMensajes = NULL; prcCDAudio->wpOriginal = NULL; prcCDAudio->wpCDAudio = NULL; } return 0; } // Función que libera los recursos utilizados. Devuelve un valor lógico que // indica si se han podido liberar los recursos. bool DestruirRecursos(PRECURSOSCDAUDIO prcCDAudio) { // Si no hay ventana no hay recursos a liberar. if(prcCDAudio->VentanaMensajes == NULL) return false; // Si se había sustituido la función de atención original se reestablece, // se ponen las variables a NULL y se devuelve true. if(prcCDAudio->wpOriginal != NULL) { WNDPROC aux = (WNDPROC)SetWindowLongPtr(prcCDAudio->VentanaMensajes, GWL_WNDPROC, (LONG)prcCDAudio->wpOriginal); if (aux == NULL) return false; prcCDAudio->VentanaMensajes = NULL; prcCDAudio->wpOriginal = NULL; prcCDAudio->wpCDAudio = NULL; return true; } // Si no se había podido sustituir la función de atención se envia el // mensaje WM_DESTROY a la ventana creada por el thread. PostMessage(prcCDAudio->VentanaMensajes,WM_DESTROY,0,0); // Se espera a que finalize el thread y se devuelve si éste a podido // destruir la ventana. WaitForSingleObject(prcCDAudio->hThread,INFINITE); return prcCDAudio->VentanaMensajes == NULL; } // Funciones para crear una clase ventana y una ventana. Ver la API de // windows para consultar el significado de cada atributo. inline bool CrearClaseVentanaMensajes(HINSTANCE hinstance, WNDPROC wp) { WNDCLASSEX wincl; wincl.lpszClassName = "VentanaMensajesClass"; wincl.lpfnWndProc = wp; wincl.hInstance = hinstance; wincl.hCursor = NULL; wincl.hIcon = NULL; wincl.hbrBackground = NULL; wincl.lpszMenuName = NULL; wincl.style = 0; wincl.cbSize = sizeof (WNDCLASSEX); wincl.hIconSm = NULL; wincl.cbClsExtra = 0; wincl.cbWndExtra = 0; return RegisterClassEx (&wincl); } inline HWND CrearVentanaMensajes(HINSTANCE hinstance, HWND hwndParentWindow) { HWND hwnd = CreateWindowEx ( 0, "VentanaMensajesClass", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, hwndParentWindow, NULL, hinstance, NULL ); return hwnd; }