Creating Lasso Types

Creating a new Lasso type in LCAPI is similar to creating a custom method. When Lasso Server starts up, it scans the “LassoModules” directory for module files (Windows DLLs, OS X DYLIBs, or Linux SOs). As it encounters each module, it executes the registerLassoModule function for that module. The developer registers the LCAPI types or methods implemented by the module inside this function. Registering type initializers differs from registering normal methods in that the third parameter in lasso_registerTagModule is the value “REG_FLAGS_TYPE_DEFAULT”:

void registerLassoModule()
{
   lasso_registerTagModule( "test", "type", myTypeInitFunc,
      REG_FLAGS_TYPE_DEFAULT, "simple test LCAPI type" );
}

The prototype of an LCAPI type initializer is the same as a regular LCAPI custom method function. Lasso will call the type initializer each time a new instance of the type is created:

osError myTypeInitFunc( lasso_request_t token, tag_action_t action );

When the type initializer function is called, a new instance of the type is created using lasso_typeAllocCustom. This new instance will be created without data members or member methods:

osError myTypeInitFunc( lasso_request_t token, tag_action_t action );
{
   lasso_type_t theNewInstance = NULL;
   lasso_typeAllocCustom( token, &theNewInstance, "test_type" );

Once the type is created, new data members and member methods can be added to it using lasso_typeAddMember. Data members can be of any type and should be allocated using any of the LCAPI type allocation calls. Member methods are allocated using lasso_typeAllocTag. LCAPI member method functions are implemented just like any other LCAPI method. In the example below, myTagMemberFunction is a function with the standard LCAPI prototype:

const char * kStringData = "This is a string member.";
lasso_type_t stringMember = NULL;
lasso_typeAllocString( token, &stringMember, kStringData, strlen(kStringData) );
lasso_typeAddDataMember( token, theNewInstance, "member1", stringMember );

lasso_type_t tagMember = NULL;
lasso_typeAllocTag( token, &tagMember, myTagMemberFunction );
lasso_typeAddMember( token, theNewInstance, "member2", tagMember );

The final step in creating a new LCAPI type instance is to return the new type to Lasso as the initializer’s return value. After the type is returned, Lasso will complete the creation of the type by instantiating the new type’s parent types:

   lasso_returnTagValue( token, theNewInstance );
   return osErrNoErr;
}

Basic Custom Type

This tutorial walks through the main points of creating a custom type using LCAPI. The resulting type is an example_file type, and the ability to open, close, read, and write to the file are implemented via the following member methods: example_file->open, example_file->close, example_file->read, example_file->write.

