Вызываем функции Windows API (и любые другие функции, написанные на языке Си) джаваскриптом из Node.js

  • Tutorial
Со вчерашнего дня, господа, можно написать вот такой скрипт:

// функция преобразования строки JavaScript (UTF-8) в UTF-16
function TEXT(text){
   return new Buffer(text, 'ucs2').toString('binary');
}

var FFI = require('node-ffi');

// подключаемся к user32.dll
var user32 = new FFI.Library('user32', {
   'MessageBoxW': [
      'int32', [ 'int32', 'string', 'string', 'int32' ]
   ]
});

// диалоговое окно
var OK_or_Cancel = user32.MessageBoxW(
   0, TEXT('Привет, Хабрахабр!'), TEXT('Заголовок окна'), 1
);

и, запустив его в Windows, получить желаемый результат — диалоговое окно Windows.

Это стало возможным потому, что модуль node-ffi (обёртку вокруг той необыкновенно полезной библиотеки libffi, которая используется для вызова библиотек на языке Си не менее чем в восьми других языках) вчера портировали на Windows.

Следует, разумеется, помнить о том, что в Windows API используются строки в формате UTF-16, а джаваскриптовые строки Node.js хранит в UTF-8. Воплощением этой разницы является в моём примере преобразователь TEXT() аналог одноимённого макро, описанного в MSDN.

Ясное дело, что на Windows API свет клином не сошёлся: модуль node-ffi можно использовать для вызова какой угодно библиотеки, написанной на языке Си или соблюдающей принятый в Си способ вызова функций (например, в Си++ это достигается директивою extern "C").

Что означает этот шаг эволюции возможностей Node.js?

Напомню предсказание, сделанное мною меньше месяца назад — шестнадцатого декабря 2011 года:
В настоящее время будущее Node.js ещё не достаточно лучезарно, поскольку ещё только ≈1½ месяца этот движок существует не только под Linux, под Мак и под Соляркою, но также и под Windows.

Следует ожидать мощного синергического толчка в тот момент, когда код модулей для Node.js начнут сочинять также и те разработчики на JavaScript, у которых на рабочем компьютере стоит Windows. По моим оценкам, для этого не достаточно портировать на Windows один только сам движок Node.js; потребуется также, по меньшей мере, вот что:
  • Плавная работа пакетного менеджера npm. В частности, будет необыкновенном полезным появление у разработчиков модулей возможности поставлять заранее скомпилированные модули для win32 и win64: нельзя же полагаться на то, что у каждого конечного пользователя стоят средства разработки (например, Visual Studio 2010 Express). Понятно, что и разработчики модулей должны взяться за ум, а не то даже команду npm install zip нельзя под Windows подать без того, чтобы наткнуться на симлинк внутри тарболла. (Или автор скрипта npm/lib/utils/tar.js мог бы получше предусмотреть это.)
      
  • Появление возможности оскриптовывания произвольной системной библиотеки. (Появится, вероятно, после портирования node-ffi на Windows.) Только отсюда протянется тропка к сотворению GUI.
      
  • Появление возможности работать с БД файлового (а не клиент-серверного) типа. (Появится, вероятно, после портирования node-sqlite3 на Windows.)
И месяца не прошло, как второй из этих трёх пунктов реализовался. Любая библиотека может быть оскриптована джаваскриптами. Соответственно, приложениям на Node.js под Windows стала доступна вся мощь системных API, а кросс-платформенные приложения могут невозбранно обращаться ко кросс-платформенным библиотекам, таская их с собою.

Первый из этих трёх пунктов (касавшийся как раз таскания библиотек с собою) не реализовался в том виде, в каком я мечтал о нём: пакетный менеджер npm всё ещё не обеспечивает возможность доставки только того скомпилированного кода, который нужен конкретной системе. Но у меня появился новый повод для оптимизма после того, как я увидел, что разработчикам модуля node-ffi удалось обойти нехватку такой возможности. Их модуль содержит один и тот же код, скомпилированный под полдесятка различных платформ:
compiled/darwin/x64/ffi_bindings.node
compiled/linux/ia32/ffi_bindings.node
compiled/linux/x64/ffi_bindings.node
compiled/sunos/ia32/ffi_bindings.node
compiled/win32/ia32/ffi_bindings.node
а скрипт lib/bindings.js во время вызова модуля node-ffi оглядывается, соображая, куда же попал он, и подбирает нужный двоичный код:

