]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Create parallel C and C++ test/demo shell extensions, with header tweaks to make...
authorlarrybr <larrybr@noemail.net>
Sun, 10 Apr 2022 09:17:29 +0000 (09:17 +0000)
committerlarrybr <larrybr@noemail.net>
Sun, 10 Apr 2022 09:17:29 +0000 (09:17 +0000)
FossilOrigin-Name: 2596e7c439b6c4c89921079a78ba6174e99f766b30fab8763be20c1b631e49ec

manifest
manifest.uuid
src/obj_interfaces.h
src/shell.c.in
src/shext_linkage.h
src/test_shellext.c
src/test_shellext.cpp [new file with mode: 0644]

index 50183c67da386083037fcdfeab823c5b9ab5b77e..3cd411c81a17a3b7ae57e11317e697394fe7f576 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Sync\sw/trunk,\sfor\s.import\sfix.
-D 2022-04-09T19:39:58.635
+C Create\sparallel\sC\sand\sC++\stest/demo\sshell\sextensions,\swith\sheader\stweaks\sto\smake\sthat\swork.\sFix\sa\sbug\sexposed\swhen\smultiple\sshell\sextensions\swere\sloaded.
+D 2022-04-10T09:17:29.899
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -534,7 +534,7 @@ F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4
 F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d
 F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541
 F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6
-F src/obj_interfaces.h ea17e13ea521955fcdbeb2257b2439d2047b4c5d042a971a4639846023a9f373
+F src/obj_interfaces.h c80525ea603357a79ddae45ba98eec2ac60a9092d1800811d92f3a0e0208d8a6
 F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab
 F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8
 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
@@ -556,8 +556,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c 18d99e7146852d6064559561769fcca0743eb32b14a97da6dbed373a30ee0e76
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 7c106b3f36d483242b0a9c696614cd53d6f29e1ac81da6a3f0e9ea92f4211cc3
-F src/shell.c.in 3984e1de9e70357ea0a017409f4421eacf94175a30969d68f6cc135a4c1fb9f9
-F src/shext_linkage.h 88a3f215fdb090fcc3b3577a05bafd234f1a556bad3f2f4ac990a177aebf4b2c
+F src/shell.c.in 9ae99f072005ee180a06c10ba53f465ae1db315573abc7767c1060c1b3c0198d
+F src/shext_linkage.h 4f58ea112273f3e6769221739b7160852cf0d7cdd4ee757f6c696e90fb7ffcb9
 F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
@@ -606,7 +606,8 @@ F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d
 F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
 F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
 F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
-F src/test_shellext.c 0794ffe8c7762d027a28a2b445277c72a68b28eafba27d38ef197a9655599b42
+F src/test_shellext.c fa658ee13e39d2b4bbb49c1b7fe060990ee0928e0412ffcdecbf98ddba3ef12e
+F src/test_shellext.cpp 95d1f67570683b0cc61e6b5a447aadba39ffe10fffa7d6015ba855fb05f10865
 F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
 F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
 F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
@@ -1951,8 +1952,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 43eb311e517b79cde9e17c1a80baed8d13d9d943dd9ee44b31831159df8715fc 21e96600d90c1cda84777abe22a11058eba46c9faefeb05f8c31bc0e7fa84b19
-R 67ea5330fa92b3979fbd7e883fdffd1f
+P 861ab023be283782131df5ccd9eff50cf2f06f48f1ea7defd3f08177a3df5889
+R 29bf4bbfab5e25a57abea25dca7857f3
 U larrybr
-Z b47b9cb62392429660769c050bed79ba
+Z 916c41d1a88108766c11b193884acec2
 # Remove this line to create a well-formed Fossil manifest.
index 6826a57cdb69bf5acee453d6de5354e99c35d5ef..30250f8904903be1965b10d48e1c1bf101c365bf 100644 (file)
@@ -1 +1 @@
-861ab023be283782131df5ccd9eff50cf2f06f48f1ea7defd3f08177a3df5889
\ No newline at end of file
+2596e7c439b6c4c89921079a78ba6174e99f766b30fab8763be20c1b631e49ec
\ No newline at end of file
index bb632aa76b92b0907926d50bee04f8c11bfc729a..bad9431369f658456c0ad17df29e85c9646f8a2a 100644 (file)
@@ -76,9 +76,8 @@
  ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args )
 
 #ifdef __cplusplus
