Extending Dali VM


General Source Code Origanization

Dali VM is organized into different packages according to functionality of the types and primitives, for example all types and primitives related to WAVE belongs to the WAVE package. A special package called basic contains all basic type and primitives.

Suppose we have a package called foo, the core files for that package are :

include/dvmfoo.h
Declarations of types, constants, function prototype that are needed by external programs that wants to use this package.
include/tclDvmFoo.h
Declarations of tcl specific stuff, such as tcl commands hooks to C functions.
packages/foo/fooInt.h
Declaration of internal types, constants, or function prototypes that are not exposed to external programs.
packages/foo/foo.c
This is not required, but it is recommended that implementation of basic primitives (new, free, copy etc) are put in this file. Add other .c files to the package as needed.
packages/foo/tcl/foocmd.c
This files contains tcl hooks to C functions in .c files directly in packages/foo directory. Add other .c file as needed, but the filename is recommended should end with cmd.
packages/tcl/fooinit.c
Contained initialization function (such as DLL entry points) for this package.

Adding New Package

Suppose we want to add a new package foo that depends on package bar.
  1. Create new directory foo and foo/tcl under directory packages.
  2. Create file include/dvmfoo.h. dvmfoo.h should includes dvmbar.h.
  3. Create file include/tclDvmFoo.h. depends on package bar, then tclDvmFoo.h should includes tclDvmBar.h and dvmfoo.h
  4. Create file packages/foo/fooInt.h. It should includes dvmfoo.h
  5. Create file packages/foo/tcl/foocmd.c. It should includes tclDvmFoo.h.
  6. Create file packages/foo/tcl/fooinit.c. It should includes tclDvmFoo.h and contains these stuff :
    
    #include "tclDvmFoo.h"
    
    EXTERN EXPORT(int,Tcldvmfoo_Init) _ANSI_ARGS_((Tcl_Interp *interp));
    EXTERN void InitHashTable _ANSI_ARGS_((Tcl_Interp *interp));
    EXTERN void CreateCommands _ANSI_ARGS_((Tcl_Interp *interp, Commands cmd[], int cmdsize));
    
    static Commands cmd[] =
    {
    }
    
    #ifdef __WIN32__
    BOOL APIENTRY
    DllEntryPoint(hInst, reason, reserved)
        HINSTANCE hInst;		/* Library instance handle. */
        DWORD reason;		/* Reason this function is being called. */
        LPVOID reserved;		/* Not used. */
    {
        return TRUE;
    }
    #endif
    
    
    EXPORT(int,Tcldvmfoo_Init)(interp)
        Tcl_Interp *interp;
    {
        if (Tcl_PkgRequire(interp, "DvmBar", "1.0", 1) == NULL) {
            sprintf (interp->result, "Error loading tclDvmBar package");
            return TCL_ERROR;
        }
        CreateCommands (interp, cmd, sizeof(cmd));
        InitHashTable (interp);
        return Tcl_PkgProvide(interp, "DvmFoo", "1.0");
    }
    
    We will leave the cmd[] array empty for the moment.

Adding New Type

Now that we have all the basic files, we are ready to add a new type, let say Foo, to our new package.
  1. Add the type definition of Foo to dvmfoo.h. Let say
        typedef struct Foo {
            int x;
    	int y;
        } Foo;
        
  2. Typically we have query primitives and initialization that access and modifies the internals of a structure, we add those as macros in dvmfoo.h
        #define FooGetX(foo) foo->x
        #define FooGetY(foo) foo->y
        #define FooSetX(foo, v) foo->x = v
        #define FooSetY(foo, v) foo->y = v
        
  3. In tcl, we refer to Foo by using a handle (which is just a string). We need to give a unique prefix to the handle, which serve as some sort of type checking mechanism of handle. In Dali VM all prefix are 9 characters long and the first 3 characters are "dvm". Words should be separated by capitalization and extra space are padded with underscore. For example,
        #define FOO_PREFIX "dvmFoo___"
    or
        #define FOO_PREFIX "dvmBarFoo"
  4. Add these macros into tclDvmFoo.h so that we can convert between a tcl handle and pointer to the structure. Details on how to used them are illustrated in the "Adding New Primitives" section.
        #define GetFoo(s)     (!strncmp(s, FOO_PREFIX, 9)?GetBuf(s):NULL)
        #define RemoveFoo(s) RemoveBuf(s)
        #define PutFoo(interp, buf) PutBuf(interp, FOO_PREFIX, buf)
    

Adding New Primitives

Now that we have our type, we should add some primitives for the type. We will show three examples : New, Add and Free.
  1. Create a new file called foo.c under packages/foo. Add C implementation of the new functions :
        Foo *
        FooNew()
        {
            Foo *new = (Foo *)malloc(sizeof(Foo));
    	return new;
        }
    
        int 
        FooAdd(foo)
            Foo *foo;
        {
            return foo->x + foo->y;
        }
    
        void
        FooFree(foo)
            Foo *foo;
        {
            free((char *)foo);
        }
        
    These should be pretty straight forward.
  2. Now we need to add tcl hooks that call these functions. A typical tcl function has 6 stages : Here are the three examples :
    int
    FooNewCmd (clientData, interp, argc, argv)
        ClientData clientData;
        Tcl_Interp *interp;
        int argc;
        char *argv[];
    {
        Foo *foo;
    
        ReturnErrorIf1 (argc != 1,
    	"wrong # args: should be %s", argv[0]);
        foo = FooNew();
        PutFoo(interp, foo);
    
        return TCL_OK;
    }
    
    
    int
    FooAddCmd (clientData, interp, argc, argv)
        ClientData clientData;
        Tcl_Interp *interp;
        int argc;
        char *argv[];
    {
        Foo *foo;
        int result;
    
        ReturnErrorIf1 (argc != 2,
    	"wrong # args: should be %s foo", argv[0]);
        foo = GetFoo(argv[1]);
        ReturnErrorIf2 (foo == NULL,
            "%s: no such foo %s", argv[0], argv[1]);
    
        result = FooAdd(foo);
        
        sprintf(interp->result, "%d", result);
    
        return TCL_OK;
    }
    
    
    int
    FooFreeCmd (clientData, interp, argc, argv)
        ClientData clientData;
        Tcl_Interp *interp;
        int argc;
        char *argv[];
    {
        Foo *foo;
    
        ReturnErrorIf1 (argc != 2,
    	"wrong # args: should be %s foo", argv[0]);
        foo = RemoveFoo(argv[1]);
        ReturnErrorIf2 (foo == NULL,
            "%s: no such foo %s", argv[0], argv[1]);
    
        FooFree(foo);
    
        return TCL_OK;
    }
    
    ReturnErrorIfn is a convinient that checks the condition, and if true print the error message into interp->result and return TCL_ERROR. It can be used only in tcl hooks, and assume that the Tcl_Interp variable is called interp.

    After we create the new Foo, we add it to an internal hash table by calling PutFoo. We can later retrieve it (in FooAddCmd) by calling GetFoo. When we want to free the structure, we call RemoveFoo, which is the same as GetFoo, but also remove it from the hash table.

  3. We need to add these functions prototypes to the header files. The implementation functions prototypes should go into dvmfoo.h whild the tcl hooks should go into tclDvmFoo.h.

    In dvmfoo.h

        Foo *FooNew();
        void FooFree(Foo *foo);
        int  FooAdd(Foo *foo);
        
    In tclDvmFoo.h
        int FooNewCmd _ANSI_ARGS_((ClientData cd, Tcl_Interp * interp, int argc, char *argv[]));
        int FooAddCmd _ANSI_ARGS_((ClientData cd, Tcl_Interp * interp, int argc, char *argv[]));
        int FooFreeCmd _ANSI_ARGS_((ClientData cd, Tcl_Interp * interp, int argc, char *argv[]));
        
  4. Lastly we want to specify which tcl command should invoke which tcl hooks. We do that in fooinit.c, by adding these lines into the cmd array.
        static Commands cmd[] =
        {
    	{ "foo_new", FooNewCmd, NULL, NULL, },
    	{ "foo_free", FooFreeCmd, NULL, NULL, },
    	{ "foo_add", FooAddCmd, NULL, NULL, },
        }
        

Changing Makefile

DIY

Last Updated :