乐闻世界logo
搜索文章和话题

How to Implement Native Modules in Electron

2月18日 10:36

Native modules allow Electron applications to use native code like C/C++, which can significantly improve performance or access low-level system functions. This article will detail how to implement native modules in Electron.

Native Module Overview

Native modules are Node.js modules written in C/C++ that interact with JavaScript through Node.js's N-API (Node API) or NAN (Node.js Native Abstractions for Node.js).

Why Native Modules Are Needed

  1. Performance Optimization: Handle intensive computational tasks
  2. System Access: Access low-level operating system APIs
  3. Hardware Interaction: Communicate with hardware devices
  4. Existing Library Integration: Use existing C/C++ libraries

Creating Native Modules with N-API

N-API is a stable ABI (Application Binary Interface) interface provided by Node.js, recommended for use.

1. Project Structure

shell
native-module/ ├── binding.gyp ├── package.json ├── src/ │ └── addon.cpp └── index.js

2. Configure binding.gyp

python
# binding.gyp { "targets": [ { "target_name": "addon", "sources": [ "src/addon.cpp" ], "include_dirs": [ "<!(node -e \"require('nan')\")" ] } ] }

3. Write C++ Code

cpp
// src/addon.cpp #include <node_api.h> #include <string> napi_value Add(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2]; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); double a, b; napi_get_value_double(env, args[0], &a); napi_get_value_double(env, args[1], &b); napi_value sum; napi_create_double(env, a + b, &sum); return sum; } napi_value Init(napi_env env, napi_value exports) { napi_value add_fn; napi_create_function(env, nullptr, 0, Add, nullptr, &add_fn); napi_set_named_property(env, exports, "add", add_fn); return exports; } NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

4. Compile Native Module

bash
# Install dependencies npm install node-gyp --save-dev # Compile node-gyp configure node-gyp build # Or use npm scripts npm run build

5. Use in Electron

javascript
// index.js const addon = require('./build/Release/addon') const result = addon.add(10, 20) console.log(result) // 30

Creating Native Modules with NAN

NAN (Node.js Native Abstractions for Node.js) provides a simpler API but is less stable than N-API.

1. Install NAN

bash
npm install nan --save-dev

2. Write C++ Code

cpp
// src/addon.cpp #include <nan.h> using namespace v8; void Add(const Nan::FunctionCallbackInfo<Value>& info) { Isolate* isolate = info.GetIsolate(); if (info.Length() < 2) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong number of arguments").ToLocalChecked())); return; } if (!info[0]->IsNumber() || !info[1]->IsNumber()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked())); return; } double a = info[0].As<Number>()->Value(); double b = info[1].As<Number>()->Value(); double sum = a + b; info.GetReturnValue().Set(sum); } void Init(Local<Object> exports) { Nan::SetMethod(exports, "add", Add); } NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

Using Native Modules in Electron

1. Configure package.json

json
{ "name": "my-electron-app", "version": "1.0.0", "main": "main.js", "dependencies": { "electron-rebuild": "^3.2.9" }, "scripts": { "rebuild": "electron-rebuild", "start": "electron ." } }

2. Rebuild Native Module

bash
# Install electron-rebuild npm install --save-dev electron-rebuild # Rebuild native module to match Electron version npm run rebuild

3. Use in Main Process

javascript
// main.js const { app, BrowserWindow, ipcMain } = require('electron') const addon = require('./build/Release/addon') let mainWindow app.whenReady().then(() => { mainWindow = new BrowserWindow({ width: 800, height: 600 }) mainWindow.loadFile('index.html') }) // Use native module for compute-intensive tasks ipcMain.handle('heavy-computation', async (event, data) => { const result = addon.performComputation(data) return result })

4. Use in Renderer Process

javascript
// renderer.js const { ipcRenderer } = require('electron') async function performHeavyComputation(data) { try { const result = await ipcRenderer.invoke('heavy-computation', data) return result } catch (error) { console.error('Computation failed:', error) } } // Usage document.getElementById('compute').addEventListener('click', async () => { const data = { /* computation data */ } const result = await performHeavyComputation(data) console.log('Result:', result) })

Native Module Best Practices

1. Error Handling

cpp
// src/addon.cpp void SafeOperation(const Nan::FunctionCallbackInfo<Value>& info) { Isolate* isolate = info.GetIsolate(); try { // Execute operations that may fail double result = performOperation(); info.GetReturnValue().Set(result); } catch (const std::exception& e) { isolate->ThrowException(Exception::Error( String::NewFromUtf8(isolate, e.what()).ToLocalChecked())); } }

2. Memory Management

cpp
// src/addon.cpp void ProcessData(const Nan::FunctionCallbackInfo<Value>& info) { Isolate* isolate = info.GetIsolate(); // Get input data Local<Array> inputArray = Local<Array>::Cast(info[0]); size_t length = inputArray->Length(); // Allocate memory double* buffer = new double[length]; // Process data for (size_t i = 0; i < length; i++) { Local<Value> element = inputArray->Get(i); buffer[i] = element->NumberValue(isolate->GetCurrentContext()).ToChecked(); } // Execute computation double result = compute(buffer, length); // Free memory delete[] buffer; info.GetReturnValue().Set(result); }

3. Async Operations