-# define INTERFACE_BEGIN(iname) struct iname { \
-    PURE_VMETHOD(void, destruct, iname, 0, ())
-# define INTERFACE_END(iname) }
+# define INTERFACE_BEGIN(iname) struct iname { virtual void destruct() = 0
+# define INTERFACE_END(iname) };
 # define CONCRETE_BEGIN(iname, derived) class derived : public iname { \
     CONCRETE_METHOD(void, destruct, derived, 0, ())
 # define CONCRETE_END(derived) }
  ARG_FIRST_ ## na(ot) ARGS_EXPAND(na)args )
 #endif
 
+/* Some preprocessing to make struct type definitions usable for C and C++
+ * code using just the typename. Usage:
+ *   AGGTYPE_BEGIN(Framus) { member; ... } AGGTYPE_END(Framus);
+ */
+#ifndef __cplusplus
+#define AGGTYPE_BEGIN(tname) typedef struct tname
+#define AGGTYPE_END(tname) tname
+#else
+#define AGGTYPE_BEGIN(tname) struct tname
+#define AGGTYPE_END(tname)
+#endif
+
 #endif /* !defined(OBJIFACE_H) */
index 74c6ed74e0e959807e6c0ea98b6248688e0c8d4e..744f55229a453763932d72de696585f26728c2da 100644 (file)
@@ -8103,9 +8103,11 @@ static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
         pes->pvUserData = pvUserData;
         return SQLITE_OK;
       }
+      ++pes;
     }
     assert(pes==pesLim);
-    pes = sqlite3_realloc(pes, (psi->numSubscriptions+1)*sizeof(*pes));
+    pes = sqlite3_realloc(psi->pSubscriptions,
+                          (psi->numSubscriptions+1)*sizeof(*pes));
     if( pes==0 ) return SQLITE_NOMEM;
     psi->pSubscriptions = pes;
     pes += (psi->numSubscriptions++);
index fcac113c064cd6d9e1c3d1e99994f2e70e4aef51..d582a03b122a6a44f6f951dc1a67b294dba57669 100644 (file)
@@ -305,31 +305,28 @@ typedef struct Prompts {
   const char *zContinue;
 } Prompts;
 
