For the server you're about to build: `jetc --shared template.jet out.cpp`
now emits no main() at all - instead the render logic is exported as
`extern "C" void jetc_render_<template>(jet::Global &global,
coreutils::MString &out)`, one call per request, meant to be dlsym()'d
by a long-running host process instead of spawning a jet-2.0 process (or
even a separate compiled executable) per request. extern "C" here only
fixes the symbol's name mangling for a stable dlsym() target - the
parameter types are still real C++ classes, safe to pass across the
dlopen() boundary since the host and the .so share the same
CoreUtils/JetCore headers and toolchain (a same-ABI plugin boundary, not
a foreign C one). coreutils::Exception propagates out of the call
uncaught, so the host decides how to handle a render failure rather than
the module unilaterally deciding (the executable mode's try/catch prints
to stdout and exits, which is CGI-appropriate but wrong for a persistent
server). The exported symbol name is derived from the input filename
(jetc.cpp's baseNameNoExt+sanitizeIdent) so multiple compiled templates'
.so's stay collision-free even under dlopen(..., RTLD_GLOBAL).
jetc/build-shared.sh builds the .so: JetCore's 22 tag classes are still
force-linked directly (Tag::interpretSpan's fallback path can dispatch
to any of them at runtime, regardless of what a given template
statically references), but CoreUtils objects are compiled -fPIC and
archived into a throwaway libCoreUtils_pic.a instead of being linked
directly - libCoreUtils.a itself isn't position-independent, so linking
it into a .so fails outright ("relocation ... can not be used when
making a shared object"), and force-linking every CoreUtils .cpp
directly (rather than archiving, so the linker only pulls in what's
actually referenced) surfaced an unrelated, pre-existing incomplete
class - IMFMessage's vtable is never emitted anywhere, presumably a
partially-migrated class (see project memory on IMFMultipart/IMFText
still being fd-based) - as an unresolved dlopen()-time symbol, even
though nothing here uses it. A normal archive-based link (like the
existing libCoreUtils.a already does for jet-2.0) never pulls IMFMessage
in at all, so building libCoreUtils_pic.a the same way sidesteps it
without touching the unrelated bug itself.
jetc/host_test.cpp is a minimal stand-in for the real server: dlopen()s
a compiled .so, dlsym()s its entry point, and calls it twice with fresh
jet::Global objects (simulating two separate requests against one loaded
module) to prove the .so is genuinely reusable, not single-use. Verified
both demo templates (demo.jet, demo2.jet - the latter covering nested
if/for, a stateful while loop, and the interpretSpan fallback path) build
as .so's and produce the same byte-identical output as the executable
and interpreter paths, across two calls each. Also re-verified the
existing (non-shared) executable build path still matches jet-2.0's
output after the Codegen::generate() signature change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
52 lines
1.6 KiB
C++
Executable File
52 lines
1.6 KiB
C++
Executable File
// Minimal stand-in for the not-yet-built server: dlopen()s a compiled
|
|
// template .so and calls its exported render entry point directly, the
|
|
// way a persistent content-provider process would (no process spawn per
|
|
// request, no re-parsing the .jet source - both the compile step AND
|
|
// the shared-object load happen once, ahead of time).
|
|
#include "Global.h"
|
|
#include "MString.h"
|
|
#include "Exception.h"
|
|
#include <dlfcn.h>
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
|
|
typedef void (*RenderFn)(jet::Global &, coreutils::MString &);
|
|
|
|
int main(int argc, char **argv, char **envp) {
|
|
if (argc < 3) {
|
|
std::cerr << "usage: host_test <module.so> <symbol>" << std::endl;
|
|
return 1;
|
|
}
|
|
void *handle = dlopen(argv[1], RTLD_NOW);
|
|
if (!handle) {
|
|
std::cerr << "dlopen failed: " << dlerror() << std::endl;
|
|
return 1;
|
|
}
|
|
dlerror();
|
|
RenderFn render = (RenderFn)dlsym(handle, argv[2]);
|
|
const char *err = dlerror();
|
|
if (err) {
|
|
std::cerr << "dlsym failed: " << err << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Simulate the host calling the SAME module twice, for two different
|
|
// "requests", to prove the .so is reusable across calls without being
|
|
// reloaded - the actual point of a persistent content provider.
|
|
for (int i = 0; i < 2; ++i) {
|
|
jet::Global global(envp);
|
|
coreutils::MString out;
|
|
try {
|
|
render(global, out);
|
|
global.outputHeaders();
|
|
std::cout << "--- render call " << i << " ---" << std::endl;
|
|
std::cout << out;
|
|
} catch (coreutils::Exception &e) {
|
|
std::cout << "render call " << i << " threw: " << e.text << std::endl;
|
|
}
|
|
}
|
|
|
|
dlclose(handle);
|
|
return 0;
|
|
}
|