Как написать на C++ компонент для Firefox, так, чтобы его потом можно было использовать из яваскриптового extension или даже из обычной веб-страницы.
Мне нужно было сделать тулбар для IE и Firefox. Осложнялось всё тем, что использовался некий нестандартный бинарный протокол, реализовала который C-дллька. Использовать её из C# (на котором я писал тулбар для IE) не составило никакой проблемы, однако для FF пришлось писать Wrapper — XPCOM-компонент, к которому я мог бы обратиться из javascript, который передавал бы данные в dll и возвращал бы результат.
XPCOM — это аналог Microsoftовского COM'а от Мозиллы, который от COM отличается практически ничем. Вообще, не очень понятно, почему Mozilla вместо хорошо описанного и привычного COM решили изобрести свой велосипед, но так уж вышло, и нам, разработчикам, придётся принимать всё так, как оно есть.
Open Source — это прекрасно, но до тех пор, пока не столкнёшься с документацией и отсутствием работающих примеров. Примеров XPCOM-компонентов я, кстати, нашёл в интернете аж 4, ни один из которых не заработал. Кроме того, самый свежий из них датировался 2006ым годом, в описаниях значилась старинная версия XULRunner'а, а в обсуждении к примеру были вопросы о совместимости с «новым Firefox 1.3» и несколько десятков вопросов «how to get it work?».
PS1. Все файлы проекта можно скачать по этой ссылке (82 кб)
PS2. У нас есть «интерфейс» (или несколько) и есть «компонент», который этот интерфейс реализует. Каждый из них имеет свой UID. Для генерации UID нам понадобится Guidgen из VS, обычно она лежит в «C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\guidgen.exe». Чтобы сгенерировать другие UID'ы, отличные от тех, которые приведены в моём примере, нужно запустить guidgen, выбирать там третий вариант, скопировать его и руками вставить в IDemo.idl. Потом сгенерировать ещё ID и скопировать в уже в Demo.h. Это нужно будет делать, если вы не будете останавливаться на Demo-компоненте и будете делать свой компонент.
PS3. idl-файлы для XPCOM отличаются от idl-файлов для COM, поэтому microsoft'овские утилиты tlbimp и midl не понимают эти idl-файлы. Если вы решите добавить IDemo.idl в проект, не забудьте установить в его свойствах «не компилировать».
Предыстория
Мне нужно было сделать тулбар для IE и Firefox. Осложнялось всё тем, что использовался некий нестандартный бинарный протокол, реализовала который C-дллька. Использовать её из C# (на котором я писал тулбар для IE) не составило никакой проблемы, однако для FF пришлось писать Wrapper — XPCOM-компонент, к которому я мог бы обратиться из javascript, который передавал бы данные в dll и возвращал бы результат.
XPCOM — это аналог Microsoftовского COM'а от Мозиллы, который от COM отличается практически ничем. Вообще, не очень понятно, почему Mozilla вместо хорошо описанного и привычного COM решили изобрести свой велосипед, но так уж вышло, и нам, разработчикам, придётся принимать всё так, как оно есть.
Open Source — это прекрасно, но до тех пор, пока не столкнёшься с документацией и отсутствием работающих примеров. Примеров XPCOM-компонентов я, кстати, нашёл в интернете аж 4, ни один из которых не заработал. Кроме того, самый свежий из них датировался 2006ым годом, в описаниях значилась старинная версия XULRunner'а, а в обсуждении к примеру были вопросы о совместимости с «новым Firefox 1.3» и несколько десятков вопросов «how to get it work?».
Поехали!
- Вначале разберёмся с environment.
Запускаем в командной строкеsubst o: d:\myworkfolder\_firefox
— в эту папку мы будем складывать все нужные файлы.
Можно даже записать эту команду в bat-файл.
- Скачиваем последний XULRunner (сейчас это 1.9.0.6) — releases.mozilla.org/pub/mozilla.org/xulrunner/releases
Нам нужен SDK. Скачиваем и распаковываем его в O:\xulrunner-sdk
- Создаём O:\console.bat следующего содержания:
set path=%path%;O:\xulrunner-sdk\bin;O:\xulrunner-sdk\sdk\bin
cmd.exe
(на всякий случай, вдруг потребуется что-нибудь сделать руками)
- Создаём папку O:\dll-src, и в ней — IDemo.idl
#include "nsISupports.idl"
#include "nsrootidl.idl"
[scriptable, uuid(CB934085-D019-47d5-A6F0-623885873281)]
interface IDemo : nsISupports
{
long func1(in long inP, out long outP);
long func2(in wstring inP, out wstring outP);
};
- Там же создаём два бат-файла, build-pre.bat и build-post.bat. Второй оставляем пустым, а в первый пишем:
set path=%path%;O:\xulrunner-sdk\bin;O:\xulrunner-sdk\sdk\bin
xpidl -I O:\xulrunner-sdk\idl -m header IDemo.idl
xpidl -I O:\xulrunner-sdk\idl -m typelib IDemo.idl
По нашему idl-файлы с помощью xpidl будет автоматически генерироваться хедер и xpt-описание интерфейса (аналог майрософтовского .tlb файла)
- Создаём новый проект Win32 / Dll library / empty
- Запускаем build-pre.bat и переносим получившийся IDemo.h в наш проект.
- Создаём в VS файл Demo.h и копируем из IDemo.h наш темплейт. Приводим его в человеческий вид:
#ifndef _DEMO_H_
#define _DEMO_H_
#include "IDemo.h"
#define DEMO_CONTRACTID "@demo.com/XPCOMDemo/Demo;1"
#define DEMO_CLASSNAME "XPCOM Demo LOL"
#define DEMO_CID {0xcb934086, 0xd019, 0x47d5, { 0xa6, 0xf0, 0x62, 0x38, 0x85, 0x87, 0x32, 0x81 }}
class Demo : public IDemo
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IDEMO
Demo();
virtual ~Demo();
};
#endif
- Создаём в VS файл Demo.cpp и копируем туда закомментированную часть из IDemo.h — там уже есть шаблон реализации. Меняем название класса.
#include "Demo.h"
#include <nsMemory.h>
#include <nsStringAPI.h>
NS_IMPL_ISUPPORTS1(Demo, IDemo)
Demo::Demo() {
}
Demo::~Demo() {
}
NS_IMETHODIMP Demo::Func1(PRInt32 inP,PRInt32 *outP,PRInt32 *_retval) {
if (inP>100) {
*_retval = 1;
*outP = 0;
} else {
*_retval = 0;
*outP = inP*2;
}
return NS_OK;
}
NS_IMETHODIMP Demo::Func2(const PRUnichar *inP,PRUnichar **outP,PRInt32 *_retval) {
const wchar_t *msg = L"привет";
*outP = (PRUnichar *) nsMemory::Clone(msg, (wcslen(msg)+1)*sizeof(wchar_t));
*_retval = 0;
return NS_OK;
}
- Создаём в VS файл DemoModule.cpp:
Это — наш главный файл и точка входа в наше приложение. Тут мы отдаём ядру XPCOM'а те компоненты, которые реализует наш модуль.
#include "nsIGenericFactory.h"
#include "Demo.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(Demo)
static nsModuleComponentInfo components[] =
{
{
DEMO_CLASSNAME,
DEMO_CID,
DEMO_CONTRACTID,
DemoConstructor,
}
};
NS_IMPL_NSGETMODULE("DemoModule", components)
- Открываем свойства проекта
C/C++ — General — Additional Include Directories пишем O:\xulrunner-sdk\sdk\include;O:\xulrunner-sdk\include
C/C++ — Preprocessor — Preprocessor Definitions добавляем ;XPCOM_GLUE
C/C++ — Advanced — Force Includes пишем mozilla-config.h
Linker — Additional Library Directories пишем O:\xulrunner-sdk\sdk\lib
Linker — Input — Additional Dependencies — пишем xpcom.lib nspr4.lib xpcomglue_s.lib
Build events — Pre-build event — Command Line — пишем там O:\dll-src\build-pre.bat
Build events — Post-build event — Command Line — пишем там O:\dll-src\build-post.bat
- Пришла пора создавать ярлык для файрфокса.
Делаем его и в свойствах пишем: «C:\Program Files\Mozilla Firefox\firefox.exe» -no-remote -P dev
Запускаем. Открылся Profile Manager — создаём профиль dev и указываем вручную папку для него: C:\Documents and settings\test\Application Data\Mozilla\Firefox\Profiles\dev1 (создаём через кнопку «создать»).
Файрфокс запускается, и когда запустится, мы его закрываем
- Теперь редактируем build-post.bat.
set path=%path%;O:\xulrunner-sdk\bin;O:\xulrunner-sdk\sdk\bin
del /f "C:\Documents and Settings\test\Application Data\Mozilla\Firefox\Profiles\dev\xpti.dat"
del /f "C:\Documents and Settings\test\Application Data\Mozilla\Firefox\Profiles\dev\compreg.dat"
copy /Y IDemo.xpt "C:\Program Files\Mozilla Firefox\components\"
copy /Y debug\Demo.dll "C:\Program Files\Mozilla Firefox\components\"
- Билдим проект!
- Создаём файлик O:\demo.html
(код приведён частично)
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
obj = Components.classes["@demo.com/XPCOMDemo/Demo;1"].createInstance(); //помните это имя? оно есть в Demo.h
myobject = obj.QueryInterface(Components.interfaces.IDemo);
var x = {};
var res = myobject.func1(10,x);
alert('func1(10,x) returned ' + res + '. x is '+x.value);
res = myobject.func2("asd",x);
alert('func2("ads",x) returned ' + res + '. x is '+x.value);
- Запускаем файрфокс через наш ярлык и открываем file://o:\demo.html
- Жмём на кнопку, соглашаемся с вопросом про UNSAFE, смотрим результаты.
Вопрос про UNSAFE возникает на строчке с «PrivilegeManager.enablePrivilege» — эта строчка просит позволения работать с XPCOM-компонентами и, конечно, в веб-страницах содержаться не должна.
Если же мы делаем Firefox Extension — какую-нибудь панельку инструментов — то нам работать с XPCOM уже позволено. Поэтому в коде расширения этой строчки быть не должно, никакого предупреждения не возникает и всё работает молча.
- Ура! Работает!
PS1. Все файлы проекта можно скачать по этой ссылке (82 кб)
PS2. У нас есть «интерфейс» (или несколько) и есть «компонент», который этот интерфейс реализует. Каждый из них имеет свой UID. Для генерации UID нам понадобится Guidgen из VS, обычно она лежит в «C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\guidgen.exe». Чтобы сгенерировать другие UID'ы, отличные от тех, которые приведены в моём примере, нужно запустить guidgen, выбирать там третий вариант, скопировать его и руками вставить в IDemo.idl. Потом сгенерировать ещё ID и скопировать в уже в Demo.h. Это нужно будет делать, если вы не будете останавливаться на Demo-компоненте и будете делать свой компонент.
PS3. idl-файлы для XPCOM отличаются от idl-файлов для COM, поэтому microsoft'овские утилиты tlbimp и midl не понимают эти idl-файлы. Если вы решите добавить IDemo.idl в проект, не забудьте установить в его свойствах «не компилировать».