-typedef struct ExtensionHelpers {
+AGGTYPE_BEGIN(ExtensionHelpers) {
   int helperCount; /* Helper count, not including sentinel */
-  union {
-    struct ExtHelpers {
-      int (*failIfSafeMode)(ShellExState *p, const char *zErrMsg, ...);
-      FILE * (*currentOutputFile)(ShellExState *p);
-      struct InSource * (*currentInputSource)(ShellExState *p);
-      char * (*strLineGet)(char *zBuf, int ncMax, struct InSource *pInSrc);
-      MetaCommand * (*findMetaCommand)(const char *cmdName, ShellExState *p,
-                                       /* out */ int *pnFound);
-      DotCmdRC (*runMetaCommand)(MetaCommand *pmc, char *azArg[], int nArg,
-                                 ShellExState *psx);
-      void (*setColumnWidths)(ShellExState *p, char *azWidths[], int nWidths);
-      int (*nowInteractive)(ShellExState *p);
-      const char * (*shellInvokedAs)(void);
-      const char * (*shellStartupDir)(void);
-      char * (*oneInputLine)(struct InSource *pInSrc, char *zPrior,
-                             int isContinuation, Prompts *pCue);
-      void (*freeInputLine)(char *zLine);
-      int (*enable_load_extension)(sqlite3 *db, int onoff);
-      void (*sentinel)(void);
-    } named ;
-    void (*nameless[13+1])(); /* Same as named but anonymous plus a sentinel. */
+  struct ExtHelpers {
+    int (*failIfSafeMode)(ShellExState *p, const char *zErrMsg, ...);
+    FILE * (*currentOutputFile)(ShellExState *p);
+    struct InSource * (*currentInputSource)(ShellExState *p);
+    char * (*strLineGet)(char *zBuf, int ncMax, struct InSource *pInSrc);
+    MetaCommand * (*findMetaCommand)(const char *cmdName, ShellExState *p,
+                                     /* out */ int *pnFound);
+    DotCmdRC (*runMetaCommand)(MetaCommand *pmc, char *azArg[], int nArg,
+                               ShellExState *psx);
+    void (*setColumnWidths)(ShellExState *p, char *azWidths[], int nWidths);
+    int (*nowInteractive)(ShellExState *p);
+    const char * (*shellInvokedAs)(void);
+    const char * (*shellStartupDir)(void);
+    char * (*oneInputLine)(struct InSource *pInSrc, char *zPrior,
+                           int isContinuation, Prompts *pCue);
+    void (*freeInputLine)(char *zLine);
+    int (*enable_load_extension)(sqlite3 *db, int onoff);
+    void *pSentinel; /* Always set to 0, above never are. */
   } helpers;
-} ExtensionHelpers;
+} AGGTYPE_END(ExtensionHelpers);
 
 /* This enum is stable excepting that it grows at the end. Members will not
  * change value across successive shell versions, except for NK_CountOf. An
@@ -366,52 +363,48 @@ typedef int (*ShellEventNotify)(void *pvUserData, NoticeKind nk,
                                 void *pvSubject, ShellExState *psx);
 
 /* Various shell extension helpers and feature registration functions */
-typedef struct ShellExtensionAPI {
+AGGTYPE_BEGIN(ShellExtensionAPI) {
   /* Utility functions for use by extensions */
   ExtensionHelpers * pExtHelpers;
 
   /* Functions for an extension to register its implementors with shell */
   const int numRegistrars; /* 6 for this version */
-  union {
-    struct ShExtAPI {
-      /* Register a meta-command */
-      int (*registerMetaCommand)(ShellExState *p,
-                                 ExtensionId eid, MetaCommand *pMC);
-      /* Register query result data display (or other disposition) mode */
-      int (*registerExporter)(ShellExState *p,
-                              ExtensionId eid, ExportHandler *pEH);
-      /* Register an import variation from (various sources) for .import */
-      int (*registerImporter)(ShellExState *p,
-                              ExtensionId eid, ImportHandler *pIH);
-      /* Provide scripting support to host shell. (See ScriptSupport above.) */
-      int (*registerScripting)(ShellExState *p,
-                               ExtensionId eid, ScriptSupport *pSS);
-      /* Subscribe to (or unsubscribe from) messages about various changes.
-       * See above NoticeKind enum and ShellEventNotify callback typedef. */
-      int (*subscribeEvents)(ShellExState *p, ExtensionId eid, void *pvUserData,
-                             NoticeKind nkMin, ShellEventNotify eventHandler);
-      /* Notify host shell that an ad-hoc dot command exists and provide for
-       * its help text to appear in .help output. Only an extension which has
-       * registered an "unknown" MetaCommand may use this.
-       * If zHelp==0, any such provision is removed. If zHelp!=0, original or
-       * replacement help text is associated with command zName.
-       * Help text before the first newline is primary, issued as summary help.
-       * Text beyond that is secondary, issued as the complete command help. */
-      int (*registerAdHocCommand)(ShellExState *p, ExtensionId eid,
-                                  const char *zName, const char *zHelp);
-      /* Preset to 0 at extension load, a sentinel for expansion */
-      void (*sentinel)(void);
-    } named;
-    void (*pFunctions[6+1])(); /* 0-terminated sequence of function pointers */
+  struct ShExtAPI {
+    /* Register a meta-command */
+    int (*registerMetaCommand)(ShellExState *p,
+                               ExtensionId eid, MetaCommand *pMC);
+    /* Register query result data display (or other disposition) mode */
+    int (*registerExporter)(ShellExState *p,
+                            ExtensionId eid, ExportHandler *pEH);
+    /* Register an import variation from (various sources) for .import */
+    int (*registerImporter)(ShellExState *p,
+                            ExtensionId eid, ImportHandler *pIH);
+    /* Provide scripting support to host shell. (See ScriptSupport above.) */
+    int (*registerScripting)(ShellExState *p,
+                             ExtensionId eid, ScriptSupport *pSS);
+    /* Subscribe to (or unsubscribe from) messages about various changes.
+     * See above NoticeKind enum and ShellEventNotify callback typedef. */
+    int (*subscribeEvents)(ShellExState *p, ExtensionId eid, void *pvUserData,
+                           NoticeKind nkMin, ShellEventNotify eventHandler);
+    /* Notify host shell that an ad-hoc dot command exists and provide for
+     * its help text to appear in .help output. Only an extension which has
+     * registered an "unknown" MetaCommand may use this.
+     * If zHelp==0, any such provision is removed. If zHelp!=0, original or
+     * replacement help text is associated with command zName.
+     * Help text before the first newline is primary, issued as summary help.
+     * Text beyond that is secondary, issued as the complete command help. */
+    int (*registerAdHocCommand)(ShellExState *p, ExtensionId eid,
+                                const char *zName, const char *zHelp);
+    void *pSentinel; /* Always set to 0, above never are. */
   } api;
-} ShellExtensionAPI;
+} AGGTYPE_END(ShellExtensionAPI);
 
 /* Struct passed to extension init function to establish linkage. The
  * lifetime of instances spans only the init call itself. Extensions
  * should make a copy, if needed, of pShellExtensionAPI for later use.
  * Its referent is static, persisting for the process duration.
  */
-typedef struct ShellExtensionLink {
+AGGTYPE_BEGIN(ShellExtensionLink) {
   int sizeOfThis;        /* sizeof(ShellExtensionLink) for expansion */
   ShellExtensionAPI *pShellExtensionAPI;
   ShellExState *pSXS;    /* For use in extension feature registrations */
@@ -438,7 +431,7 @@ typedef struct ShellExtensionLink {
    */
   int nLoadArgs;
   char **azLoadArgs;
-} ShellExtensionLink;
+} AGGTYPE_END(ShellExtensionLink);
 
 /* String used with SQLite "Pointer Passing Interfaces" as a type marker.
  * That API subset is used by the shell to pass its extension API to the
@@ -463,12 +456,27 @@ typedef struct ShellExtensionLink {
 
 /*
  * Define boilerplate macros analogous to SQLITE_EXTENSION_INIT#
+ * Note that the argument names are reused across the macro set.
+ * This reflects the fact that, for the macros to be useful, the
+ * same objects must be referenced from different places. Hence,
+ * the actual arguments must appear in all of the invocations.
  */
-/* Place at file scope prior to usage of the arguments by extension code. */
+
+/* Place at file scope prior to usage of the arguments by extension code.
+ * This defines 3 static objects, named per the arguments and set or used
+ * for an extension to link into the shell host.
+ */
+#ifndef __cplusplus
 #define SHELL_EXTENSION_INIT1( shell_api_ptr, ext_helpers_ptr, link_func ) \
   static struct ShExtAPI *shell_api_ptr = 0; \
   static struct ExtHelpers *ext_helpers_ptr = 0; \
   DEFINE_SHDB_TO_SHEXTLINK(link_func)
+#else
+#define SHELL_EXTENSION_INIT1( shell_api_ptr, ext_helpers_ptr, link_func ) \
+  static ShellExtensionAPI::ShExtAPI *shell_api_ptr = 0; \
+  static ExtensionHelpers::ExtHelpers *ext_helpers_ptr = 0; \
+  DEFINE_SHDB_TO_SHEXTLINK(link_func)
+#endif
 
 /* Place within sqlite3_x_init() among its local variable declarations. */
 #define SHELL_EXTENSION_INIT2( link_ptr, link_func, db_ptr ) \
@@ -477,8 +485,8 @@ typedef struct ShellExtensionLink {
 /* Place within sqlite3_x_init() code prior to usage of the *_ptr arguments. */
 #define SHELL_EXTENSION_INIT3( shell_api_ptr, ext_helpers_ptr, link_ptr ) \
  if( (link_ptr)!=0 ){ \
-  shell_api_ptr = &link_ptr->pShellExtensionAPI->api.named; \
-  ext_helpers_ptr = &link_ptr->pShellExtensionAPI->pExtHelpers->helpers.named; \
+  shell_api_ptr = &link_ptr->pShellExtensionAPI->api; \
+  ext_helpers_ptr = &link_ptr->pShellExtensionAPI->pExtHelpers->helpers; \
  }
 
 /* This test may be used within sqlite3_x_init() after SHELL_EXTENSION_INIT3 */
index 809cd427fc139d1209e4e415169941089f87b288..3eb28634252e2491d65ed060843d0b6a78506118 100644 (file)
@@ -18,8 +18,9 @@
 
 SQLITE_EXTENSION_INIT1;
 
-static struct ShExtAPI *pShExtApi = 0;
-static struct ExtHelpers *pExtHelpers = 0;
+SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher);
+#define SHX_API(entry) pShExtApi->entry
+#define SHX_HELPER(entry) pExtHelpers->entry
 
 typedef struct BatBeing BatBeing;
 static void sayHowMany( BatBeing *pbb, FILE *out, ShellExState *psx );
@@ -112,8 +113,6 @@ static int shellEventHandle(void *pv, NoticeKind nk,
   return 0;
 }
 
-DEFINE_SHDB_TO_SHEXTLINK(shext_link);
-
 /*
 ** Extension load function.
 */
@@ -126,26 +125,26 @@ int sqlite3_testshellext_init(
   const sqlite3_api_routines *pApi
 ){
   int nErr = 0;
-  ShellExtensionLink *pShExtLink;
+  int iLdErr;
   SQLITE_EXTENSION_INIT2(pApi);
-  pShExtLink = shext_link(db);
-  if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=1 ){
+  SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db);
+
+  SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink);
+  iLdErr = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 5, 5);
+  if( iLdErr!=EXLD_Ok ){
+    *pzErrMsg = sqlite3_mprintf("Load failed, cause %d\n", iLdErr);
+    return SQLITE_ERROR;
+  }else{
     ShellExState *psx = pShExtLink->pSXS;
     MetaCommand *pmc = (MetaCommand *)&batty;
     int rc;
 
-    pShExtApi = & pShExtLink->pShellExtensionAPI->api.named;
-    pExtHelpers = & pShExtLink->pShellExtensionAPI->pExtHelpers->helpers.named;
-    pShExtApi->subscribeEvents(psx, sqlite3_testshellext_init, &batty,
-                               NK_CountOf, shellEventHandle);
-    batty.pPrint = pExtHelpers->findMetaCommand("print", psx, &rc);
-    rc = pShExtApi->registerMetaCommand(psx, sqlite3_testshellext_init,  pmc);
+    SHX_API(subscribeEvents)(psx, sqlite3_testshellext_init, &batty,
+                             NK_CountOf, shellEventHandle);
+    batty.pPrint = SHX_HELPER(findMetaCommand)("print", psx, &rc);
+    rc = SHX_API(registerMetaCommand)(psx, sqlite3_testshellext_init, pmc);
     if( rc!=0 ) ++nErr;
     pShExtLink->eid = sqlite3_testshellext_init;
   }
-  else{
-    printf("No ShellExtensionLink pointer or registration API.\n");
-    ++nErr;
-  }
   return nErr ? SQLITE_ERROR : SQLITE_OK;
 }
