Приложение MCICDPL
Если вы будете разрабатывать проигрыватель звуковых компакт-дисков, то можете взять за основу приложение MCICDPL (рис. 3.1), которое работает с устройством чтения CD-ROM при помощи управляющих сообщений MCI.
Рис. 3.1. Главное окно приложения MCICDPL
Исходный текст приложения представлен в листинге 3.1.
Листинг 3.1. Файл mcicdpl/mcicdpl.cpp
// ---------------------------------------- // Проигрыватель звуковых компакт-дисков // ----------------------------------------
#define STRICT #include <windows.h> #include <mmsystem.h> #include <mem.h> #include <stdlib.h>
#include "mcicdpl.hpp"
#define CD_EMPTY 0 #define CD_READY 1 #define CD_PLAYING 2 #define CD_PAUSED 3
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void mciwioError(DWORD dwrc); void Play(HWND hwnd, UINT nTrack);
// Имя класса окна char const szClassName[] = "MCICDP";
// Заголовок окна char const szWindowTitle[] = "MCI CD Player";
HINSTANCE hInst;
DWORD dwrc; UINT nTimerID;
MCI_OPEN_PARMS MCIOpen; MCI_SET_PARMS MCISet; MCI_STATUS_PARMS MCIStatus; MCI_PLAY_PARMS MCIPlay;
BOOL bMediaPresent = FALSE; BOOL bPaused = FALSE; UINT nMode = 0; UINT nCurTrack = 0; UINT nTrackCnt = 0;
HWND hwndCurTrack = NULL;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
if(hPrevInstance) return FALSE;
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
hInst = hInstance;
// Открываем устройство чтения компакт-дисков MCIOpen.lpstrDeviceType = (LPSTR)MCI_DEVTYPE_CD_AUDIO; dwrc = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD)(LPVOID)&MCIOpen); if(dwrc) { mciwioError(dwrc); return -1; }
// Устанавливаем формат времени MCISet.dwTimeFormat = MCI_FORMAT_TMSF; dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)(LPVOID)&MCISet); if(dwrc) { mciwioError(dwrc); return -1; }
// Создаем диалоговую панель вместо главного окна hwnd = CreateDialog(hInstance, szClassName, 0, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Определяем идентификатор поля, которое используется // для отображения номера текущей дорожки hwndCurTrack = GetDlgItem(hwnd, IDT_CURTRACK);
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, NULL, 0, 0)) { if((hwnd == 0) (!IsDialogMessage(hwnd, &msg))) DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = DLGWINDOWEXTRA; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { // --------------------------------------- // Обработчик сообщения WM_CREATE // --------------------------------------- case WM_CREATE: { // Создаем таймер, который нужен для периодического // определения состояния устройства чтения CD nTimerID = SetTimer(hwnd, 1, 1000, NULL);
return 0; }
// --------------------------------------- // Обработчик сообщения WM_COMMAND // --------------------------------------- case WM_COMMAND: { switch(wParam) { // Запуск режима проигрывания case IDB_PLAY: { // Если в проигрывателе есть компакт-диск, // запускаем проигрывание if(bMediaPresent) Play(hwnd, 1); return 0; }
// Останов проигрывания case IDB_STOP: { if(bMediaPresent) { bPaused = FALSE; nCurTrack = 0; mciSendCommand(MCIOpen.wDeviceID, MCI_STOP, NULL, NULL); } return 0; }
// Временный останов проигрывания case IDB_PAUSE: { if(bMediaPresent) { if(!bPaused) { bPaused = TRUE; mciSendCommand(MCIOpen.wDeviceID, MCI_PAUSE, NULL, NULL); } } return 0; }
// Продолжение проигрывания после // временного останова case IDB_RESUME: { if(bMediaPresent) { if(bPaused) { bPaused = FALSE; MCIPlay.dwCallback = (DWORD)hwnd; mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID)&MCIPlay); } } return 0; }
// Позиционирование на следующую дорожку case IDB_NEXT: { if(bMediaPresent) { UINT nNewTrack;
// Если текущая дорожка - последняя, // начинаем проигрывание с первой дорожки. // Если нет - проигрываем следующую дорожку if(nCurTrack == nTrackCnt) nNewTrack = 1; else nNewTrack = nCurTrack + 1;
Play(hwnd, nNewTrack); } return 0; }
// Позиционирование на предыдущую дорожку case IDB_PREV: { if(bMediaPresent) { UINT nNewTrack;
// Если текущая дорожка - первая, // проигрываем последнюю дорожку if(nCurTrack <= 1) nNewTrack = nTrackCnt; else nNewTrack = nCurTrack - 1;
Play(hwnd, nNewTrack); } return 0; }
// Завершаем работу приложения case IDOK: case IDCANCEL: { SendMessage(hwnd, WM_CLOSE, 0, 0L); return 0; }
// Выполняем команду извлечения диска из // устройства чтения case IDB_EJECT: { mciSendCommand(MCIOpen.wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, NULL); return 0; } } }
// --------------------------------------- // Обработчик сообщения WM_TIMER // --------------------------------------- case WM_TIMER: { UINT nCurMode;
// Если окно свернуто в пиктограмму, ничего не делаем, // чтобы не снижать производительность системы if(IsIconic(hwnd)) return 0;
// Определяем текущее состояние проигрывателя CD MCIStatus.dwItem = MCI_STATUS_MODE; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)(LPVOID)&MCIStatus);
// Проверяем, готово ли устройство чтения к работе if((MCIStatus.dwReturn == MCI_MODE_NOT_READY) (MCIStatus.dwReturn == MCI_MODE_OPEN)) { // Устройство не готово nCurMode = CD_EMPTY; } else if((MCIStatus.dwReturn == MCI_MODE_STOP) && bPaused) { // Устройство остановлено nCurMode = CD_PAUSED; } else if(MCIStatus.dwReturn == MCI_MODE_PLAY) { // Устройство находится в режиме проигрывания nCurMode = CD_PLAYING; } else { // Устройство готово nCurMode = CD_READY; }
// Если с момента последней проверки произошло // изменение режима, записываем код нового режима if(nMode != nCurMode) { nMode = nCurMode; }
// Проверяем, вставлен ли компакт-диск MCIStatus.dwItem = MCI_STATUS_MEDIA_PRESENT; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)(LPVOID)&MCIStatus);
// Если компакт-диск вставлен, определяем // количество звуковых дорожек if((!bMediaPresent) && MCIStatus.dwReturn) { bMediaPresent = TRUE; bPaused = FALSE; nCurTrack = 0;
MCIStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)(LPVOID)&MCIStatus);
nTrackCnt = MCIStatus.dwReturn; }
// Если компакт-диск не вставлен, сбрасываем // номер текущей дорожке в поле диалоговой панели else if((bMediaPresent) && !MCIStatus.dwReturn) { bMediaPresent = FALSE; bPaused = FALSE; SetWindowText(hwndCurTrack, (LPSTR)""); }
// Если приложение находится в режиме проигрывания, // определяем номер текущей дорожки if(nCurMode == CD_PLAYING) { // Определяем текущую позицию MCIStatus.dwItem = MCI_STATUS_POSITION; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)(LPVOID)&MCIStatus);
// Если номер дорожки изменился, отображаем новое // значение в соответствующем поле диалоговой панели if(nCurTrack != (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn)) { BYTE szBuf[20]; nCurTrack = (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn); SetWindowText(hwndCurTrack, itoa(nCurTrack, szBuf, 10)); } } return 0; }
// --------------------------------------- // Обработчик сообщения MM_MCINOTIFY // --------------------------------------- case MM_MCINOTIFY: { if(wParam == MCI_NOTIFY_SUCCESSFUL) { if(bMediaPresent) Play(hwnd, 1); } return 0; }
// --------------------------------------- // Обработчик сообщения WM_CLOSE // --------------------------------------- case WM_CLOSE: { DestroyWindow(hwnd); return 0; }
// --------------------------------------- // Обработчик сообщения WM_DESTROY // --------------------------------------- case WM_DESTROY: { // Закрываем устройство чтения компакт-дисков dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_CLOSE, NULL, NULL); if(dwrc) mciwioError(dwrc);
// Уничтожаем таймер KillTimer(hwnd, nTimerID);
PostQuitMessage(0); return 0; } } return DefDlgProc(hwnd, msg, wParam, lParam); }
//----------------------------------------------------- // mciwioError // Обработка ошибок //----------------------------------------------------- void mciwioError(DWORD dwrc) { BYTE szBuf[MAXERRORLENGTH];
if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH)) MessageBox(NULL, szBuf, "MCIWAVE Error", MB_ICONEXCLAMATION); else MessageBox(NULL, "Неизвестная ошибка", "MCIWAVE Error", MB_ICONEXCLAMATION); }
//----------------------------------------------------- // Play // Запуск проигрывания дорожки //----------------------------------------------------- void Play(HWND hwnd, UINT nTrack) { bPaused = FALSE;
MCIPlay.dwCallback = (DWORD)hwnd; MCIPlay.dwFrom = MCI_MAKE_TMSF(nTrack, 0, 0, 0);
dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD)(LPVOID)&MCIPlay); if(dwrc) { mciwioError(dwrc); return; } }
Особенностью данного приложения является отсутствие главного окна - его роль выполняет диалоговая панель.
Сразу после запуска приложение пытается открыть устройство чтения компакт-дисков, и если в системе нет соответствующего драйвера, приложение завершает свою работу с сообщением об ошибке.
Далее устанавливается формат времени MCI_FORMAT_TMSF, так как приложение будет выполнять позиционирование по дорожкам компакт-диска.
Далее с помощью функции CreateDialog создается диалоговая панель, при этом указывается зарегистрированный приложением класс окна szClassName (строка "MCICDP"):
hwnd = CreateDialog(hInstance, szClassName, 0, NULL);
Для того чтобы функция окна могла получать сообщения от диалоговой панели, в описании шаблона диалоговой панели используется оператор CLASS :
CLASS "MCICDP"
В шаблоне предусмотрен статический орган управления, который имеет идентификатор IDT_CURTRACK и используется для отображения номера текущего трека. Перед запуском цикла обработки сообщений приложение определяет его идентификатор и сохраняет в переменной hwndCurTrack:
hwndCurTrack = GetDlgItem(hwnd, IDT_CURTRACK);
Затем диалоговая панель отображается на экране и запускается цикл обработки сообщений, в котором вызывается функция IsDialogMessage:
while(GetMessage(&msg, NULL, 0, 0)) { if((hwnd == 0) (!IsDialogMessage(hwnd, &msg))) DispatchMessage(&msg); }
Во время обработки сообщения WM_CREATE создается таймер с периодом 1 секунда. Этот таймер будет использоваться для определения текущего состояния устройства чтения компакт-дисков.
Если нажать на кнопку "Play", функция окна получит сообщение WM_COMMAND с параметром IDB_PLAY. При этом приложение проверит состояние флага bMediaPresent (наличие компакт-диска в устройстве) и, если этот флаг установлен, запустит проигрывание первой дорожки. Содержимое флага bMediaPresent периодически обновляется в соответствии с действительным состоянием устройства обработчиком сообщений таймера.
Кнопка "Stop" позволяет остановить процесс проигрывания. При этом устройству посылается команда MCI_STOP. Алогично, кнопка "Pause" выполняет временный останов, соответствующий обработчик посылает управляющее сообщение с кодом MCI_PAUSE. Для продолжения проигрывания после временного останова используется команда MCI_PLAY, для которой не задается начальная позиция (команда MCI_RESUME не поддерживается драйвером устройства чтения CD ROM). В этом случае проигрывание возобновляется с текущей позиции, то есть с прерванного места.
Для выполнения операции позиционирования на следующую или предыдущую дорожку вычисляется номер следующей дорожки исходя из номера текущей дорожки. Если новый номер дорожки меньше 1 или больше максимального, выполняется переход, соответственно, на последнюю или первую дорожку компакт-диска.
Обработчик сообщения таймера проверяет, не находится ли окно приложения (диалоговая панель) в свернутом виде. Если пользователь свернул окно в пиктограмму, нет смысла определять текущее состояние устройства, поэтому для увеличения общей производительности системы обработчик сообщения таймера в этом случае просто возвращает управление.
Код текущего состояния устройства записывается в переменную nMode. Далее обработчик сообщения WM_TIMER с помощью команды MCI_STATUS проверяет, вставлен ли в устройство компакт-диск. Если диск вставлен, определяется количество дорожек. определенное значение сохраняется в переменной nTrackCnt.
Номер текущей дорожки также определяется каждый раз при обработке сообщения таймера (при условии, что компакт-диск вставлен в устройство и устройство находится в режиме проигрывания). Если этот номер изменился, новое значение отображается в статическом органе управления диалоговой панели с идентификатором hwndCurTrack:
if(nCurTrack != (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn)) { BYTE szBuf[20]; nCurTrack = (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn); SetWindowText(hwndCurTrack, itoa(nCurTrack, szBuf, 10)); }
Макрокоманда MCI_TMSF_TRACK извлекает номер дорожки из значения, возвращенного командой MCI_STATUS с параметром MCI_STATUS_POSITION.
В файле mmsystem.h определены и другие макрокоманды, которые используются аналогичным образом для получения других полей: MCI_TMSF_FRAME , MCI_TMSF_MINUTE , MCI_TMSF_SECOND , MCI_MSF_FRAME , MCI_MSF_MINUTE , MCI_MSF_SECOND . Можно сделать и обратные преобразования. Например, можно использовать макрокоманду MCI_MAKE_TMSF для упаковки в двойное слово номера дорожки, минут, секунд и номера фрейма:
dwFormat = MCI_MAKE_TMSF(track, min, sec, frame);
В нашем приложении предусмотрена обработка сообщения MM_MCINOTIFY . Это сообщение используется для того чтобы "зациклить" проигрывание компакт-диска. После того как команда проигрывания будет выполнена до конца (то есть после того как будет завершено проигрывание последней дорожки компакт-диска), функция окна приложения получит сообщение MM_MCINOTIFY с параметром MCI_NOTIFY_SUCCESSFUL. Обработчик этого сообщения выглядит очень просто - он запускает проигрывание заново с первой дорожки:
case MM_MCINOTIFY: { if(wParam == MCI_NOTIFY_SUCCESSFUL) { if(bMediaPresent) Play(hwnd, 1); } return 0; }
При завершении работы приложения обработчик сообщения WM_DESTROY закрывает устройство чтения компакт-дисков и уничтожает таймер.
Функция Play, определенная в нашем приложении, запускает проигрывание компакт-диска начиная с заданной дорожки. В ней для формирования позиции используется макрокоманда MCI_MAKE_TMSF :
MCIPlay.dwFrom = MCI_MAKE_TMSF(nTrack, 0, 0, 0);
Все параметры, кроме первого, содержат нулевые значения, поэтому проигрывание будет запущено с самого начала дорожки, имеющей номер nTrack.
Файл mcicdpl.hpp (листинг 3.2) содержит определения констант, используемых в приложении.
Листинг 3.2. Файл mcicdpl/mcicdpl.hpp
#define IDT_CURTRACK 200 #define IDB_STOP 101 #define IDB_PAUSE 102 #define IDB_RESUME 103 #define IDB_NEXT 104 #define IDB_PREV 105 #define IDB_EJECT 106 #define IDB_PLAY 100
Файл описания ресурсов приложения представлен в листинге 3.3. Он содержит определение пиктограммы и диалоговой панели, выступающей в роли главного окна приложения.
Листинг 3.3. Файл mcicdpl/mcicdpl.rc
#include "g:\tcwin\include\windows.h" #include "mcicdpl.hpp"
APPICON ICON "mcicdpl.ico"
MCICDP DIALOG 45, 20, 153, 57 STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX CLASS "MCICDP" CAPTION "Compact Disk Player" BEGIN PUSHBUTTON "Play", IDB_PLAY, 84, 11, 31, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "Stop", IDB_STOP, 118, 11, 31, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "Pause", IDB_PAUSE, 84, 26, 31, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "Resume", IDB_RESUME, 118, 26, 31, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON ">>I", IDB_NEXT, 6, 26, 31, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "I<<", IDB_PREV, 40, 26, 31, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "Eject", IDB_EJECT, 6, 41, 65, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP DEFPUSHBUTTON "Exit", IDOK, 84, 41, 65, 11, WS_CHILD | WS_VISIBLE | WS_TABSTOP LTEXT "Track:", -1, 12, 7, 35, 8, WS_CHILD | WS_VISIBLE | WS_GROUP LTEXT "00", IDT_CURTRACK, 35, 7, 16, 8, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", -1, "static", SS_BLACKFRAME | WS_CHILD | WS_VISIBLE, 6, 4, 65, 15 END
Файл определения модуля приложения MCICDPL представлен в листинге 3.4.
Листинг 3.4. Файл mcicdpl/mcicdpl.def
NAME MCICDPL DESCRIPTION 'Приложение MCICDPL, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8194 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple