This is G o o g l e's cache of http://www.roundelay.net/dvdsynth/programmers-guide.html.
G o o g l e's cache is the snapshot that we took of the page as we crawled the web.
The page may have changed since that time. Click here for the current page without highlighting.
To link to or bookmark this page, use the following url: http://www.google.com/search?q=cache:vTp9KyzsSdcC:www.roundelay.net/dvdsynth/programmers-guide.html++site:www.roundelay.net+dvdsynth&hl=en&lr=lang_en&ie=UTF-8


Google is not affiliated with the authors of this page nor responsible for its content.
These search terms have been highlighted: dvdsynth 

Programmer's guide to DVDSynth

Return to the main page

Programmer's guide to DVDSynth

This documentation is still preliminary, like DVDSynth itself. The plugin interface will be changing to support things like persistence and versioning. The general design and many specific functions will be the same, though.

General principles

Most of the interaction between DVDSynth and plugins occurs through objects with COM-like interfaces. The objects are structs whose first member, called vtable, is a pointer to a table of function pointers, each taking a pointer to the object as its first argument. For example, to call the AddSeparator() method through an object pointer menu of type DvsMenu*, you would do something like this in C or C++:

    menu->vtable->AddSeparator(menu);

There's no counterpart to QueryInterface, AddRef, or Release; in this respect DVDSynth's interfaces are more like C++ or Java interfaces. In general each object can implement only one Dvs* interface.

Some interfaces are implemented by DVDSynth, while others are implemented by your plugin. In the latter case, it's often legal to leave some functions unimplemented (by setting their function pointers to zero). This will lead to some function-specific default behavior.

Some interfaces are "global" and don't have an associated object. In this case the pointer points directly to the function table, and the functions don't take an object as their first argument.

All functions are declared __cdecl. Originally they were __stdcall like COM, until I discovered that (a) MinGW's implementation of __stdcall is very buggy, and (b) __stdcall doesn't exist at all under Linux.

Execution model

Plugins generally have two parts which must be implemented as separate DLLs. The "user" DLL runs in user mode and handles user interaction and other such stuff. The "kernel" DLL does nothing but field SCSI requests; you should think of it as running in kernel mode, although it doesn't always.

The separation of labor between the two DLLs is guided by these constraints:

The last constraint means that ideally the kernel DLL should have as little code and data as possible; the next-to-last constraint means that all the code related to handling SCSI requests must be in the kernel DLL.

In order to work on both Win9x/ME and Win2k/XP platforms, your kernel DLL must not dynamically link to anything, not even kernel32.dll. This means you almost certainly can't link in a standard library, even statically. If you're using the MS linker you will probably need to use the /entry: option to set the DLL entry point to your own function instead of _DLLMainCRTStartup. Your DLL entry point should return without doing anything, because it is not called at all on Win9x/ME systems.

Under Win2k/XP, the "kernel" DLL actually runs in user mode. This makes debugging much more convenient and prevents buggy plugins from leaking resources or bluescreening the system. It also means that if you are writing a Win2k/XP-only plugin, you do not need a kernel DLL at all; you can implement the kernel code in your user DLL. Obviously, though, it's better to make cross-platform plugins unless you have a good reason not to.

Quick introduction to SCSI and ATAPI

In order to write a device plugin or a filter plugin you will need to know a little bit about how SCSI works.

From a DVDSynth plugin's perspective, SCSI works like this: somebody (the "system") sends a SCSI command descriptor block (CDB for short) to a SCSI device, possibly accompanied by some data. The device either returns an error code, or else returns a success code possibly accompanied by some data. The first byte of the CDB identifies the operation to be performed (the "operation code"), and the remaining bytes of the CDB are arguments specific to that operation. The total length of the CDB depends on the operation code, but is always 6, 10, 12, or 16 bytes. The operation code also determines whether data will be sent to the device, returned from the device, or neither (it is not possible to both send and receive data in a single operation).

There are a few operations which are supported by all devices, the most important being INQUIRY, 0x12, which returns information about what kind of device it is. Most operations, however, are supported only by some types of device and may have a different meaning when sent to a different type.

Two important sources of information on the existing operation codes are:

DVDSynth plugins handle SCSI requests through a function called ScsiCommand which has the following prototype:

    scsi_result_t ScsiCommand(
        DvsDeviceKernel*     self,
        const unsigned char* cdb,
        int                  cdblen,
        unsigned char*       buffer,
        unsigned long*       pbuflen,
        int                  inout,
        SenseData*           sense
    );

The first argument, self, is just a this pointer. The second argument is the CDB, and the third is its length. The fourth is the buffer for data (if any) and the fifth is a pointer to the buffer length. If data is being returned from the device to the system, you should adjust this value downward if less data was transferred than requested.

The inout argument specifies the direction of data transfer, 1 for "in" (from the device to the system) and 2 for "out" (from the system to the device). Generally you should ignore this, since the CDB operation code determines the transfer direction. The sense argument returns "sense data" from the device. In 99.9% of cases you should ignore this too.

The return value of the function should generally either be SCSIRESULT_SUCCESS or SCSIRESULT_ERROR(0xKSSQQ), where K is the "sense key" represented as a hexadecimal nibble, SS is the "additional sense code," and QQ is the "additional sense code qualifier." You can find a list of standard values for these things in Appendix A of the Mt. Fuji spec, or you can look in LogScsi.cpp. Be warned that SCSIRESULT_SUCCESS is not a simple value like 0 or 1, and SCSIRESULT_ERROR(0x12345) is not equal to 0x12345.

ATAPI is the same as SCSI. (It isn't really, but you can pretend it is when writing your plugins.)

Device plugin overview

Device plugins are intended for implementing new types of virtual devices (although they can do other things as well). The "Mirror Drive" plugin is a device plugin, as is Filters.dll.

All the definitions and prototypes you need to write a device plugin are in dvdsynth-device.h in the include directory of the distribution.

The general flow of control for a device plugin is:

  1. DVDSynth automatically loads your DLL (provided it's in the DVDSynth directory), and calls its DvdsynthDevicePluginEntry entry point. It passes a table of pointers to utility functions implemented in dvdsynth.exe. Your DvdsynthDevicePluginEntry function saves that table of pointers and returns a table of pointers to its own set of plugin functions.
  2. When the user opens the DVDSynth tray menu, DVDSynth calls your plugin's Add*MenuItems functions, if they're defined. Your AddNewMenuItems function adds an entry for your device type to the "New..." submenu.
  3. When the user selects your menu item, your plugin gets control. Your plugin calls ReserveDockingBay to get an empty slot for the new device. The slot is returned as a docking-bay object. Then it loads its kernel-mode component using the Driver_Load function, creates user-mode and kernel-mode device objects, and sets those as the handlers for the docking bay using the SetHandlers method. At this point your device is live.
  4. Your kernel device object handles SCSI commands sent to the device.
  5. The AddDeviceMenuItems function in your user device object, if present, can add options to the device-specific submenu. The "Unplug this device" option is always there.
  6. When the user asks to unplug the device, your user object's QueryUnplug method is called, if present, and can accept or reject the request.
  7. If all plugins and the system agree, the device will be removed, and then your user object's Delete method will be called. The Delete method should take care of cleaning up allocated memory, and should also unload the kernel driver with Driver_Unload.

Filter plugin overview

Filter plugins are intended for filtering the SCSI commands sent to a virtual device which is implemented in a device plugin.

All the definitions and prototypes you need to write a filter plugin are in dvdsynth-filter.h in the include directory of the distribution.

The general flow of control for a filter plugin is:

  1. DVDSynth automatically loads your DLL (provided it's in the DVDSynth directory), and calls its DvdsynthFilterPluginEntry entry point. It passes a table of pointers to utility functions implemented in dvdsynth.exe. Your DvdsynthFilterPluginEntry function saves that table of pointers and returns a structure containing information about the filter or filters supported by your DLL.
  2. When the user adds a copy of your filter to a device in the filter selection dialog, your HookDevice function is called, and is passed a kernel device object corresponding to the raw device with all previous filters already applied. Your filter loads its kernel-mode component using the Driver_Load function, creates user-mode and kernel-mode device objects, and returns them from the function.
  3. From this point on your device objects are treated just like a device plugin's device objects; the only difference is that the kernel device object can pass SCSI commands down the filter chain at its option, while an ordinary device object can only handle them itself.

Structure and function reference

DvdsynthDevicePluginEntry

DvsDeviceGlobal* DvdsynthDevicePluginEntry(DvsDockingBayGlobal*); 

This function must be exported from any user DLL implementing one or more virtual SCSI devices. DVDSynth calls it at startup time with the address of a list of callback functions, and it returns a list of plugin functions. It may also return 0, in which case DVDSynth unloads the plugin and acts as though it did not exist. You might want to do this if your initialization fails or if you determine (by calling Is95, for example) that your plugin won't run correctly on this platform.

DvdsynthFilterPluginEntry

DvsFilterGlobal* DvdsynthFilterPluginEntry(DvsDockingBayGlobal*);

This function must be exported from any user DLL implementing one or more SCSI filters. DVDSynth calls it at startup time with the address of a list of callback functions, and it returns a structure containing plugin information. It may also return 0, in which case DVDSynth unloads the plugin and acts as though it did not exist. You might want to do this if your initialization fails or if you determine (by calling Is95, for example) that your plugin won't run correctly on this platform.

DvdsynthDriverInit

void DvdsynthDriverInit(DvsDockingBayKernelGlobal* callbacks);

This function may be exported from a kernel DLL. When the DLL is loaded with Driver_Call, this entry point will be called (if it exists) with a pointer to the kernel-mode callbacks. There is no other way to get the address of these callbacks.

DvsDeviceGlobal

  struct DvsDeviceGlobal {
     void           (*AddNewMenuItems)  (DvsMenu* menu);
     void           (*AddAboutMenuItems)(DvsMenu* menu);
     void           (*AddMainMenuItems) (DvsMenu* menu);
     DvsDockingBay* (*HookDockingBay)   (DvsDockingBay* original_docking_bay);
  };

This is a list of plugin functions returned from DvdsynthDevicePluginEntry. All four are optional. They are:

DvsFilterGlobal

  struct DvsFilterGlobal {
     DvsFilterGlobal* next;
     const char*      visible_name;
     unsigned         flags;
     DvsDeviceUser* (*HookDevice)       (DvsDeviceKernel** pkernel, DvsDockingBay* bay);
     void           (*AddAboutMenuItems)(DvsMenu* menu);
  };

This is a struct containing plugin information returned from DvdsynthFilterPluginEntry. The elements are:

DvsDeviceUser

  struct DvsDeviceUser {
     DvsDeviceUser_vtable *vtable;
  };

  struct DvsDeviceUser_vtable {
     void (*AddDeviceMenuItems)(DvsDeviceUser* self, DvsMenu* menu);
     int  (*QueryUnplug)       (DvsDeviceUser* self);
     void (*Delete)            (DvsDeviceUser* self);
  };

This interface is implemented by device and filter plugins. It handles the user-interface side of a virtual device. The methods are:

DvsDeviceKernel

  struct DvsDeviceKernel {
     dvs_scsi_func* ScsiCommand;
  };

This interface is the kernel counterpart to DvsDeviceUser. Unlike other interfaces it does not use a vtable, since there is only one method. The ScsiCommand method is described in the "introduction to SCSI" section of this document.

DvsMenu

  struct DvsMenu {
     DvsMenu_vtable* vtable;
  };

  struct DvsMenu_vtable {
     void (*AddSeparator)   (DvsMenu* self);
     void (*AddItem)        (DvsMenu* self, const char* text, int checked, void (*callback)(void* ,int), void* p, int i);
     void (*AddDisabledItem)(DvsMenu* self, const char* text, int checked);
     void (*BeginSubmenu)   (DvsMenu* self, const char* text, int disabled);
     void (*EndSubmenu)     (DvsMenu* self);
  };

The DvsMenu interface is implemented by DVDSynth. Whenever the user clicks on the tray menu, DVDSynth creates an empty menu, adds some standard items (like "Exit"), and calls the various plugins to add everything else. The DvsMenu methods are:

DvsDockingBayGlobal

struct DvsDockingBayGlobal {
   DvsDockingBay*
         (*ReserveDockingBay)();

   dvs_driver_handle
         (*Driver_Load)   (const char* filename);
   void* (*Driver_Call)   (dvs_driver_handle handle, const char* exported_name, const char* types, ...);
   void  (*Driver_Unload) (dvs_driver_handle handle);

   int   (*OpenFileRO)        (dvs_file_handle* phandle, const char* pathname);
   int   (*CreateOrOpenFileRW)(dvs_file_handle* phandle, const char* pathname, int creation_disposition);
   void  (*CloseFile)         (dvs_file_handle handle);

   unsigned (*OpenKernelEventHandle) (void* user_handle);
   void     (*CloseKernelEventHandle)(unsigned kernel_handle);

   int   (*Is95) ();

   dvs_scsi_func* KernelScsiCommand;

   int   (*GetDvdVideoInfo) (DvsDeviceKernel* device, DvdVideoInfo* info);

   unsigned (*GetDvdsynthDevicesID) ();

   int   (*Sprint) (char* buf, int space, const char* fmt, const char* types, ...);

   const char*
         (*GetDvdsynthDirectory)();
   void* (*GetTaskbarHWND)      ();
};

This structure contains callback functions available to user DLLs (only). It is passed by DVDSynth to DvdsynthDevicePluginEntry and DvdsynthFilterPluginEntry. The callbacks are:

DvsDockingBay

  struct DvsDockingBay {
     DvsDockingBay_vtable* vtable;
  };

  struct DvsDockingBay_vtable {
     void     (*SetHandlers)     (DvsDockingBay* self, DvsDeviceUser* user_handler, DvsDeviceKernel* kernel_handler);
     void     (*RequestUnplug)   (DvsDockingBay* self);
     void*    (*SharedPool_Alloc)(DvsDockingBay* self, unsigned len);
     int      (*GetScsiID)       (DvsDockingBay* self);
     unsigned (*GetDriveLetters) (DvsDockingBay* self);
  };

Objects implementing this interface are returned by the ReserveDockingBay function. The methods are:

DvsDockingBayKernelGlobal

  struct DvsDockingBayKernelGlobal {
     void (*MemSet)     (void* dst, int val, unsigned long count);
     void (*MemCpy)     (void* dst, const void* src, unsigned long count);
     void (*DebugPrintf)(const char* fmt, ...);
     void (*AsyncUserModeCall)(void (__cdecl*)(void*, int), void*, int);
     int  (*ReadFile)   (dvs_file_handle file_handle, unsigned offset, unsigned offset_high, unsigned count, void* buf);
     int  (*WriteFile)  (dvs_file_handle file_handle, unsigned offset, unsigned offset_high, unsigned count, void* buf);
     void (*SetEvent)   (unsigned event_handle);
  };

This structure contains callback functions available to kernel DLLs (only). It is passed by DVDSynth to DvdsynthDriverInit. The callbacks are:


Comments:

Your name (optional):

Your email address (optional):

Return to the main page