Есть необходимость использовать XPCOM в Delphi. Конкретно - для управления Cookies из своего приложения.

Скачал и установил Gecko SDK под Delphi. Скомпилировал прилагаемый пример - запускается (хотя правильно ли он работает, я так и не понял, но под дебаггером вроде все идет нормально).

Далее, поскольку требуемых мне интерфейсов в текущей версии Gecko SDK под Delphi не оказалось, описал их самостоятельно.

Теперь сама проблема: подгрузить интерфейсы удается (указатель получаю), но вызов их функций в лучшем случае просто не приводит к ожидаемым результатам, в худшем - инициирует аварийный выход. Если кто знает, подскажите, пожалуйста, что не так делаю.

Вот доопределенные мною интерфейсы:

Выделить код

Код:

const
  NS_COOKIEMANAGER_CONTRACTID      = '@mozilla.org/cookiemanager;1';

type
  nsICookie2 = interface(nsICookie)
  ['{736619fe-8d09-4e59-8223-32f176c22977}']
  end;

  nsICookieManager2 = interface(nsICookieManager)
  ['{5047cab4-9cb2-4927-a4ab-77422bc3bc67}']
    procedure add ( domain: nsACString; path: nsACString; name: nsACString; value: nsACString; isSecure: PRBool; isHttpOnly: PRBool; isSession: PRBool; expiry: PRInt64 ); stdcall;
    function cookieExists ( cookie: nsICookie2 ): PRBool; stdcall;
    function countCookiesFromHost ( host: nsACString ): PRUint32; stdcall;
    procedure importCookies ( cookieFile: nsIFile ); stdcall;
  end;

А вот попытка их применить:

Выделить код

Код:

var
  cookie_man : nsICookieManager2;
  n: Cardinal;
  icsDomain: IInterfacedCString;
begin
  GRE_Startup;
  NS_CreateInstance(NS_COOKIEMANAGER_CONTRACTID, nsICookieManager2, cookie_man);

  n := cookie_man.RemoveAll; // просто ничего не происходит: ожидаю - очищение кук из FireFox

  icsDomain := NewCString;
  icsDomain.Assign('somehost.ru');
  n := cookie_man.countCookiesFromHost(icsDomain.ACString); // возвращает 0, хотя куки приутствуют, кроме того, вызов этой функции в итоге инициализирует аварийное завершение программы

  GRE_Shutdown;
end.

Первое, что бросается в глаза - это неправильно определенный на Delphi интерфейс nsICookieManager2. Он должен выглядеть так:

Выделить код

Код:

nsICookieManager2 = interface(nsICookieManager)
    ['{5047CAB4-9CB2-4927-A4AB-77422BC3BC67}']
    function Add(const aDomain: nsACString; const aPath: nsACString; const aName: nsACString; const aValue: nsACString; aIsSecure: LongBool; aIsHttpOnly: LongBool; aIsSession: LongBool; aExpiry: PRInt64): HRESULT; stdcall;
    function CookieExists(aCookie: nsICookie2; var _retval: LongBool): HRESULT; stdcall;
    function CountCookiesFromHost(const aHost: nsACString; var _retval: Cardinal): HRESULT; stdcall;
    function ImportCookies(aCookieFile: nsIFile): HRESULT; stdcall;
  end;

То есть, при директиве вызова stdcall должен возвращаться HRESULT для всех методов, а результат помещаться в _retval.
Или можно поменять директиву вызова на safecall. Тогда обработка ошибок в Delphi будет комовской и все что вы получите -  EOleException с кодом исключения.

А вообще интересно было бы посмотреть на SDK и xpidl-компилятор, которые вы используете, а также полный код программы.

Благодарю за Ваши пояснения и исправления в определении интерфеса!

Программа теперь отрабатывает нормально, НО, к сожалению, желаемого результата от нее все равно пока получить не могу. Вот полный текст программы:

Выделить код

Код:

program Project1;

{$APPTYPE CONSOLE}

uses
  nsXPCOM,
  nsXPCOMGlue,
  nsTypes,
  nsError,
  nsGeckoStrings,
  nsNetUtil,
  SysUtils;