diff --git a/src/test_shellext.cpp b/src/test_shellext.cpp
new file mode 100644 (file)
index 0000000..89d5218
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+** 2022 Feb 28
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Test extension for testing the shell's .load -shellext ... function.
+** To build from the SQLite project root:
+** g++ -shared -fPIC -Wall -I. -g src/test_shellext.cpp -o test_shellext.so
+*/
+#include <stdio.h>
+#include "shx_link.h"
+
+SQLITE_EXTENSION_INIT1;
+
+SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher);
+#define SHX_API(entry) pShExtApi->entry
+#define SHX_HELPER(entry) pExtHelpers->entry
+
+struct BatBeing : MetaCommand {
+
+  ~BatBeing() {}; // No held resources; copy/assign is fine and dying is easy.
+
+  void destruct() { this->~BatBeing(); }
+
+  const char *name() { return "bat_being"; };
+
+  const char *help(const char *zHK) {
+    if( !zHK )
+      return ".bat_being ?whatever?    Demonstrates vigilantism weekly\n";
+    if( !*zHK )
+      return "   Options summon side-kick and villains.\n";
+    return 0;
+  };
+
+  DotCmdRC argsCheck(char **pzErrMsg, int nArgs, char *azArgs[]) {
+    return DCR_Ok;
+  };
+  DotCmdRC execute(ShellExState *psx, char **pzErrMsg,
+                   int nArgs, char *azArgs[]);
+
+  BatBeing(MetaCommand *pp = 0) {
+    numCalls = 0;
+    pPrint = pp;
+  };
+
+  // Default copy/assign are fine; nothing held.
+
+  int numCalls;
+  MetaCommand * pPrint;
+};
+
+static void sayHowMany( BatBeing *pbb, FILE *out, ShellExState *psx ){
+  if( pbb->pPrint ){
+    static char cmd[] =  "print";
+    char *az[] = { cmd, 0 };
+    char *zErr = 0;
+    DotCmdRC rc;
+    az[1] = sqlite3_mprintf("This execute has been called %d times.\n",
+                            ++pbb->numCalls);
+    rc = pbb->pPrint->execute(psx, &zErr, 2, az);
+    sqlite3_free(az[1]);
+    if( rc!= DCR_Ok ){
+      fprintf(out, "print() failed: %d\n", rc);
+    }
+  }
+}
+
+DotCmdRC BatBeing::execute(ShellExState *psx, char **pzErrMsg,
+                           int nArgs, char *azArgs[]) {
+  FILE *out = SHX_HELPER(currentOutputFile)(psx);
+  switch( nArgs ){
+  default: fprintf(out, "The Penguin, Joker and Riddler have teamed up!\n");
+  case 2: fprintf(out, "The Dynamic Duo arrives, and ... ");
+  case 1: fprintf(out, "@#$ KaPow! $#@\n");
+  }
+  sayHowMany(this, out, psx);
+  return DCR_Ok;
+}
+
+/* Define/initialize BatBeing as a MetaCommand subclass using above v-table. 
+ * This compiles in a type-safe manner because the batty_methods v-table
+ * and methods it incorporates strictly match the MetaCommand interface.
+ */
+static BatBeing batty(0);
+
+static int shellEventHandle(void *pv, NoticeKind nk,
+                            void *pvSubject, ShellExState *psx){
+  FILE *out = SHX_HELPER(currentOutputFile)(psx);
+  if( nk==NK_ShutdownImminent ){
+    BatBeing *pbb = (BatBeing *)pv;
+    fprintf(out, "Bat cave meteor strike detected after %d calls.\n",
+            pbb->numCalls);
+  }else if( nk==NK_Unsubscribe ){
+    fprintf(out, "BatBeing incommunicado.\n");
+  }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing ){
+    const char *zWhat = (nk==NK_DbUserAppeared)? "appeared" : "vanishing";
+    fprintf(out, "dbUser(%p) %s\n", pvSubject, zWhat);
+    if( psx->dbUser != pvSubject ) fprintf(out, "not dbx(%p)\n", psx->dbUser);
+  }else if( nk==NK_DbAboutToClose ){
+    fprintf(out, "db(%p) closing\n", pvSubject);
+  }
+  return 0;
+}
+
+/*
+** Extension load function.
+*/
+extern "C"
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_testshellext_init(
+  sqlite3 *db,
+  char **pzErrMsg,
+  const sqlite3_api_routines *pApi
+){
+  int iLdErr;
+  int nErr = 0;
+  SQLITE_EXTENSION_INIT2(pApi);
+  SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db);
+
+  SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink);
+  iLdErr = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 5, 5);
+  if( iLdErr!=EXLD_Ok ){
+    *pzErrMsg = sqlite3_mprintf("Load failed, cause %d\n", iLdErr);
+    return SQLITE_ERROR;
+  }else{
+    ShellExState *psx = pShExtLink->pSXS;
+    int rc;
+
+    SHX_API(subscribeEvents)(psx, sqlite3_testshellext_init, &batty,
+                             NK_CountOf, shellEventHandle);
+    batty.pPrint = SHX_HELPER(findMetaCommand)("print", psx, &rc);
+    rc = SHX_API(registerMetaCommand)(psx, sqlite3_testshellext_init, &batty);
+    if( rc!=0 ) ++nErr;
+    pShExtLink->eid = sqlite3_testshellext_init;
+  }
+  return nErr ? SQLITE_ERROR : SQLITE_OK;
+}