Multi-instance app support added.
This commit is contained in:
parent
84b0762820
commit
fc3a513ada
3 changed files with 166 additions and 5 deletions
|
|
@ -66,6 +66,7 @@ static void updateTime(void);
|
||||||
AppDescriptorT appDescriptor = {
|
AppDescriptorT appDescriptor = {
|
||||||
.name = "Clock",
|
.name = "Clock",
|
||||||
.hasMainLoop = true,
|
.hasMainLoop = true,
|
||||||
|
.multiInstance = true,
|
||||||
.stackSize = 0,
|
.stackSize = 0,
|
||||||
.priority = TS_PRIORITY_LOW
|
.priority = TS_PRIORITY_LOW
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ int32_t sCurrentAppId = 0;
|
||||||
static int32_t allocSlot(void);
|
static int32_t allocSlot(void);
|
||||||
static void appTaskWrapper(void *arg);
|
static void appTaskWrapper(void *arg);
|
||||||
static const char *baseName(const char *path);
|
static const char *baseName(const char *path);
|
||||||
|
static void cleanupTempFile(ShellAppT *app);
|
||||||
|
static int32_t copyFile(const char *src, const char *dst);
|
||||||
|
static bool isPathLoaded(const char *path);
|
||||||
|
static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize);
|
||||||
void shellAppInit(void);
|
void shellAppInit(void);
|
||||||
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
|
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
|
||||||
ShellAppT *shellGetApp(int32_t appId);
|
ShellAppT *shellGetApp(int32_t appId);
|
||||||
|
|
@ -88,6 +92,96 @@ static const char *baseName(const char *path) {
|
||||||
return slash ? slash + 1 : path;
|
return slash ? slash + 1 : path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Delete the temp file for a multi-instance app copy, if one exists.
|
||||||
|
static void cleanupTempFile(ShellAppT *app) {
|
||||||
|
if (app->tempPath[0] != '\0') {
|
||||||
|
remove(app->tempPath);
|
||||||
|
app->tempPath[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Binary file copy. Returns 0 on success, -1 on failure.
|
||||||
|
static int32_t copyFile(const char *src, const char *dst) {
|
||||||
|
FILE *in = fopen(src, "rb");
|
||||||
|
|
||||||
|
if (!in) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *out = fopen(dst, "wb");
|
||||||
|
|
||||||
|
if (!out) {
|
||||||
|
fclose(in);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[4096];
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
|
||||||
|
if (fwrite(buf, 1, n, out) != n) {
|
||||||
|
fclose(in);
|
||||||
|
fclose(out);
|
||||||
|
remove(dst);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(in);
|
||||||
|
fclose(out);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check if a DXE path is already loaded in any active slot.
|
||||||
|
static bool isPathLoaded(const char *path) {
|
||||||
|
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||||
|
if (sApps[i].state != AppStateFreeE && strcmp(sApps[i].path, path) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Build a temp path for a multi-instance copy. Uses the TEMP or TMP
|
||||||
|
// environment variable if set, otherwise falls back to the current directory.
|
||||||
|
// The slot ID is embedded in the filename to ensure uniqueness.
|
||||||
|
// Example: TEMP=C:\TEMP -> "C:\TEMP\_dvx02.app"
|
||||||
|
static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize) {
|
||||||
|
// Find extension from original path
|
||||||
|
const char *lastSlash = strrchr(origPath, '/');
|
||||||
|
const char *lastBack = strrchr(origPath, '\\');
|
||||||
|
|
||||||
|
if (lastBack > lastSlash) {
|
||||||
|
lastSlash = lastBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *dot = strrchr(origPath, '.');
|
||||||
|
|
||||||
|
if (!dot || (lastSlash && dot < lastSlash)) {
|
||||||
|
dot = origPath + strlen(origPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *tmpDir = getenv("TEMP");
|
||||||
|
|
||||||
|
if (!tmpDir) {
|
||||||
|
tmpDir = getenv("TMP");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmpDir && tmpDir[0]) {
|
||||||
|
snprintf(out, outSize, "%s/_dvx%02ld%s", tmpDir, (long)id, dot);
|
||||||
|
} else {
|
||||||
|
snprintf(out, outSize, "_dvx%02ld%s", (long)id, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Public API (alphabetical)
|
// Public API (alphabetical)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -129,6 +223,7 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
|
||||||
app->dxeHandle = NULL;
|
app->dxeHandle = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanupTempFile(app);
|
||||||
app->state = AppStateFreeE;
|
app->state = AppStateFreeE;
|
||||||
shellLog("Shell: force-killed app '%s'", app->name);
|
shellLog("Shell: force-killed app '%s'", app->name);
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +247,14 @@ ShellAppT *shellGetApp(int32_t appId) {
|
||||||
// Each .app file is a DXE3 shared object that exports _appDescriptor and
|
// Each .app file is a DXE3 shared object that exports _appDescriptor and
|
||||||
// _appMain (and optionally _appShutdown). The leading underscore is the
|
// _appMain (and optionally _appShutdown). The leading underscore is the
|
||||||
// COFF symbol convention; DJGPP's dlsym expects it.
|
// COFF symbol convention; DJGPP's dlsym expects it.
|
||||||
|
//
|
||||||
|
// Multi-instance support: DXE3's dlopen returns the same handle for the
|
||||||
|
// same path (reference-counted), so two loads of the same .dxe share all
|
||||||
|
// global/static state — only one main loop runs and closing one kills both.
|
||||||
|
// If the app's descriptor sets multiInstance=true, we copy the .dxe to a
|
||||||
|
// unique temp file before dlopen, giving each instance its own code+data.
|
||||||
|
// If multiInstance=false (the default), a second load of the same path is
|
||||||
|
// blocked with an error message.
|
||||||
int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
// Allocate a slot
|
// Allocate a slot
|
||||||
int32_t id = allocSlot();
|
int32_t id = allocSlot();
|
||||||
|
|
@ -161,13 +264,56 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this DXE is already loaded. If so, we need to inspect its
|
||||||
|
// descriptor to decide whether to allow a second instance.
|
||||||
|
const char *loadPath = path;
|
||||||
|
char tempPath[260] = {0};
|
||||||
|
|
||||||
|
if (isPathLoaded(path)) {
|
||||||
|
// Peek at the descriptor from the already-loaded module to check
|
||||||
|
// multiInstance. Since it's already loaded, dlopen returns the
|
||||||
|
// existing handle (cheap, no actual reload).
|
||||||
|
void *peek = dlopen(path, RTLD_GLOBAL);
|
||||||
|
|
||||||
|
if (peek) {
|
||||||
|
AppDescriptorT *peekDesc = (AppDescriptorT *)dlsym(peek, "_appDescriptor");
|
||||||
|
bool allow = peekDesc && peekDesc->multiInstance;
|
||||||
|
dlclose(peek);
|
||||||
|
|
||||||
|
if (!allow) {
|
||||||
|
char msg[320];
|
||||||
|
snprintf(msg, sizeof(msg), "%s is already running.", baseName(path));
|
||||||
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-instance allowed: copy to a temp file so dlopen gets
|
||||||
|
// an independent code+data image.
|
||||||
|
makeTempPath(path, id, tempPath, sizeof(tempPath));
|
||||||
|
|
||||||
|
if (copyFile(path, tempPath) != 0) {
|
||||||
|
char msg[320];
|
||||||
|
snprintf(msg, sizeof(msg), "Failed to create instance copy of %s.", baseName(path));
|
||||||
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPath = tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
// Load the DXE
|
// Load the DXE
|
||||||
void *handle = dlopen(path, RTLD_GLOBAL);
|
void *handle = dlopen(loadPath, RTLD_GLOBAL);
|
||||||
|
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
char msg[512];
|
char msg[512];
|
||||||
snprintf(msg, sizeof(msg), "Failed to load %s:\n%s", baseName(path), dlerror());
|
snprintf(msg, sizeof(msg), "Failed to load %s:\n%s", baseName(path), dlerror());
|
||||||
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||||
|
|
||||||
|
if (tempPath[0]) {
|
||||||
|
remove(tempPath);
|
||||||
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,6 +325,11 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
snprintf(msg, sizeof(msg), "%s: missing appDescriptor", baseName(path));
|
snprintf(msg, sizeof(msg), "%s: missing appDescriptor", baseName(path));
|
||||||
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||||
dlclose(handle);
|
dlclose(handle);
|
||||||
|
|
||||||
|
if (tempPath[0]) {
|
||||||
|
remove(tempPath);
|
||||||
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,6 +340,11 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
snprintf(msg, sizeof(msg), "%s: missing appMain", baseName(path));
|
snprintf(msg, sizeof(msg), "%s: missing appMain", baseName(path));
|
||||||
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||||
dlclose(handle);
|
dlclose(handle);
|
||||||
|
|
||||||
|
if (tempPath[0]) {
|
||||||
|
remove(tempPath);
|
||||||
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,6 +357,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
app->appId = id;
|
app->appId = id;
|
||||||
snprintf(app->name, SHELL_APP_NAME_MAX, "%s", desc->name);
|
snprintf(app->name, SHELL_APP_NAME_MAX, "%s", desc->name);
|
||||||
snprintf(app->path, sizeof(app->path), "%s", path);
|
snprintf(app->path, sizeof(app->path), "%s", path);
|
||||||
|
snprintf(app->tempPath, sizeof(app->tempPath), "%s", tempPath);
|
||||||
app->dxeHandle = handle;
|
app->dxeHandle = handle;
|
||||||
app->hasMainLoop = desc->hasMainLoop;
|
app->hasMainLoop = desc->hasMainLoop;
|
||||||
app->entryFn = entry;
|
app->entryFn = entry;
|
||||||
|
|
@ -304,6 +461,7 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
||||||
app->dxeHandle = NULL;
|
app->dxeHandle = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanupTempFile(app);
|
||||||
shellLog("Shell: reaped app '%s'", app->name);
|
shellLog("Shell: reaped app '%s'", app->name);
|
||||||
app->state = AppStateFreeE;
|
app->state = AppStateFreeE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[SHELL_APP_NAME_MAX];
|
char name[SHELL_APP_NAME_MAX];
|
||||||
bool hasMainLoop;
|
bool hasMainLoop;
|
||||||
|
bool multiInstance; // true = allow multiple instances via temp copy
|
||||||
int32_t stackSize; // 0 = TS_DEFAULT_STACK_SIZE
|
int32_t stackSize; // 0 = TS_DEFAULT_STACK_SIZE
|
||||||
int32_t priority; // TS_PRIORITY_* or custom
|
int32_t priority; // TS_PRIORITY_* or custom
|
||||||
} AppDescriptorT;
|
} AppDescriptorT;
|
||||||
|
|
@ -84,6 +85,7 @@ typedef struct {
|
||||||
int32_t appId; // unique ID = slot index (1-based; 0 = shell)
|
int32_t appId; // unique ID = slot index (1-based; 0 = shell)
|
||||||
char name[SHELL_APP_NAME_MAX];
|
char name[SHELL_APP_NAME_MAX];
|
||||||
char path[260];
|
char path[260];
|
||||||
|
char tempPath[260]; // temp copy path for multi-instance (empty = not a copy)
|
||||||
void *dxeHandle; // dlopen() handle
|
void *dxeHandle; // dlopen() handle
|
||||||
AppStateE state;
|
AppStateE state;
|
||||||
bool hasMainLoop;
|
bool hasMainLoop;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue