понедельник, 21 ноября 2011 г.

Вызов javascript функции в Firefox из Windows приложения

Разрабатываются Windows приложение и Web приложение. Они выполняются одновременно. Возникла задача передать данные из первого приложения во второе и выполнить там javascript функцию с переданными данными. Для IE всё настолько тривиально, что здесь даже не стоит обсуждать; сложнее оказалось для Mozilla Firefox.
Я стремился следовать принципу KISS при решении этой задачи. Весьма полезными оказались следующие ресурсы: Предлагаемый способ основан на создании в Firefox невидимого окна для приёма сообщений. Это окно можно будет найти по имени класса и имени окна (я использую "Modus RP Class" и "Modus RP"). Для упрощения примера я буду передавать строку, но можно передать любую структуру. Также для упрощения в Web приложении будет вызываться функция window.alert(). В Delphi 2010 для отправки сообщения я на форму добавил кнопку TButton и обработчик события OnClick:
procedure TForm1.Button1Click(Sender: TObject);
var
  s: String;
  copyData: TCopyDataStruct;
  targetHandle: HWND;
begin
  s := 'Текущее время: ' + DateTimeToStr(Now());
  copyData.lpData := PChar(s);
  copyData.cbData := SizeOf(Char) * (Length(s) + 1);
  targetHandle := FindWindow('Modus RP Class', 'Modus RP');
  if targetHandle <> 0 then
    SendMessage(targetHandle, WM_COPYDATA, Handle, Longint(@copyData));
end;
Теперь привожу исходный код страницы HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Call JavaScript Function</title>
    <script type="text/javascript" language="javascript">
    
    function getBrowserName() {
        var browserName = "";
        var ua = navigator.userAgent.toLowerCase();
        if ( ua.indexOf("opera") != -1 ) 
            browserName = "opera";
        else if ( ua.indexOf("msie") != -1 ) 
            browserName = "msie";
        else if ( ua.indexOf("safari") != -1 ) 
            browserName = "safari";
        else if ( ua.indexOf("mozilla") != -1 ) 
            if ( ua.indexOf("firefox") != -1 ) 
                browserName = "firefox";
            else 
                browserName = "mozilla";
        return browserName;
    };
    
    if (getBrowserName() == "firefox") {    
        try {
            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
            Components.utils.import("resource://gre/modules/ctypes.jsm");

            var user32 = ctypes.open("user32.dll");
            var kernel32 = ctypes.open("kernel32.dll");

            const HWND_MESSAGE = -3; 
            const WM_COPYDATA = 74;

            var lpszClassName = ctypes.char.array()("Modus RP Class");    
            var lpszWindowName = ctypes.char.array()("Modus RP");
            
            const COPYDATASTRUCT = ctypes.StructType("COPYDATASTRUCT",
                [ 
                  {dwData: ctypes.uintptr_t},
                  {cbData: ctypes.uint32_t},
                  {lpData: ctypes.voidptr_t}
                ]);    

            var DefWindowProc = user32.declare("DefWindowProcA", ctypes.winapi_abi, ctypes.int,
                ctypes.voidptr_t, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t);
                
            function windowProcJSCallback(hWnd, uMsg, wParam, lParam) {
              if (uMsg == WM_COPYDATA) {
                var CD = COPYDATASTRUCT.ptr(lParam).contents;
                var s = ctypes.cast(CD.lpData, ctypes.jschar.ptr).readString();
                alert(s);
                return ctypes.int(0);
              } else 
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
            }
            
            var WindowProcType = ctypes.FunctionType(ctypes.stdcall_abi, ctypes.int,
                [ctypes.voidptr_t, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t]).ptr;
            var WindowProcPointer = WindowProcType(windowProcJSCallback);
            var WNDCLASS = ctypes.StructType("WNDCLASS",
                [
                  { style: ctypes.uint32_t },
                  { lpfnWndProc: WindowProcType }, 
                  { cbClsExtra: ctypes.int32_t },
                  { cbWndExtra: ctypes.int32_t },
                  { hInstance: ctypes.voidptr_t },
                  { hIcon: ctypes.voidptr_t },
                  { hCursor: ctypes.voidptr_t },
                  { hbrBackground: ctypes.voidptr_t },
                  { lpszMenuName: ctypes.char.ptr },
                  { lpszClassName: ctypes.char.ptr }
                ]);
                
            var GetModuleHandle = kernel32.declare("GetModuleHandleA", ctypes.winapi_abi, 
                ctypes.voidptr_t, ctypes.char.ptr);    
            var hInstance = GetModuleHandle(null);    
                
            var wndclass = WNDCLASS();
            wndclass.hInstance = hInstance;
            wndclass.lpszClassName = lpszClassName;
            wndclass.lpfnWndProc = WindowProcPointer;   

            var RegisterClass = user32.declare("RegisterClassA", ctypes.winapi_abi, 
                ctypes.uint32_t, WNDCLASS.ptr);
            var UnregisterClass = user32.declare("UnregisterClassA", ctypes.winapi_abi, 
                ctypes.bool, ctypes.char.ptr, ctypes.voidptr_t);
            var DestroyWindow = user32.declare("DestroyWindow", ctypes.winapi_abi, 
                ctypes.bool, ctypes.voidptr_t);

            RegisterClass(wndclass.address());

            var CreateWindowEx = user32.declare("CreateWindowExA", ctypes.winapi_abi, 
                ctypes.voidptr_t, ctypes.int, ctypes.char.ptr, ctypes.char.ptr, ctypes.int, 
                ctypes.int, ctypes.int, ctypes.int, ctypes.int, ctypes.voidptr_t, 
                ctypes.voidptr_t, ctypes.voidptr_t, ctypes.voidptr_t);

            var win = CreateWindowEx(0, lpszClassName, lpszWindowName, 0, 0, 0, 0, 0, 
                ctypes.voidptr_t(HWND_MESSAGE), null, hInstance, null);    

            window.onunload = function () {
              if (win && !win.isNull()) {
                  DestroyWindow(win);
                  UnregisterClass(lpszClassName, hInstance);
                  user32.close();
                  kernel32.close();
                  win = null;
              }    
            };
        } catch(ex) {}
    }
    
    </script>
</head>

<body>
    <h1>Call JavaScript Function</h1>
</body>
При нажатии на кнопку в Delphi приложении в окне Firefox выполняется функция alert(). Результат представлен на картинке:
Перед перезагрузкой страницы или закрытием окна невидимое окно уничножается, его класс разрегистрируется, используемые библиотеки закрываются.
Чтобы всё это работало, требуется дать разрешения в Firefox. Для этого нужно открыть в нём окно about:config и изменить свойство signed.applets.codebase_principal_support = true. Затем открыть файл C:/Users/.../AppData/Roaming/Mozilla/Firefox/Profiles/...default/prefs.js и добавить строки
user_pref("capability.principal.codebase.p<n>.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.p<n>.id", "http://<localhost:8080>");
user_pref("capability.principal.codebase.p<n>.subjectName", "");
где <n> - порядковый номер и <localhost:8080> - адрес Вашего сервера.

1 комментарий:

  1. Здравствуйте

    Подскажите пожалуйста как создать экземпляр класса который хранится в dll из javascript. С вызовом функций вроде понятно, а как классами пользоваться?

    ОтветитьУдалить