const

  NS_COOKIEMANAGER_CONTRACTID      = '@mozilla.org/cookiemanager;1';

type

  nsICookie2 = interface(nsICookie)
  ['{736619fe-8d09-4e59-8223-32f176c22977}']
  end;

  nsICookieManager2 = interface(nsICookieManager)
  ['{5047cab4-9cb2-4927-a4ab-77422bc3bc67}']
    function add ( domain: nsACString; path: nsACString; name: nsACString; value: nsACString; isSecure: PRBool;
                    isHttpOnly: PRBool; isSession: PRBool; expiry: PRInt64 ): nsresult; stdcall;
    function cookieExists ( cookie: nsICookie2; out _retval: PRBool ): nsresult; stdcall;
    function countCookiesFromHost ( host: nsACString; out _retval: PRUint32 ): nsresult; stdcall;
    function importCookies ( cookieFile: nsIFile ): nsresult; stdcall;
  end;

var

  cookie_man : nsICookieManager2;
  res: nsresult;
  n: Cardinal;
  icsDomain: IInterfacedCString;

begin
  GRE_Startup;

  icsDomain := NewCString;
  NS_CreateInstance(NS_COOKIEMANAGER_CONTRACTID, nsICookieManager2, cookie_man);
  res := cookie_man.RemoveAll;

  icsDomain.Assign('somehost.ru');
  res := cookie_man.countCookiesFromHost(icsDomain.ACString, n);

  GRE_Shutdown;
end.

Gecko SDK для Delphi можно скачать здесь: http://ftp.newbielabs.com/Delphi%20Geck … Readme.htm.

Что касается xpidl-компилятора... должен признаться, что не совсем понимаю, что это. Однако, как мне кажется, отдельно его не ставил. Правда, несколько раньше я хотел скомпилировать сам FireFox под VS2005 и качал C-шное SDK (включая этот компилятор) но вроде в систему его не ставил.

Какие Cookies вы хотите получить? Броузера встроенного в ваше приложение или установленного в системе?

У меня пример из этого SDK не заработал. По-идеи, должен показаться броузер, вместо этого - пустая панель. Или неправильный SDK, или неправильный GRE. В nsXPCOMGlue есть nsGREDirServiceProvider.GetFile, так вот он в момент инициализации броузера постоянно возвращает NS_ERROR_FAILURE. Думаю ошибка в инициализации GRE. Похоже, что это какой-то недоделанный проект.

