Внутреннее устройство приложения Mac для разработчика Windows: часть 1.

Все началось с программы hello world в win32, и это было моим введением в создание приложений с графическим интерфейсом.

Первоначальным источником вдохновения для него послужил Чарльз Петцольд и его легендарная книга Окна программирования. Программа Hello world из Первой главы, вероятно, является наиболее упрощенным представлением программы с графическим интерфейсом.
Псевдокод будет выглядеть так, как показано ниже.

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                       _In_opt_ HINSTANCE hPrevInstance,
                       _In_ LPTSTR    lpCmdLine,
                       _In_ int       nCmdShow) {
     RegisterClass(hInstance);
     // Creates a WNDCLASS structure basically the NSWindow kind of        
     // main class
     if (!InitInstance (hInstance, nCmdShow))   {     
        return FALSE;      
     }
//Then there comes the (message loop) == NSRunloop
   while(GetMessage(&msg, NULL, 0, 0)) {
       TranslateMessage(&msg);
       DispatchMessage(&msg);
   }    
   exit(msg.wParam);
}

Dispatch Message находит окно и соответствующий WindowProc и отправляет туда сообщение, которое будет обработано, что-то вроде this.

Mac OS X также имеет аналогичную структуру с другой реализацией. Итак, я начал писать простое приложение с нажатием кнопки и кнопки, чтобы понять, могу ли я имитировать программу win32, а также, возможно, понять основы оконной системы в mac os x.

Для навигации по этой статье я использовал два образца.
a. Приложение UIapplicationbareBone: это приложение не использует никаких файлов .xib, plist просто использует стандартную функцию main и цикл сообщений, как показано выше.
b. Приложение SampleBareBone: это приложение заменяет стандартный класс NSApplication на MyApplication, который реализует функцию запуска, имеющую цикл сообщений.

Первый фрагмент кода приложения показан ниже.

Как показано выше, первая строка - это вызов sharedApplication.
Выполняет следующие действия.
1. InitializeAppContext, как показано ниже, устанавливает eventHandlers.

0x7fff3bca7201 <+141>: callq  0x7fff3c8b08c8            ; symbol stub for: InstallEventHandler
0x7fff3bca7206 <+146>: callq  0x7fff3bca7c42            ; _NSInstallCocoaAppApplicationLevelEventHandlers
0x7fff3bca720b <+151>: movl   0x5c623c9f(%rip), %eax    ; _cgsContextID

2. Добавляет CFRunLoopSource пытается зарегистрироваться в цикле выполнения, который может быть запущен извне для работы с событием. Apple docs на самом деле дает очень точное описание того же. Будет несколько вызовов CFRunLoopSource из [NSApplication sharedApplication] каждый раз, когда добавляется источник для другой части стека приложений, трассировки ниже показывают то же самое.

Еще один интересный факт о графической оболочке оконных систем привязан к серверу Windows. В Windows это проводник Windows, в Mac OS x - Finder (когда мы завершаем процесс поиска, вы увидите весь экран обновляется при перезапуске сервера Windows, то же самое происходит с проводником в Windows.), а для IOS это трамплин. По словам Джонатана Левина, автора книги Mac Os X and IOS internals говорится о нескольких серверах Windows на Mac OS X, чтобы снизить нагрузку на сервер окон.

В любом случае, давайте продолжим путешествие, а теперь пора обсудить цикл сообщений.
Как мы видим в приведенном выше приложении, цикл сообщений довольно прост.
На самом деле [NSApplication run ] делает то же самое, что и в приведенном выше фрагменте кода.

Здесь вступает в игру второе приложение, здесь я заменил класс NSApplication классом MyApplication, реализующим функцию Выполнить. Код будет таким, как показано ниже.

Эта функция [self nextEventMatchingMask: untilDate: inMode: dequeue:] фактически завершает вызов функции CFRunloopRun, которая реализует фактически перекачку / цикл сообщений.

Итак, давайте начнем смотреть, как, к счастью, реализован CFRunLoopRun, исходный код которого для краткости открыт. Я попытался частично показать реализацию и объяснить то же самое.

Давайте разберемся, что такое Runloop в первую очередь согласно документации по яблокам.

Объект CFRunLoop отслеживает источники ввода для задачи и отправляет управление, когда они становятся готовыми к обработке. Примеры источников ввода могут включать пользовательские устройства ввода, сетевые соединения, периодические или отложенные по времени события и асинхронные обратные вызовы.

Он может отслеживать три типа объектов
a. Источники
б. Таймеры
c. Наблюдатели
Итак, согласно утверждению, мы можем ожидать, что такая же работа будет проделана в псевдокоде для CFRunloop. Давайте углубимся, цикл while начинается с
CFRunLoopDoObservers для таймеров и источников, наблюдаемых ранее в обеих функциях, это отправит уведомления до таймеры / источники обрабатываются.

Затем вызывается __CFRunLoopServiceMachPort, и извлекается сообщение типа mach_msg_header_t вместе с livePort типа mach_port_t.

В зависимости от типа порта, конкретная функция берет на себя обслуживание сообщения, большая часть заканчивается на mach_msg.
mach_msg, структура которого показана здесь, выглядит как rpc вид структуры, параметр опции служит указателем направления MACH_SEND_MSG и MACH_RCV_MSG

mach_msg_return_t   mach_msg
                    (mach_msg_header_t                msg,
                     mach_msg_option_t             option,
                     mach_msg_size_t            send_size,
                     mach_msg_size_t        receive_limit,
                     mach_port_t             receive_name,
                     mach_msg_timeout_t           timeout,
                     mach_port_t                   notify);

mach_msg_return_t   mach_msg_overwrite
                    (mach_msg_header_t*          send_msg,
                     mach_msg_option_t             option,
                     mach_msg_size_t            send_size,
                     mach_msg_size_t        receive_limit,
                     mach_port_t             receive_name,
                     mach_msg_timeout_t           timeout,
                     mach_port_t                   notify,
                     mach_msg_header_t       *receive_msg,
                     mach_msg_size_t     receive_msg_size);

Интересно то, что происходит после вызова mach_msg, что привело меня к другой точке останова и еще одной трассировке стека, которую я усек.

br s -n mach_msg

он заканчивается, как мы видим в mach_msg_trap, который является точкой перехода в режим ядра (это то, как далеко я могу пройти).

Нет, что делает [NSApp sendEvent: event]? Здесь, как указано в документации, начинается цепочка респондентов и t его статья заканчивается!
Я вернусь с дополнительным исследованием цепочки респондентов в моем следующем посте < br /> Спасибо, что прошли через это.

Ссылки и инструменты:

  1. Http://www.charlespetzold.com/blog/2014/12/The-Infamous-Windows-Hello-World-Program.html
  2. Https://github.com/opensource-apple/CF/blob/master/CFRunLoop.c
  3. отладчик lldb
  4. Дезассемблер бункера