var join = require('path').join
  , bindings = 'ffi_bindings.node'

function requireTry () {

  var i = 0
    , l = arguments.length
    , n

  for (; i<l; i++) {
    n = arguments[i]
    try {
      var b = require(n)
      b.path = n
      return b
    } catch (e) {
      if (!/not find/i.test(e.message)) {
        throw e
      }
    }
  }

  throw new Error('Could not load the bindings file. Tried:\n' +
      [].slice.call(arguments).map(function (a) { return '  - ' + a }).join('\n'))
}

module.exports = requireTry(
    // Production "Release" buildtype binary
    join(__dirname, '..', 'compiled', process.platform, process.arch, bindings)
    // Release files, but manually compiled
  , join(__dirname, '..', 'out', 'Release', bindings) // Unix
  , join(__dirname, '..', 'Release', bindings)        // Windows
    // Debug files, for development
  , join(__dirname, '..', 'out', 'Debug', bindings)   // Unix
  , join(__dirname, '..', 'Debug', bindings)          // Windows
)

Как видно, работает ничуть не хуже; папка «compiled» вот только занимает ≈700 килобайтов, но при нынешних объёмах дисков и скоростях сетей об этом можно позабыть.

Итак, остаётся дождаться доступа к SQLite — и Node.js под Windows станет платформою, пригодною для разработки на JavaScript множества фактически полезных приложений. Но с учётом вышесказанного можно считать, что кросс-платформенная разработка модулей-обёрток упростилася; стало быть, нам недолго осталось ждать и обёртку вокруг SQLite, и появления модулей для интеграции Node.js с целым рядом других известных кросс-платформенных библиотек.
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 33
  • +5
    весело
    • +2
      Учитывая, что с помощью node.js можно создавать не только сайты, но и всякие логеры/демоны и т.п., то думаю тут невозможно не согласиться с автором о том, что
      >Node.js под Windows станет платформою, пригодною для разработки на JavaScript множества фактически полезных приложений.

      Всем кто не видел доклад «JavaScript на сервере – node.js на Windows» с HTML5 Camp советую это сделать
      • +1
        Там докладчик всё же настроен достаточно скептически, и даже на тридцать четвёртой минуте доклада (≈33:27) тоном ha ha only serious признаётся, что презентация о том, почему не надо использовать Node.js.
        • –1
          Как бы там ни было, но из данного доклада лично я для себя узнал много нового.
      • +1
        Понятно, что и разработчики модулей должны взяться за ум, а не то даже команду npm install zip нельзя под Windows подать без того, чтобы наткнуться на симлинк внутри тарболла.

        и в чём же проблема симлинков?
        • 0
          Рассказываю: в начале декабря наблюдались какие-то неприятные глюки при распаковке симлинков из тарболла под Windows менеджером пакетов npm, и я немало досадовал на тех разработчиков пакетов, которым это было всё равно: раз уж используют npm, то могли бы и призадуматься над обходом его глюков, так я думал.

          Однако, по доброй иронии судьбы, это моё воззрение успело устареть в тот же день 16 декабря 2011 года, когда оно было оглашено. Разработчик менеджера пакетов npm внёс необходимые изменения в код своего продукта. (Правда, выпуск npm с этими изменениями cостоялся несколькими днями позже 16 декабря, если мне память не изменяет.)

          Теперь команда npm install zip срабатывает невозбранно, например.
        • –1
          Запасаемся печенюшками и чаем, по изучению новых разновидностей вирусов :)
          • +1
            Не понимаю, чем спровоцировано это высказывание. Возможности написания вируса на Node.js никак не ограничивались прежним отсутствием работающего модуля node-ffi под Windows. Движок Node.js сам собою может и обращаться к файловой системе, и читать да писать файлы, и лазать в Сеть…
            • –1
              Ну зато теперь ну уж точно будут винлокеры и троянцы в .js файлах. также к ним будет прилагаться README файл с описанием как запустить: сказать node.js, скачать npm, сделать npm install -d, запустить node virus.js
              • 0
                Интересно, -d зачем?
                • –2
                  А просто подробный вывод информации о ходе установке. Пишу уже по привычке.
            • +2
              Жирные будут вирусы, тянуть за собой node.exe на пару мегабайт, к нему в придачу еще пару модулей и собственно сам скрипт, хотя наверно все зависит от целевой аудитории, если кул-хацкер ничего не знает кроме JS то возможно толк и будет.
          • –1
            Ребят, а стоит ли работать на node.js с процессами?
            И сложен ли процесс написания? Или достаточно знаний js?
            • +1
              Ребят, а стоит ли работать на node.js с процессами?
              А «работать с процессами» — это что такое? Task manager написать? Или в собственном скрипте fork() вызвать? Если второе, то не стóит, наверное — проще работать с очередью событий.

              И сложен ли процесс написания? Или достаточно знаний js?
              Знаний JS никак не достаточно — непременно, непременно следует также API изучить, и без этого даже не начинать дела.

              Настойчиво рекомендую также окинуть взглядом полный список модулей хотя бы для того, чтобы не начать дело с написания одного из них тогда, когда достаточно начать дело с использования одного из них. Свободного открытого кода очень много.
              • 0
                Я бы сказал, достаточно знаний node.js. JS — это лишь язык. А node.js — инструмент и набор библиотек.
              • 0
                Я честно вначале не верил в успех ноды на винде…
                Но рад что ошибался)
                • 0
                  Слуште, а виндового враппера для Node.js-скриптов, чтоб делать их полноценными самостоятельными экзешниками, пока на горизонтах не видать?
                  • 0
                    А для чего вы хотите это использовать?
                    • 0
                      Делать программки, требующие от пользователей минимальных движений для установки и запуска, пользуясь всеми возможностями языка и библиотек из node.js
                      • 0
                        14 декабря я донёс эту мысль до разработчиков, но пока что никто не отозвался.

                        Ну, оно и понятно: на этом этапе развития движка есть замыслы куда более насущные.
                    • –3
                      В чем сложность сделать самому?
                    • 0
                      Всем, кто дочитал в комментариях до этого места — призовой вопрос.

                      В настоящее время в модуле node-ffi код файла ffi.cc вызывает функцию ffi_prep_cif() с параметром FFI_DEFAULT_ABI (и всегда FFI_DEFAULT_ABI). Этот параметр управляет выбором двоичного интерфейса приложения (Application binary interface).

                      В то же время js-ctypes (джаваскриптовая обёртка вокруг libffi, используемая браузером Firefox и некоторыми его расширениями) задаёт сразу несколько констант ABI: default_abi, stdcall_abi и winapi_abi и в примерах рекомендует использовать winapi_abi (а не default_abi) для обращения к MessageBoxW(). Да и в MSDN функция MessageBoxW() вполне недвусмысленно записывается через WINAPI, что подразумевает специальный ABI, как я это понимаю.

                      А теперь внимание, вопрос. Если вызов функции MessageBoxW() подразумевает использование специального ABI, то почему мой пример вообще сработал в Node.js без проблем? Если же вызов функции MessageBoxW() не подразумевает использование специального ABI, то зачем разработчикам браузера Firefox понадобилось эдак «нагородить огород»?

                      Вопрос предлагается для самостоятельного обдумывания, а не в качестве шарады с известным ответом. Дело в том, что я сам ещё не знаю ответа.
                      • +1
                        int WINAPI MessageBox() в примере на MSDN это макрос из файла WinDef.h:
                        #define WINAPI __stdcall

                        или иного ABI — в зависимости от системы.

                        Зоопарк *_abi, вероятнее всего, из-за обратной совместимости, кривых рук и/или горячих голов. Уверен, что разницы в них никакой нет. Разве что default_abi может являться __cdecl.
                        • 0
                          Предположим (и даже с изрядной уверенностью), что default_abi непременно __cdecl. Тогда в чём разница между ним и __stdcall, и почему в вызове функции MessageBoxW() эта разница ничем не проявила себя?
                          • +1
                            Судя по статье в Википедии, единственная разница состоит в работе со стеком. Действуя по соглашению cdecl, вызванная функция оканчивается простым ret, а вызывающий её код очищает стек после вызова, так что вызов выглядит наподобие следующего примера:
                            ; x = function_name(a, b, c)
                            push c             ; arg 3
                            push b             ; arg 2
                            push a             ; arg 1
                            call function_name ; jump to function_name's code
                            add esp, 12        ; pop function args (a, b, c) off the stack
                            mov x, eax         ; fetch function return value
                            

                            Ну а stdcall предполагает, что вызванная функция очищает стек самостоятельно (в вышеприведённом примере оканчиваясь ret 12 вместо простого
                            ret
                            ), так что нет нужды чистить его (add esp, 12) опосля каждого вызова.

                            Получается, что если функцию stdcall (такую, как MessageBoxW()) вызвали через FFI_DEFAULT_ABI, то стек принуждён отдать вдвое больше данных, чем было в него командами push засунуто.

                            Остаётся всё равно дивиться тому, что скрипт отработал невозбранно, невзирая на такую скверную работу со стеком. Прозреваю, что где-то есть какой-то компенсаторный механизм на случай ошибки — и механизм этот сработал.
                            • 0
                              > Прозреваю, что где-то есть какой-то компенсаторный механизм

                              Попробовал вызвать MessageBoxA() через Alien (FFI для Lua, тоже на базе libffi) не указав stdcall как ABI и получил ошибку.
                              • –1
                                А какую ошибку?
                                • +1
                                  Хм, кажется, я наврал. Сделал с утра что-то не то.

                                  Пытаюсь повторить и не получается. Точнее, всё отлично получается и это очень странно.

                                  MessageBox = alien.user32.MessageBoxA;
                                  MessageBox:types({'int', 'string', 'string', 'int'});
                                  MessageBox(0, 'Hello', 'Hello from Lua!', 1);

                                  и

                                  MessageBox = alien.user32.MessageBoxA;
                                  MessageBox:types({'int', 'string', 'string', 'int', abi = 'stdcall'});
                                  MessageBox(0, 'Hello', 'Hello from Lua!', 1);

                                  Оба работают.

                                  Попробовал вызвать GetWindowTextA и SendMessageA, которые у меня всегда крашили программу, если не указать ABI, но и они работают:

                                  GetWindowText = alien.user32.GetWindowTextA;
                                  GetWindowText:types({'int', 'pointer', 'int'});
                                  buffer = alien.buffer(128);
                                  GetWindowText(hWnd, buffer, 128);
                                  print(buffer);
                      • +2
                        Позвольте предложить альтернативное решение для Windows:

                        1. Создаем файл

                        helloworld.js
                        import System.Windows.Forms;
                        
                        MessageBox.Show("Привет Хабр!", "Заголовок окна");
                        


                        2. Компилим его:
                        C:\Windows\Microsoft.NET\Framework\v4.0.30319\jsc.exe helloworld.js


                        И Запускаем helloworld.exe, при этом эффект будет ровно тот же.
                        Размер — 4,6 кб
                        • 0
                          Ну это же пример был, а если у меня есть библиотека myapi.dll и в ней функция RecalculateMatrix(array), то как Вы import напишете? А то, что в .NET есть обертки для некоторых WinAPI — это ясно.
                          • +1
                            Добавить сборку в GAC?
                            • +1
                              Ну, это тоже был всего лишь пример.

                              Я бы хотел подчеркнуть, что через jsc у вас есть доступ ко всем библиотекам .NET Framework.

                              И Вы можете подключать сторонние .NET библиотеки (сборки).

                              Но, если Ваша RecalculateMatrix существует только в dll, написанной на языке Си и Вы не планируете ее переписывать, то через технологию P/Invoke вы можете написать для нее библиотеку-обвертку на C# и использовать технологию подключения COM/WinApi – P/Invoke. И использовать эту библиотеку, в итоге, в JavaScript.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.