The example project is the “CAPIFile” project in the LCAPI examples downloadable from this site. Due to the length of the code in that file, the entire code is not reproduced here. Instead, this section provides a conceptual overview of the example_file type and describes the basic LCAPI functions used to implement it.

  1. The first step in creating a custom type is to register the type’s initializer. Type initializers are registered in the same way that regular method functions are registered. The only difference being that “REG_FLAGS_TYPE_DEFAULT” should be passed for the fourth (flags) parameter.

    This concept is illustrated in lines 247–282 of the CAPIFile.cpp file:

    void registerLassoModule()
    {
       ...
       lasso_registerTagModule("", kFileTypeName, file_init,
          REG_FLAGS_TYPE_DEFAULT, "Initializer for the file type.");
    }
    
  2. The registered type initializer will be called when the module is loaded. In the above case, the LCAPI function file_init was registered as being the initializer. The prototype for file_init should look like any other LCAPI function, as shown on line 285 of the CAPIFile.cpp file:

    osError file_init(lasso_request_t token, tag_action_t action)
    
  3. The file_init function will now be called whenever the module is loaded. Within the type initializer, the type’s member methods are added. Each member method is implemented by its own LCAPI function. However, before members can be added, the new blank type must be created using lasso_typeAllocCustom.

    You can only use lasso_typeAllocCustom within a properly registered type initializer. The value it produces should always be the return value of the method as set by the lasso_returnTagValue function. See lines 289–290 of the CAPIFile.cpp file:

    lasso_type_t file;
    lasso_typeAllocCustom(token, &file, kFileTypeName);
    
  4. Once the blank type has been created, members can be added to it. LCAPI types often need to store pointers to allocated structures or memory. LCAPI provides a means to accomplish this by using the lasso_setPtrMember and lasso_getPtrMember functions. These functions allow the developer to store a pointer with a specific name. The pointer is stored as a regular integer data member. The names of all pointer members should begin with an underscore. Naming a pointer as such will indicate to Lasso that it should not be copied when a copy is made of the type instance. In the initializer function, add the integer data member as seen on lines 293–295:

    lasso_type_t i;
    lasso_typeAllocInteger(token, &i, 0);
    lasso_typeAddDataMember(token, file, kPrivateMember, i);
    

    This LCAPI example_file type stores its private data in a structure named file_desc_t. The actual call to lasso_setPtrMember is in the method’s onCreate method as shown on lines 344–345 of the CAPIFile.cpp file:

    file_desc_t * desc = new file_desc_t;
    lasso_setPtrMember(token, self, kPrivateMember, desc, &cleanUp);
    
  5. Member methods for open, close, read, and write could be written like this:

    lasso_type_t mem;
    lasso_typeAllocTag(token, &mem, file_open);
    lasso_typeAddMember(token, file, "open", mem);
    
    lasso_typeAllocTag(token, &mem, file_close);
    lasso_typeAddMember(token, file, "close", mem);
    
    lasso_typeAllocTag(token, &mem, file_read);
    lasso_typeAddMember(token, file, "read", mem);
    
    lasso_typeAllocTag(token, &mem, file_write);
    lasso_typeAddMember(token, file, "write", mem);
    

    But to avoid the repetitive nature of this, the CAPIFile.cpp file defines a macro named ADD_TAG to do the work as seen on lines 300–309:

    #define ADD_TAG(NAME, FUNC) {
       lasso_type_t mem;\
       lasso_typeAllocTag(token, &mem, FUNC);\
       lasso_typeAddMember(token, file, NAME, mem);\
    }
    
    // Add the type's member tags
    ADD_TAG(kMemOpen, file_open);
    ADD_TAG(kMemClose, file_close);
    ADD_TAG(kMemRead, file_read);
    ADD_TAG(kMemWrite, file_write);
    
  6. At this point, the return value should be set. Keep in mind that the new example_file type is completely blank except for the members that were added above. No inherited members are available at this point. Inherited members are only added after the LCAPI type initializer returns. Line 324 of the CAPIFile.cpp file sets the return value:

    lasso_returnTagValue(token, file);
    
  7. There were no errors in the type initialization process, so return a “no error” code to Lasso, completing the type’s initialization. See line 325 of the CAPIFile.cpp file:

    return osErrNoErr;
    

    Note

    For brevity, this example will not cover accepting parameters in the type’s onCreate method. The full “CAPIFile” project illustrates accepting parameters in the onCreate member method to open the file under various read and write permissions.

  8. The new file type has now been initialized and made available to the caller in the script. The first member method of the file type is example_file->open, which is implemented as the LCAPI function file_open beginning on line 385 of the CAPIFile.cpp file:

    osError file_open(lasso_request_t token, tag_action_t action)
    {
    
  9. The first step in implementing a member method is to acquire the “self” instance. The “self” is the instance upon which the member call was made. This is illustrated on lines 387–390 of the CAPIFile.cpp file:

    lasso_type_t self = NULL;
    lasso_getTagSelf(token, &self);
    if(!self)
       return osErrInvalidParameter;
    
  10. Once the “self” is successfully acquired and is not “null”, the rest of the member method can proceed. This member method accepts one parameter, which is the path to the file that will be opened. Since the path is a string value, it can be acquired using lasso_getTagParam. If the path parameter was not passed to the open member method, an error should be returned and indicated to the user. All of this can be seen on lines 400–418 of the CAPIFile.cpp file:

    // See what parameters we are being initialized with
    int count;
    lasso_getTagParamCount(token, &count);
    
    if( count < 2 )
    {
       lasso_setResultMessage(token, "file->open requires at least a file path and open mode.");
       return osErrInvalidParameter;
    }
    
    if( count > 0 ) // We are given *at the least* a path
    {
       // First param is going to be a string, so use the LCAPI call to get it
       auto_lasso_value_t pathParam;
       pathParam.name = "";
       lasso_getTagParam(token, 0, &pathParam);
    
       desc->fPath = pathParam.name;
    }
    
  11. Once the path is properly converted, the actual file can be opened using the file system calls supplied by the operating system. This concept is illustrated on line 225 of the CAPIFile.cpp file:

    FILE * f = fopen(xformPath, openMode);
    
  12. The FILE pointer can now be retrieved using the lasso_typeGetCustomPtr LCAPI function. No error has occurred while opening the file, so complete the function call and return “no error”. See line 449 of the CAPIFile.cpp file:

    return osErrNoErr;
    
  13. The remaining method functions are implemented in a similar manner. Study the CAPIFile example for a more in-depth and complete example of how to properly construct custom Lasso types in LCAPI.