Если вам нужен в приложении встроенный броузер, то можно посмотреть на Mozilla ActiveX control (http://www.iol.ie/~locka/mozilla/contro … singDelphi). А если нужны cookies установленного в системе броузера, то нужно разбираться с форматом файла в котором они хранятся, или писать экстеншен и  вычитывать при запущеном броузере.

Опишите задачу, может тогда еще чем-то смогу помочь.

P.S. xpidl-компилятор переводит интерфейс idl в паскалевский или сишный хедер. У вас там была ошибка, очевидно вы все делали вручную.

Еще раз спасибо за быстрый ответ!

Вообщем-то я и сам сомневался, что включенный пример работает. Похоже, что этот SDK писался просто под другой GRE...

Мне нужно читать и записывать cookies из/в базу браузера, установленного в системе, из внешнего приложения. FireFox3 использует SQLite для хранения кук. С этим я уже разобрался. Все бы хорошо, но этот механизм не позволяет редактировать куки "на лету": т.е. пока браузер запущен, он не видит изменений, сделанных извне (хотя и видит их после перезапуска).

Т.е. в моем случае, на сколько я понял, может помочь только написание расширения? Что ж, буду рад если подскажете, как это лучше сделать. На каком языке его лучше писать, так чтобы можно было потом обеспечить нормальное взаимодействие с приложением на дельфях?


P.S. Спасибо за пояснение по поводу xpidl-компилятора! Действительно все делал вручную.

Расширения пишутся на XUL и Java Script. Здесь есть достаточно материала для старта: http://forum.mozilla-russia.org/viewtopic.php?id=4393

Для взаимодействия с внешней программой можна использовать XPCOM-компонент. Например, броузер при старте загружает экстеншен, тот просто поднимает компонент и дальше вся работе ведется в нем. Компонент можно написать на Delphi или С++, а для взаимодействия использовать любую межпроцесcную технологию (http://msdn.microsoft.com/en-us/library … S.85).aspx).

Спасибо Elexander'у за ссылки! Написал на С++ компонент, связался с ним через pipe, вроде все заработало.

Но недавно столкнулся с необходимостью записывать сессионные куки, и возникла проблема. Вот вырезка из кода:

Выделить код

Код:

if (t)
  bRes = NS_SUCCEEDED(cookieMgr2->Add(aDomain, aPath, aName, aValue, PR_FALSE, PR_FALSE, PR_FALSE, t));
else
  bRes = NS_SUCCEEDED(cookieMgr2->Add(aDomain, aPath, aName, aValue, PR_FALSE, PR_FALSE, PR_TRUE, t)); // сессионная

В обоих ветвях результат возвращаяется положительный (1). Однако сессионная кука в FireFox почему-то не появляется. Что может быть не так в коде?

И еще вопрос: для установки куки вида <name>=deleted имеет значение, как ее устанавливать сессионной или нет? (пока разницы не заметил)

Delivron пишет

Но недавно столкнулся с необходимостью записывать сессионные куки, и возникла проблема. Вот вырезка из кода:

Выделить код

Код:

if (t)
  bRes = NS_SUCCEEDED(cookieMgr2->Add(aDomain, aPath, aName, aValue, PR_FALSE, PR_FALSE, PR_FALSE, t));
else
  bRes = NS_SUCCEEDED(cookieMgr2->Add(aDomain, aPath, aName, aValue, PR_FALSE, PR_FALSE, PR_TRUE, t)); // сессионная

В обоих ветвях результат возвращаяется положительный (1). Однако сессионная кука в FireFox почему-то не появляется. Что может быть не так в коде?

Я не проверял, но проблема может быть в формате передаваемых данных. Возможно, для домена обязательно должна первой идти точка, а для пути обязательный закрывающий слеш. Поэкспериментируйте с этим. И, по-моему, результат успешного завершения - это 0, а не 1.

А вообще нет. Пардон за беспокойство - код работает :) Правда он работает, когда фф окончательно загрузился, а вот с моментом загрузки - проблема: мне нужно перебить сессионную куку новой, когда первая грузится из sessionstore.js. А вот как отловить момент "догрузки" фф - не ясно. Если слишко рано пытаться выставлять куку (а ставлю я ее на событии DLL_PROCESS_ATTACH) - фф периодически валится или вообще ее не выставляет, а подгружает старую, из сохраненной сессии. А вот если ее ставить с задержкой в потоке - то уже чаще ставится (но тоже не всегда).

Что касается макроса NS_SUCCEEDED, он возвращает 1 в случае успешного завершения (проверено). А точку перед доменом - да нужно ставить (но этим я озаботился еще раньше).

Delivron пишет

А вот как отловить момент "догрузки" фф - не ясно. Если слишко рано пытаться выставлять куку (а ставлю я ее на событии DLL_PROCESS_ATTACH) - фф периодически валится или вообще ее не выставляет, а подгружает старую, из сохраненной сессии. А вот если ее ставить с задержкой в потоке - то уже чаще ставится (но тоже не всегда).

Все менеджеры mozilla будут, по идее, уже инициализированы в CreateInstance интерфейса nsIFactory. Это то место, где должен создаваться экземпляр вашего объекта. Так что, правильно бы было добавлять куки в его кострукторе, а не на загрузку библиотеки в адресное пространство процесса.

Вообще, экземпляр моего объекта похоже даже не создается (по крайней мере в конструктор объекта я под дебеггером не попал), ведь я никогда явно не запрашиваю свой интерфейс. Поэтому куда сунуть вызов своей функции даже не заню, может переписать NS_GENERIC_FACTORY_CONSTRUCTOR под себя, добавив в конец вызов этой функции, или это не поможет? (надо будет проверить на досуге)

А что мешает явно запросить интерфейс в инициализации расширения и сохранить в глобальной переменной?