Как стать автором
Обновить

Авторы расширений, хакеры браузера, встречайте js-ctypes

Время на прочтение 7 мин
Количество просмотров 2.1K
Автор оригинала: Dan Witte
Что такое (можете спросить вы) js-ctypes? Скажем, пишете вы расширение на джаваскрипте, и ему нужно обратиться к местному коду (native code). Например, weave-crypto приходится обращаться к библиотеке NSS. А ваше расширение может пожелать, например, вызвать напрямую NSPR, libc, или функции Win32. Прямо сейчас есть два выхода: либо использовать скриптуемые XPCOM-интерфейсы (обеспечиваемые libxul), либо написать и реализовать собственные XPCOM-интерфейсы, то есть поставлять двоичный код в своём расширении. Если первый вариант не годится, остаётся только второй, но тогда поставлять расширение становится заметно сложнее: приходится отдельно компилировать двоичный код для каждой из поддерживаемых платформ, чтобы упаковать его внутрь вашего кросс-платформенного xpi.

Ответом на эту трудность поэтому станет библиотека js-ctypes: она позволяет джаваскрипту вызывать местный код (написанный на Си) и манипулировать сишными типами данных, без использования XPCOM, и нет необходимости компилировать ни одну строку кода. Это означает, что вам не придётся определять XPCOM-интерфейсы, и что можно будет использовать разделяемые библиотеки (shared libraries), подобные libc, напрямую. Есть и побочный положительный эффект: мы большей частью устраняем потери на преобразование типов данных, свойственные XPConnect, так что выполнение кода может становиться быстрее. (Сравнение скорости я приведу в одной из последующих блогозаписей.) Библиотека js-ctypes будет поставляться с Gecko 1.9.3, а эта платформа (если номера версий не переменятся) станет фундаментом для Firefox 3.7.

Вы можете спросить: «Но как...?». И вот примеры (они проверялись на 32-битных Линуксах x86 и содержат не кросс-платформенные части):

1) Открытие библиотеки.
// Сперва импортируем модуль ctypes.
Components.utils.import("resource://gre/modules/ctypes.jsm");

// Открываем libc.
let library = ctypes.open("libc.so.6");

// Определяем функцию fopen, имеющую следующий Си-прототип:
//  FILE* fopen(const char* name, const char* mode);
let fopen = library.declare("fopen",                     // имя символа
                            ctypes.default_abi,          // конвенция вызова cdecl
                            ctypes.PointerType("FILE*"), // возвращаемый функцией тип (FILE*)
                            ctypes.char.ptr,             // первый аргумент (const char*)
                            ctypes.char.ptr);             // второй аргумент (const char*)

// Вызываем функцию, она возвращает объект указателя FILE*.
let file = fopen("hello world.txt", "w");

// Что такое 'file'?
file.toString();
// выдаст "ctypes.PointerType("FILE*")(ctypes.UInt64("0x9781b38"))" (значение указателя)

// ... далее пишем данные в файл ...


* This source code was highlighted with Source Code Highlighter.

2) Определение и использование структурных типов (struct) и массивов.
// Определяем struct по имени 'hostent', содержащий пять полей,
// каждое из которых имеет свой тип.
// Вот соответствующее определение на Си:
//  struct hostent {
//     char* h_name;      // имя хоста
//     char** h_aliases;  // массив строк, содержащих псевдонимы хоста
//     int h_addrtype;     // используется ли адрес IPv4 или IPv6
//     int h_length;      // длина (в байтах) IP-адреса
//     char** h_addr_list; // массив IP-адресов (от нэймсервера)
//  };
let hostent = ctypes.StructType("hostent",
                                [{ h_name     : ctypes.char.ptr                 },
                                 { h_aliases  : ctypes.char.ptr.ptr             },
                                 { h_addrtype : ctypes.int                     },
                                 { h_length    : ctypes.int                     },
                                 { h_addr_list : ctypes.uint8_t.array(4).ptr.ptr }]);

