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 functions which DVDSynth calls to handle GUI-related events must be in the user DLL.
- The function which DVDSynth calls to handle SCSI requests must be in the kernel DLL.
- The full Win32 API is available from the user DLL, but only a very limited set of functions is available from the kernel DLL.
- It is not possible to synchronously call user DLL code from the kernel DLL. (If it were, this whole user/kernel division would be unnecessary.) Asynchronous notifications are of course possible.
- Under Windows 9x/ME, all of the kernel DLL's code and data resides in unpaged memory.
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:
- The SCSI-2 specification covers many device types, including CD-ROM, but is now very outdated.
- The Mt. Fuji Commands for Multimedia Devices specification defines a large number of additional SCSI operation codes which are recognized by most modern CD/DVD drives. These codes cover reading audio CD subchannel data, authenticating DVD drives, and many other things. As of this writing the latest version is in Fuji5r14.pdf (or Fuji5r14.zip).
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:
- 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.
- 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.
- 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.
- Your kernel device object handles SCSI commands sent to the device.
- 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.
- 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.
- 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:
- 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.
- 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.
- 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
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.
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.
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.
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:
- AddNewMenuItems, AddAboutMenuItems, and AddMainMenuItems: These functions are called when the DVDSynth tray menu opens, with DvsMenu objects pointing to the respective submenus. You can add any menu items you want, but the one you're most likely to want to add is an item in the New menu which creates an instance of your device type. (There should be an AddOptionsMenuItems function too, now that I think about it.)
- HookDockingBay: This function is used by Filters.dll to interpose filters between a virtual device and DVDSynth. If you want to use this function you almost certainly should be writing a filter plugin instead.
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:
- next: If you implement more than one filter in a single DLL, DvdsynthFilterPluginEntry returns a linked list of filters. This is a pointer to the next node in the list. If you implement only one filter, this is 0.
- visible_name: This is the filter name which appears in the filter selection dialog box.
- flags: The available flags are DVDSYNTH_FILTER_ONLY_ONE, which indicates that only one instance of this filter can be applied to a particular device, and DVDSYNTH_FILTER_ALL_DEVICE_CLASSES, which indicates that the filter will work on all types of devices. (Otherwise it will be available only on CD/DVD devices.)
- HookDevice: This function is called after the filter selection dialog is closed, if your filter was one of those selected. *pkernel a DvsDeviceKernel object pointer, representing the device "underneath" yours (the raw device plus filters listed above yours in the dialog box). Although this device is not visible to the system, you can send commands to it if you want to by using the KernelScsiCommand function. You should set *pkernel to your own kernel device object, and return your user device object. If you decide not to filter after all, you can leave *pkernel as is and return 0.
- AddAboutMenuItems: Equivalent to the DvsDeviceGlobal function of the same name.
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:
- AddDeviceMenuItems: this function is passed a DvsMenu in which the currently active submenu is the device-specific menu for this device (the submenu with a title like "Device #1 (E:)"). Your function can add any items or submenus it likes. This function may be omitted, in which case no items are added.
- QueryUnplug: this function is called when the user asks to unplug a device. It should return a nonnegative value to accept the unplug, or a negative value to veto it. If you return a negative value, you should display an error message so that the user has some idea what happened. Do not assume that the device will actually be unplugged if you return a positive value -- some other plugin might veto it.
- Delete: this function should delete itself as well as its corresponding kernel object, and any resources they allocated.
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.
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:
- AddItem: Adds a menu item to the currently active submenu. The menu item will be labelled with text, and will have a check-mark if checked is nonzero. If the item is chosen by the user, the function callback will be called with the arguments p and i.
- AddDisabledItem: Like AddItem, except that the menu item is disabled and cannot be selected, and so no callback function is specified.
- AddSeparator: Adds a horizontal dividing line to the menu. DVDSynth will collapse consecutive AddSeparator calls into one (even if they come from different plugins) and will not add a separator at the beginning or end of a menu, so you can safely use this function without worrying about uglifying the menu.
- BeginSubmenu: Creates a new submenu of the current submenu, with the given title. The new submenu becomes active and will be the target of future AddItem, AddDisabledItem, and AddSeparator calls until you call EndSubmenu.
- EndSubmenu: Changes the active submenu to the parent of the current active submenu.
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:
- ReserveDockingBay: This allocates a slot for a new device out of a pool of 31. If there are no more slots available (unlikely), then DVDSynth will display an error dialog and then return 0. So if this function returns 0 you should silently abort creating a new device. Otherwise, the return value is an object implementing DvsDockingBay.
- Driver_Load: This loads your kernel driver into kernel memory. It also calls the DvdsynthDriverInit entry point in the kernel driver if it exists. If the load fails, DVDSynth will display an error dialog box to the user and then return 0. The filename argument must not contain a path; only files in DVDSynth's directory can be loaded by this function.
- Driver_Call: This function can be used to call an exported entry point in an already-loaded kernel DLL from user mode. Because user-mode memory is not always accessible in kernel mode, the function provides for a limited degree of argument marshalling. The third argument, types, is a string containing one character per function argument. Here are the legal characters and their meanings:
- i: an integer value; passed as-is.
- p: an untranslated pointer value; same as i.
- &: a pointer to a 4-byte integer or pointer value; the value is copied to kernel memory and the new value is copied back to its original location on return.
- <: a pointer to a byte array; the length of the byte array is given by the preceding argument (which must be of type i or &). The array is copied to kernel memory and then copied back on return.
- >: like <, except that the length is given by the following argument.
- s: a pointer to a null-terminated string; the contents of the string are copied to kernel memory, but any changes are not copied back on return.
Note that the marshaller will do the right thing with null pointers, and with pointers which already point to shared kernel memory.
The exported function may be either __cdecl or __stdcall without trouble, because Driver_Call ignores the stack pointer on function return.
- Driver_Unload: Decrements the reference count on the given kernel DLL and unloads it when it reaches zero (i.e. when the number of Driver_Unload calls matches the number of Driver_Load calls). Under Win9x/ME, drivers not properly unloaded will remain in locked memory until the system is next rebooted -- closing DVDSynth is not enough.
- OpenFileRO and CreateOrOpenFileRW: Open a kernel-mode file handle for read-only access or read/write access, respectively. This is the only way to get a file handle which can be used from the kernel DLL. The possible values of creation_disposition are:
- 0 - succeed only if the file already existed.
- 1 - succeed whether the file already existed or not.
- 2 - succeed only if the file did not exist.
On success these functions return a nonnegative value and put the handle into *phandle. On failure they return a negative value. The error codes aren't well-defined yet.
- CloseFile closes a kernel-mode file handle.
- OpenKernelEventHandle creates a kernel-mode counterpart to an existing Win32 event handle. The argument has type HANDLE; the reason it's declared as void* is to prevent the header file from depending on <windows.h>.
- CloseKernelEventHandle closes the handle created by OpenKernelEventHandle. You must call this before destroying the user-mode event handle. On Win9x/ME systems, if you never call it, the handle will not be destroyed until the next system reboot.
- Is95 returns nonzero iff you're running on a Win9x/ME system.
- KernelScsiCommand allows you to call the ScsiCommand function of a kernel device object. (You must not call it directly.) Its prototype is the same as ScsiCommand (dvs_scsi_func is typedef'd to that prototype).
- GetDvdVideoInfo returns various information about the DVD disc currently inserted in the supplied device. If the device isn't a DVD drive or doesn't have a DVD disc in it, it'll return a negative value. A nonnegative value indicates success. The most interesting value returned is vmg_ifo_sector, which is the logical block address of the beginning of the VIDEO_TS.IFO file. Only the DVD subtitler currently uses this function.
- Sprint is an internationalized sprintf function, intended for localization support which hasn't materialized yet. Look at the source code to see how it works.
- GetDvdsynthDirectory returns the folder containing the DVDSynth executable, with a trailing slash.
- GetTaskbarHWND returns the taskbar notification window handle. (The return type is HWND). Used internally.
- GetDvdsynthDevicesID is a hack. Ignore it.
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:
- SetHandlers: This informs DVDSynth that your user and kernel device objects are ready for use. After you call this function DVDSynth will make the device visible to the system and start calling your objects to handle SCSI commands and UI events.
- RequestUnplug: This causes DVDSynth to behave as though the user had selected the "Unplug this device" option from the device menu.
- SharedPool_Alloc: This allocates memory that is visible from both user mode and kernel mode (at the same virtual address). This is currently the only supported way to allocate such memory. There is no corresponding freeing function; instead, all shared memory associated with a particular device is automatically freed by DVDSynth when the device is unplugged (after your Delete method is called).
- GetScsiID: This returns the SCSI ID of this device. This is the same as the device number displayed in the tray menu.
- GetDriveLetters: This returns a bitmap with set bits indicating a drive letter assigned to this device. Bit 0 is drive A:, bit 25 is drive Z:. Generally at most one bit will be set.
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:
- MemSet and MemCpy behave like the standard C functions. They are provided because C run-time libraries are not available in kernel mode.
- DebugPrintf prints a message that you can view from user mode with the DebugView utility from sysinternals.com.
- AsyncUserModeCall is supposed to call the specified function in user mode with the specified arguments, but I can't remember if I actually implemented it, so you're probably better off using events for now.
- ReadFile and WriteFile perform kernel mode file I/O. (The functions to open and close the files are only available from user mode.) No file pointer is maintained, so you must specify an absolute start offset with every call.
- SetEvent behaves like the Win32 SetEvent function. You can use it to wake a sleeping user-mode thread from kernel mode. You must not pass a Win32 HANDLE to this function; instead, use the return value of OpenKernelEventHandle.
Return to the main page