Aller au contenu

Comment appeler les API natives Windows depuis Electron

Modifier cette page
HagiCode for Windows Microsoft Store artwork
HagiCode for Windows is now on Microsoft Store
HagiCode for Windows is officially live on Microsoft Store. Windows users can install it directly from the storefront and stay on the store-managed update path. Open the listing and take a look.
Open Microsoft Store

Comment appeler les API natives Windows depuis Electron

Appeler les API natives Windows dans une application Electron, c’est comme vouloir voir la mer mais ne pouvoir regarder qu’une carte. Après quelques efforts, j’ai finalement trouvé quelques chemins, et j’écris cet article comme souvenir et pour indiquer la direction aux futurs développeurs.

Contexte

Lors du développement d’applications de bureau Electron, il est inévitable de devoir interagir avec le système d’exploitation. Sur Windows, ces besoins sont nombreux :

  • Appeler les API du Microsoft Store pour gérer les achats in-app
  • Gérer la virtualisation du système de fichiers spécifique aux applications du Microsoft Store
  • Obtenir des permissions et ressources au niveau système
  • Interagir avec les composants Windows Runtime (WinRT)

Electron est fondamentalement un environnement Node.js, et Node.js ne fournit pas directement la capacité d’accéder aux API natives Windows. Il faut un pont entre les deux.

C’est comme si vous vouliez communiquer avec un ami qui ne parle pas chinois : il vous faut un interprète. Electron est écrit en JavaScript, les API Windows en C/C++, les langues sont différentes, il faut trouver un moyen de construire un pont. La cruauté du monde du code est là, pas de sentiments humains.

À propos de HagiCode

Les solutions partagées dans cet article proviennent de notre expérience pratique dans le projet HagiCode. HagiCode Desktop doit appeler les API Microsoft Store pour gérer les achats d’abonnement et la gestion des licences, c’est pourquoi nous avons exploré une solution technique. Après tout, c’est le besoin qui crée la motivation, cette affirmation est tout à fait vraie.

Comparaison des solutions techniques

Pour appeler les API natives Windows dans Electron, il existe plusieurs solutions principales. Chaque solution a son cas d’utilisation, comme les différents outils dans une boîte à outils : utilisés au bon endroit, ils peuvent发挥 leur maximum d’efficacité ; utilisés au mauvais endroit, ils ne font qu’ajouter des complications.

SolutionCas d’utilisationAvantagesInconvénients
dynwinrtAPI WinRT (comme Store API)Type sûr, génération automatique de bindings, support JavaScript moderneNe supporte que les API WinRT, nécessite Windows SDK
Extension Node.js nativeHaute performance, toutes les API WindowsContrôle complet, performance optimaleNécessite des compétences C++, complexité cross-plateforme
child_process + PowerShellAppels temporaires, uniquesSimple et rapide, pas besoin de compilationPerformance médiocre, gestion des erreurs complexe
edge.js/ffi-napiAppeler des DLL existantesRéutiliser des bibliothèques existantesProblèmes de compatibilité, coûts de maintenance élevés

HagiCode Desktop adopte une solution hybride : utiliser dynwinrt pour accéder aux API Microsoft Store, utiliser des extensions Node.js natives pour traiter les opérations d’achat Store haute performance, et utiliser les modules natifs fs et path de Node.js pour gérer la virtualisation du système de fichiers spécifique aux applications Microsoft Store. Simplifier quand c’est possible, c’est aussi notre principe.

Solution 1 : Utiliser dynwinrt pour appeler les API WinRT

dynwinrt est une chaîne d’outils fournie par Microsoft qui peut générer automatiquement des bindings JavaScript basés sur les fichiers de métadonnées du Windows SDK. Elle est spécialement conçue pour appeler les API WinRT, comme les API Microsoft Store.

Installer les dépendances :

{
"optionalDependencies": {
"@microsoft/dynwinrt": "0.1.0-preview.6",
"@microsoft/dynwinrt-codegen": "0.1.0-preview.6"
}
}

Générer les bindings WinRT :

scripts/generate-store-bindings.js
const { execFileSync } = 'node:child_process';
function generateStoreNamespace(windowsWinmdPath) {
execFileSync('npx', [
'dynwinrt-codegen',
'generate',
'--winmd', windowsWinmdPath,
'--namespace', 'Windows.Services.Store',
'--output', 'src/main/subscription/generated-js',
'--lang', 'js',
]);
}

Utiliser les bindings générés :

// Utiliser les bindings Store API générés par dynwinrt
import { Windows } from '../subscription/generated-js/index.js';
async function queryStoreProduct(storeId: string) {
const storeContext = Windows.Services.Store.StoreContext.getDefault();
const result = await storeContext.getAssociatedStoreProductsAsync(['Subscription', 'Durable']);
if (result.extendedError !== 0) {
throw new Error(`Store API error: ${result.extendedError}`);
}
return result.products.get(storeId);
}

L’avantage de dynwinrt est la sécurité des types, et le code généré est cohérent avec les habitudes JavaScript modernes. Mais il ne peut traiter que les API WinRT, si vous devez appeler les API Win32 traditionnelles, vous devrez utiliser une autre solution. Les outils sont comme ça, chacun a ses spécialités.

Solution 2 : Extension Node.js native

Lorsque vous avez besoin de haute performance ou de fonctionnalités non supportées par dynwinrt, les extensions Node.js natives sont le meilleur choix. Cette solution nécessite d’écrire du code en C++, puis de le compiler en fichiers .node avec node-gyp.

Créer binding.gyp :

{
"targets": [{
"target_name": "windows-store-addon",
"sources": ["src/windows-store-addon.cpp"],
"include_dirs": [
"<!(node -e \"require('nan')\")"
],
"defines": [
"WIN32_LEAN_AND_MEAN"
]
}]
}

Exemple de module natif C++ :

src/windows-store-addon.cpp
#include <nan.h>
#include <windows.h>
#include <wrl.h>
#include <windows.services.store.h>
using namespace v8;
using namespace Windows::Services::Store;
NAN_METHOD(QueryStoreStatus) {
auto async = new Nan::AsyncWorker(
[]() {
// Appeler les API Microsoft Store
auto context = StoreContext::GetDefault();
auto products = context->GetAssociatedStoreProductsAsync(...)->GetResults();
// Traiter les résultats
}
);
Nan::AsyncQueueWorker(async);
}
NAN_MODULE_INIT(InitModule) {
Nan::Set(target, Nan::New("queryStoreStatus").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(QueryStoreStatus)).ToLocalChecked());
}
NODE_MODULE(windows_store_addon, InitModule)

Compiler et utiliser :

Terminal window
node-gyp rebuild
import addon from './build/Release/windows-store-addon.node';
const result = addon.queryStoreStatus({
storeId: 'your-store-id',
productKinds: ['Subscription', 'Durable']
});

La performance des extensions natives est la meilleure, mais le coût de développement est aussi élevé. Il faut connaître C++ et gérer les problèmes de compatibilité cross-plateforme. Si votre équipe a de l’expérience C++, ou si les exigences de performance sont particulièrement élevées, cette solution vaut l’investissement. Seulement, ce chemin est finalement un peu plus difficile à parcourir.

Solution 3 : Gérer la virtualisation des applications Microsoft Store

Les applications Microsoft Store s’exécutent dans un environnement virtualisé, le mappage des chemins nécessite un traitement spécial. HagiCode Desktop utilise la fonction suivante pour traiter ce problème :

src/main/windows-store-path-display.ts
export function resolveWindowsStorePackageFamilyName(executablePath: string): string | null {
const WINDOWS_APPS_SEGMENT = '\\windowsapps\\';
const windowsPath = executablePath.replace(/\//g, '\\');
const markerIndex = windowsPath.toLowerCase().indexOf(WINDOWS_APPS_SEGMENT);
if (markerIndex < 0) return null;
const relativePath = windowsPath.slice(markerIndex + WINDOWS_APPS_SEGMENT.length);
const packageFullName = relativePath.split('\\', 1)[0]?.trim();
return packageFullName || null;
}
export function resolveWindowsStoreVirtualizedPhysicalPath(
logicalPath: string,
options: ResolveWindowsStorePathDisplayOptions = {}
): string | null {
const packageFamilyName = options.packageFamilyName
?? resolveWindowsStorePackageFamilyName(options.execPath ?? process.execPath);
if (!packageFamilyName) return null;
const packageStorageRoot = path.win32.join(
options.env.LOCALAPPDATA,
'Packages',
packageFamilyName
);
// Mapper le chemin virtuel au chemin physique
if (isPathWithinWindowsRoot(logicalPath, options.env.APPDATA)) {
return path.win32.join(
packageStorageRoot,
'LocalCache',
'Roaming',
path.win32.relative(options.env.APPDATA, logicalPath)
);
}
return null;
}

La virtualisation, c’est assez complexe à expliquer. Pour faire simple, les chemins de fichiers vus par les applications Microsoft Store sont différents des emplacements de stockage réels, il faut faire une traduction. Le code ci-dessus fait ce travail de traduction. Comme la mémoire et la réalité, parfois elles ne se superposent pas, il faut un peu de patience pour distinguer.

Expérience pratique

Détection de plateforme

Vérifiez toujours process.platform === 'win32' pour éviter d’exécuter du code spécifique à Windows sur des plateformes non-Windows. C’est une bonne habitude, comme vérifier la météo avant de sortir, pour éviter de se faire mouiller et de blâmer la mauvaise météo.

if (process.platform !== 'win32') {
return { availability: 'not-supported' };
}

Gestion des erreurs

Les appels aux API Windows peuvent échouer, il faut gérer les erreurs correctement. Nous sommes tombés dans ce piège, sans une gestion des erreurs complète, les utilisateurs ne savent pas ce qui s’est passé lorsqu’ils rencontrent des problèmes. En écrivant beaucoup de code, on comprend que la gestion des erreurs n’est pas pour autre chose, juste pour avoir moins de problèmes.

function normalizeThrownError(error: unknown): { errorCode: string | null; errorMessage: string | null } {
if (error instanceof Error) {
const errorWithCode = error as Error & { code?: unknown };
return {
errorCode: normalizeErrorCode(errorWithCode.code) ?? error.name,
errorMessage: error.message,
};
}
return { errorCode: null, errorMessage: error == null ? null : String(error) };
}

Traitement asynchrone

La plupart des API Microsoft Store sont asynchrones, utilisez Promise ou async/await. Lors de l’écriture de code asynchrone, n’oubliez pas de traiter les cas limites, comme les timeouts, les annulations, etc. Après tout, personne ne veut trop goûter à l’attente.

async function queryStatus(): Promise<RawStoreLicenseState> {
try {
const result = await storeContext.getAssociatedStoreProductsAsync(productKinds);
return buildSupportedStateFromProductQueries(result);
} catch (error) {
return buildUnavailableState(error);
}
}

Nettoyage des ressources

Assurez-vous de libérer les ressources natives lorsqu’elles ne sont plus nécessaires. Les ressources C++ ne sont pas automatiquement récupérées, la libération manuelle est une bonne habitude. Comme certaines choses, il faut les lâcher pour pouvoir avancer léger.

class MicrosoftStoreSubscriptionBroker {
private broker: StoreLicensePlatformBroker | null = null;
dispose(): void {
this.broker?.dispose();
this.broker = null;
}
}

Conversion d’horodatage

Windows utilise 1601-01-01 comme époque, il faut convertir en horodatage Unix. Ce détail est facilement ignoré, mais si le traitement n’est pas correct, les dates seront toutes fausses. Le temps, une petite différence fait une grande différence.

const WINDOWS_EPOCH_OFFSET_MILLISECONDS = 11644473600000n;
const HUNDRED_NANOSECONDS_PER_MILLISECOND = 10000n;
function toIsoDate(value: unknown): string | null {
const universalTime = (value as { universalTime?: unknown } | null)?.universalTime;
const ticks = typeof universalTime === 'bigint' ? universalTime : null;
if (ticks == null) return null;
const unixMilliseconds = ticks / HUNDRED_NANOSECONDS_PER_MILLISECOND - WINDOWS_EPOCH_OFFSET_MILLISECONDS;
return new Date(Number(unixMilliseconds)).toISOString();
}

Meilleures pratiques

Basé sur notre expérience dans le projet HagiCode, voici quelques recommandations :

  • Prioriser dynwinrt : Pour les API WinRT, dynwinrt fournit des bindings JavaScript sécurisés et modernes
  • Minimiser les extensions natives : N’utiliser les extensions natives que lorsque vous avez vraiment besoin de haute performance ou de fonctionnalités non supportées par dynwinrt
  • Compatibilité cross-plateforme : Utiliser la compilation conditionnelle ou la détection runtime pour traiter différentes plateformes
  • Couverture de tests : Tester complètement les appels aux API natives sur Windows, y compris les scénarios d’erreur
  • Documentation : Enregistrer clairement le but et les effets secondaires possibles de chaque appel d’API native

Lorsque vous écrivez du code, simplifiez quand c’est possible. Si dynwinrt peut résoudre le problème, n’allez pas écrire d’extensions C++. Les coûts de maintenance seront bien moindres. C’est aussi un petit apprentissage, pas une vérité profonde.

Conclusion

Appeler les API natives Windows est un moyen important pour les applications Electron de réaliser des fonctionnalités avancées sur la plateforme Windows. Cet article partage plusieurs solutions techniques utilisées dans le projet HagiCode Desktop : dynwinrt pour les API WinRT, les extensions Node.js natives pour les scénarios haute performance, et le traitement des chemins virtualisés pour l’accès aux fichiers des applications Store.

Le choix de la solution dépend de vos besoins spécifiques. Si vous appelez simplement des API WinRT, dynwinrt est le choix le plus simple. Si vous avez besoin de haute performance ou des API Win32 traditionnelles, les extensions natives sont nécessaires. Pour des opérations temporaires, utiliser child_process pour appeler PowerShell fonctionne aussi. Tous les chemins mènent à Rome, certains sont juste un peu plus faciles, d’autres un peu plus sinueux.

Quelle que soit la solution utilisée, rappelez-vous ces principes : faire la détection de plateforme, gérer les erreurs correctement, traiter l’asynchronisme, nettoyer les ressources à temps. Ces détails déterminent la robustesse du code. En écrivant du code longtemps, vous comprendrez que les détails sont souvent plus importants que les grands frameworks.

Si vous faites aussi un développement similaire, j’espère que cette expérience pourra vous aider. Dans la technique, plus on tombe dans des pièges, plus on a d’expérience. Comme dans la vie, plus on tombe, plus on apprend à marcher…

Références

Résumé

Autour de « Comment appeler les API natives Windows depuis Electron », une méthode plus sûre pour avancer est de faire fonctionner progressivement les configurations clés, les limites de dépendances et les chemins de mise en œuvre, puis de compléter les détails d’optimisation.

Une fois les objectifs, les étapes et les points de validation clairs, ce type de solution peut généralement entrer plus facilement dans la livraison réelle.

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。