cpp
// src/addon.cpp #include <uv.h> struct AsyncData { uv_work_t request; Nan::Persistent<Function> callback; double input; double result; }; void AsyncWork(uv_work_t* req) { AsyncData* data = static_cast<AsyncData*>(req->data); // Execute time-consuming operation data->result = performHeavyComputation(data->input); } void AsyncComplete(uv_work_t* req, int status) { Nan::HandleScope scope; AsyncData* data = static_cast<AsyncData*>(req->data); Local<Value> argv[] = { Nan::Null(), Nan::New(data->result) }; Local<Function> callback = Nan::New(data->callback); callback->Call(Nan::Null(), 2, argv); delete data; } void AsyncComputation(const Nan::FunctionCallbackInfo<Value>& info) { AsyncData* data = new AsyncData(); data->request.data = data; data->input = info[0]->NumberValue(info.GetIsolate()->GetCurrentContext()).ToChecked(); data->callback.Reset(info[1].As<Function>()); uv_queue_work(uv_default_loop(), &data->request, AsyncWork, AsyncComplete); }

Common Application Scenarios

1. Image Processing

cpp
#include <opencv2/opencv.hpp> void ProcessImage(const Nan::FunctionCallbackInfo<Value>& info) { // Use OpenCV to process images cv::Mat image = cv::imread("input.jpg"); cv::GaussianBlur(image, image, cv::Size(5, 5), 0); cv::imwrite("output.jpg", image); info.GetReturnValue().Set(Nan::New("Image processed").ToLocalChecked()); }

2. File System Operations

cpp
#include <fstream> void ReadFile(const Nan::FunctionCallbackInfo<Value>& info) { String::Utf8Value path(info[0]); std::ifstream file(*path, std::ios::binary); std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); info.GetReturnValue().Set(Nan::New(content).ToLocalChecked()); }

3. Encryption/Decryption

cpp
#include <openssl/aes.h> void Encrypt(const Nan::FunctionCallbackInfo<Value>& info) { // Use OpenSSL for encryption unsigned char key[16] = { /* 16-byte key */ }; unsigned char iv[16] = { /* 16-byte IV */ }; // Execute encryption operation // ... info.GetReturnValue().Set(Nan::New("Encrypted").ToLocalChecked()); }

Debugging Native Modules

1. Debug with GDB

bash
# Compile debug version node-gyp configure --debug node-gyp build --debug # Debug with GDB gdb --args electron .

2. Add Logging

cpp
#include <iostream> void DebugFunction(const Nan::FunctionCallbackInfo<Value>& info) { std::cout << "Debug: Function called" << std::endl; // Execute operation std::cout << "Debug: Function completed" << std::endl; }

3. Use Node.js Debugger

javascript
// main.js const addon = require('./build/Release/addon') // Use debugger statement debugger const result = addon.add(10, 20)

Cross-Platform Compatibility

1. Conditional Compilation

cpp
// src/addon.cpp #if defined(_WIN32) #include <windows.h> #elif defined(__APPLE__) #include <CoreFoundation/CoreFoundation.h> #elif defined(__linux__) #include <unistd.h> #endif void PlatformSpecificFunction(const Nan::FunctionCallbackInfo<Value>& info) { #if defined(_WIN32) // Windows-specific code #elif defined(__APPLE__) // macOS-specific code #elif defined(__linux__) // Linux-specific code #endif }

2. Unified Interface

cpp
// Provide cross-platform unified interface void GetSystemInfo(const Nan::FunctionCallbackInfo<Value>& info) { Isolate* isolate = info.GetIsolate(); Local<Object> result = Object::New(isolate); #if defined(_WIN32) SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); result->Set(isolate->GetCurrentContext(), Nan::New("processors").ToLocalChecked(), Nan::New(sysInfo.dwNumberOfProcessors)); #elif defined(__APPLE__) || defined(__linux__) long processors = sysconf(_SC_NPROCESSORS_ONLN); result->Set(isolate->GetCurrentContext(), Nan::New("processors").ToLocalChecked(), Nan::New(processors)); #endif info.GetReturnValue().Set(result); }

Performance Optimization

1. Reduce Data Copying

cpp
// Avoid unnecessary data copying void ProcessArray(const Nan::FunctionCallbackInfo<Value>& info) { Local<Array> array = Local<Array>::Cast(info[0]); // Directly access array elements, avoid copying for (uint32_t i = 0; i < array->Length(); i++) { Local<Value> element = array->Get(i); // Process element } }

2. Use Caching

cpp
// Cache computation results static std::unordered_map<std::string, double> cache; void CachedComputation(const Nan::FunctionCallbackInfo<Value>& info) { String::Utf8Value key(info[0]); auto it = cache.find(*key); if (it != cache.end()) { info.GetReturnValue().Set(it->second); return; } double result = performComputation(*key); cache[*key] = result; info.GetReturnValue().Set(result); }

Common Questions

Q: What to do if native module cannot be loaded in Electron?A: Make sure to use electron-rebuild to rebuild the native module to match Electron's Node.js version.

Q: How to share native modules between multiple Electron versions?A: Create native modules using N-API, N-API provides stable ABI that can be shared across different Node.js versions.

Q: Will native modules increase application size?A: Yes, but the increase is usually small. You can control the size by optimizing code and reducing dependencies.

Q: How to test native modules?A: Use Node.js native testing frameworks like node-test-runner, or write JavaScript test cases to call native modules for testing.

标签:Electron