Creating resources in an addon

Updated: April 19, 2023

To use addon resources in an application, you must first create them in an addon. Here, we provide an example of doing so. One approach is to create a const structure containing your resource definitions and then use that as a template to create a resource structure for each context (if your addon uses contexts). You would use contexts if you needed multiple instances of the same addon.

Let's create a context structure first:

typedef struct my_context
{
    int32_t volume;             // volume, 0-100
    int64_t position;           // position, in microseconds
    AOResource_t *resources;
};

We need type information for our volume and position resources, such as minimum, maximum, and increment values. In an AOResource_t structure, there is predefined type information in the type element flags. When the type flag is AOR_TYPE_LONG, then value is an int32_t and the resource info is a pointer to int32_t min, max, and step values:

// volume range from 0 to 100, in increments of one
static const int32_t volumerange[] = { 0, 100, 1 };

When type is AOR_TYPE_LONGLONG, the value resource is an int64_t and info is a pointer to int64_t min, max, and step values:

// position range from 0 to 86400000000 (86400 seconds, or 24 hours.)
static const int64_t posrange[] = { 0, 86400000000, 1 };

For details of how the AOResource_t structure is defined, see the AOResource_t reference. Now we can define our const AOResource_t resources structure:

static const AOResource_t resources[] =
{
    { "Volume", "Current Volume", (void*)offsetof(my_context,volume),
        &volumerange, AOR_TYPE_LONG | AOR_TYPE_READABLE | AOR_TYPE_WRITABLE },
    { "Position", "Current Position", (void*)offsetof(my_context,position),
        &posrange, AOR_TYPE_LONGLONG | AOR_TYPE_READABLE | AOR_TYPE_WRITABLE },
    { 0 }
};

As you can see, the pointer to the current value of the resource (which is the third field in each array entry) is not valid; it's an offset into the context. When we create a new context, we have to adjust this pointer accordingly. Assuming we use the AODeConstructor interface, our creation function might look like:

static void *Create(const AOICtrl_t *interfaces)
{
    my_context *ctx = (my_context*)calloc( 1, sizeof(my_context) );
    int32_t n;
    AOResource_t *res;
    
    // Allocate new resource structure and copy const version into it:
    ctx->res = (AOResource_t*)malloc( sizeof(resources) );
    memcpy( ctx->res, &resources, sizeof(resources) );
    
    for (res=ctx->res;res->name;res++)
    {
        char *p = (char *)ctx;
    
        // Add the address of the context to the offset, making the value
        // pointer now point to the correct location in our context:
        res->value = (void*)(&p[(int32_t)res->value]);
    }
    
    // Initialize our context elements, if necessary
    ctx->volume = 50;
    
    return ctx;
}

static void Destroy(void *p)
{
    my_context *ctx = (my_context*)p;
    free(ctx->res);
    free(ctx);
}

static AODeConstructor media_filter =
{
    Create,
    Destroy
};

If we want the outside world to be able to access our resources, we need to implement the AOResourceAccess interface. This is quite easy as well:

static const AOResource_t *GetResources( void *handle )
{
    my_context *ctx = (my_context*)handle;
    
    return handle->resources;
}

static int32_t SetResource( void *handle, 
                            const char *res,
                            const void *data )
{
    my_context *ctx = (my_context*)handle;
    
    // first resource is volume
    if ( strcmp( res, ctx->resources[0].name ) == 0 )
    {
        ctx->volume = *((int32_t*)data);
        
        // do any other volume control stuff here
        
        // return success
        return 0;
    }
    // second resource is position
    else if ( strcmp( res, ctx->resources[1].name ) == 0 )
    {
        ctx->position = *((int64_t*)data);
        
        // do any other positioning stuff here
        
        // return success
        return 0;
    }
    
    // no matching resource, return error
    return -1;
}

static AOResourceAccess resource_access =
{
    GetResources,
    SetResource,
};

At the end of our addon, we put the interfaces together in an AOInterface_t list:

#ifdef VARIANT_dll
AOInterface_t interfaces[] =
#else
AOInterface_t my_addon_interfaces[] =
#endif
{
    { "Name", 1, "my_addon" },
    { "AODeConstructor", AODECONSTRUCTOR_VERSION, &media_filter },
    { "AOResourceAccess", AORESOURCEACCESS_VERSION, &resource_access },
    { 0, 0, 0 },
};

We use the #ifdef directive to build a shared (DLL) and static library version of our addon with the same source code. This way, we can link directly with the static version if we want our application to be completely self-contained, or use the DLL if not.