// Определяем функцию 'gethostbyname', имеющую нижеследующий Си-прототип:
//  struct hostent* gethostbyname(const char* name);
let gethostbyname = library.declare("gethostbyname",
                                    ctypes.default_abi,
                                    hostent.ptr,         // используем наш тип 'hostent'
                                    ctypes.char.ptr);

// Просим нашу функцию обработать имя хоста.
let google = gethostbyname("mail.google.com");

// По полученному указателю получим struct типа 'hostent',
// обращаемся к его полю 'h_name', и преобразуем к джаваскриптовой
// строке. Наши действия примерно подобны вот этому оператору Си:
//  printf("%s", google->h_name);
google.contents.h_name.readString();
// выдаст "googlemail.l.google.com"

// Дважды следуя по указателю из поля 'h_addr_list',
// получим первый элемент массива, который и сам является
// массивом из четырёх байтов IPv4-адреса хоста.
// Вот примерный Си-эквивалент:
//  printf("%u.%u.%u.%u", (int) h_addr_list[0][0], (int) h_addr_list[0][1],
//         (int) h_addr_list[0][2], (int) h_addr_list[0][3]);
google.contents.h_addr_list.contents.contents.toString();
// выдаст "ctypes.uint8_t.array(4)([74, 125, 19, 17])", то есть 74.125.19.17


* This source code was highlighted with Source Code Highlighter.

3) Создание Си-подобных указателей на функции, указывающих на джаваскриптовые функции.

(Заметим, что эта часть библиотеки ещё не готова, но над патчем идёт работа.)
// Определим тип функции сравнения, которая принимает два указателя
// на элементы, а затем возвращает:
// -1, если i < j;
// 0, если i == j;
// 1, если i > j.
// Эквивалентный Си-код таков:
//  typedef int (comparator_t*)(const int8_t* i, const int8_t* j);
let comparator_t = ctypes.FunctionType(ctypes.default_abi, ctypes.int,
                                      ctypes.int8_t.ptr, ctypes.int8_t.ptr);

// Каков Си-тип 'comparator_t'?
comparator_t.name;
// выдаст "int (*)(int8_t*,int8_t*)"

// Определим функцию 'qsort', которая принимает массив элементов,
// и функцию сравнения, и сортирует массив.
//  void qsort(void* array, size_t length, size_t elemsize, comparator_t comp);
let qsort = library.declare("qsort", ctypes.default_abi, ctypes.void_t,
             ctypes.voidptr_t, ctypes.size_t, ctypes.size_t, comparator_t);

// Реализуем джваскриптовую функцию, точно соответствующую вышеприведённому 'comparator_t'.
function reverse(i, j) { return j.contents - i.contents; }

// Создадим сишный указатель на функцию, указывающий на нашу джаваскриптовую функцию.
let reverse_ptr = comparator_t(reverse);

// Что такое 'reverse_ptr'?
reverse_ptr.toString();
// выдаст "ctypes.FunctionType(ctypes.default_abi, ctypes.int, ctypes.int8_t.ptr,
//                             ctypes.int8_t.ptr)(ctypes.UInt64("0x81a430cb"))"

// Создадим массив значений и вызовем 'qsort'.
let array_t = ctypes.int8_t.array();
let ints = array_t([3, 1, 5, 6, 4, 2]);
qsort(ints.address(), ints.length, array_t.elementType.size, reverse_ptr);

// Вуаля!
ints.toString();
// выдаст "ctypes.int8_t.array(6)([6, 5, 4, 3, 2, 1])"


* This source code was highlighted with Source Code Highlighter.

Итак, если вы автор расширения, или пишете код для браузера, имейте в виду js-ctypes — и дайте нам знать, как пойдут дела!
Теги:
Хабы:
+29
Комментарии 41
Комментарии Комментарии 41

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн