如何把Electron做成一個Runtime,讓多個應用共享同一個Electron

liulun發表於2021-12-24

這個問題涉及到很多知識,而且要想把這個Runtime做好很繞。

下面我就說一下我的思路:
(以下內容以Windows平臺為基礎,Mac平臺和Linux平臺還得去調查一下,才能確定是否可行)

首先,我們先區分三類使用者:

  1. Runtime建設者(就是我們)
  2. Runtime使用者(就是使用Runtime的開發者)
  3. 終端使用者(就是使用Runtime開發者開發的應用的那些使用者)

接下來我們就以Runtime建設者的視角來審視這項工作

首先我們要為Runtime使用者提供一個專有的打包工具,我們就叫它:打包工具。這個打包工具還內建了幾個可執行程式,我們給他們起個名字,分別叫:

  • 最終安裝程式
  • 最終執行程式
  • 最終解除安裝程式

好,我們一個一個聊他們的職責

打包工具的職責
按Runtime使用者的要求修改最終執行程式的圖示、應用簽名、版本、版權、檔名等資源資訊;
按Runtime使用者的要求修改最終解除安裝程式的圖示、應用簽名、版本、版權、檔名等資源資訊;
把最終執行程式、最終解除安裝程式與Runtime使用者開發好的HTML/CSS/JS等靜態檔案放到一起,壓縮成一個壓縮包,我們叫他:資原始檔
把這個資原始檔以資源的形式封裝到最終安裝程式中;
按Runtime使用者的要求修改這個最終安裝程式的圖示、應用簽名、版本、版權、檔名等資源資訊;
(修改資源的程式碼,後文有介紹)

這幾個工作完成之後,Runtime使用者就可以把這個最終安裝程式分發給終端使用者了。

最終安裝程式的職責
這個最終安裝程式在終端使用者的電腦上執行時,會完成以下工作:

檢查終端使用者的登錄檔,看其是否安裝了我們的Electron Runtime
如果沒有安裝,則下載Electron的發行版,釋放到一個特定目錄下,並在登錄檔記下來。
在這個特定目錄下記錄當前應用的資訊(解除安裝當前應用時要用到);
把自身的資源釋放到終端使用者指定的目錄內,也就是前文說的資原始檔
解壓縮資原始檔得到最終執行程式、最終解除安裝程式和Runtime使用者開發的HTML/CSS/JS等靜態檔案
寫登錄檔記錄最終解除安裝程式的位置,這樣使用者就可以在控制皮膚裡解除安裝我們的程式了。
按終端使用者的要求,建立開始選單圖示、桌面圖示,這些圖示均指向最終執行程式
(讀取資源的程式碼,後文有介紹)

如果終端使用者工作在沒有網路的環境下,那麼我們也可以允許Runtime使用者把Electron Runtime打包到最終安裝程式內,這是打包工具的職責。

如果擔心Electron官方提供的下載地址速度慢,可以考慮使用國內映象地址:​npmmirror.com/mirrors/electron/

最終執行程式的職責
檢查使用者登錄檔,找到Electron Runtime的放置路徑
啟動Electron Runtime並把當前應用的入口程式當做引數傳給Electron.exe,應用入口程式就是Runtime使用者開發HTML/CSS/JS等靜態檔案之一,
electron.exe path/to/entry.js
最終解除安裝程式的職責
刪除安裝目錄下的檔案
刪除登錄檔的解除安裝程式資訊
刪除Electron Runtime所在目錄下的應用程式資訊,如果發現沒有別的應用在依賴Electron Runtime了,那麼就把Electron Runtime所在目錄也刪掉。

 


把一個檔案作為資源寫入一個可執行程式的程式碼如下所示:

HANDLE hFile;
DWORD dwFileSize,dwBytesRead;
LPBYTE lpBuffer;
char szFile[MAX_PATH+1] = {0};
::GetDlgItemText(hwnd,EditId,szFile,MAX_PATH);
hFile = CreateFile(szFile,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
dwFileSize = GetFileSize(hFile, NULL);
lpBuffer = new BYTE[dwFileSize];
ReadFile(hFile, lpBuffer, dwFileSize, &dwBytesRead, NULL);
HANDLE hResource = BeginUpdateResource(szFilePath, FALSE);
UpdateResource(hResource,RT_RCDATA,MAKEINTRESOURCE(EditId),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPVOID)lpBuffer,dwFileSize);
EndUpdateResource(hResource, FALSE);
delete [] lpBuffer;
CloseHandle(hFile);
return 1;

可執行程式讀取自身資源,把資源寫到指定路徑下的程式碼如下:

HMODULE hInstance = ::GetModuleHandle(NULL);
TCHAR szFilePath[MAX_PATH + 1];
GetPath(szFilePath,resourceName,hInstance);
HRSRC hResID = ::FindResource(hInstance,resourceID,RT_RCDATA);
HGLOBAL hRes = ::LoadResource(hInstance,hResID);
LPVOID pRes = ::LockResource(hRes);
DWORD dwResSize = ::SizeofResource(hInstance,hResID);
if(!dwResSize) return 0;
HANDLE hResFile = CreateFile(szFilePath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
DWORD dwWritten = 0;
WriteFile(hResFile,pRes,dwResSize,&dwWritten,NULL);
CloseHandle(hResFile);
if(dwResSize == dwWritten) return 1;
return 0;

這兩段程式碼是從我的一個專案中摘抄出來的,僅供參考。

遺留的問題
我們並沒有考慮多Electron版本共存的問題;
此方案高度依賴Windows API,跨平臺實現差異肯定會比較大;
應用程式啟動後,工作列的圖示是Electron Runtime的圖示,而非Runtime使用者指定的圖示(這是有解決辦法的);

 

相關文章