Wine implementation is closer to the Windows NT architecture, even if several subsystems are not implemented yet (remind also that 16bit support is implemented in a 32-bit Windows EXE, not as a subsystem). Here's the overall picture:
+---------------------+ \ | Windows EXE | } application +---------------------+ / +---------+ +---------+ \ | Windows | | Windows | \ application & system DLLs | DLL | | DLL | / +---------+ +---------+ / +---------+ +---------+ +-----------+ +--------+ \ | GDI32 | | USER32 | | | | | \ | DLL | | DLL | | | | Wine | \ +---------+ +---------+ | | | Server | \ core system DLLs +---------------------+ | | | | / (on the left side) | Kernel32 DLL | | Subsystem | | NT-like| / | (Win32 subsystem) | |Posix, OS/2| | Kernel | / +---------------------+ +-----------+ | | / | | +---------------------------------------+ | | | NTDLL | | | +---------------------------------------+ +--------+ +---------------------------------------+ \ | Wine executable (wine-?thread) | } unix executable +---------------------------------------+ / +---------------------------------------------------+ \ | Wine drivers | } Wine specific DLLs +---------------------------------------------------+ / +------------+ +------------+ +--------------+ \ | libc | | libX11 | | other libs | } unix shared libraries +------------+ +------------+ +--------------+ / (user space) +---------------------------------------------------+ \ | Unix kernel (Linux,*BSD,Solaris,OS/X) | } (Unix) kernel space +---------------------------------------------------+ / +---------------------------------------------------+ \ | Unix device drivers | } Unix drivers (kernel space) +---------------------------------------------------+ /
Wine must at least completely replace the "Big Three" DLLs (KERNEL/KERNEL32, GDI/GDI32, and USER/USER32), which all other DLLs are layered on top of. But since Wine is (for various reasons) leaning towards the NT way of implementing things, the NTDLL is another core DLL to be implemented in Wine, and many KERNEL32 and ADVAPI32 features will be implemented through the NTDLL.
As of today, no real subsystem (apart the Win32 one) has been implemented in Wine.
The Wine server provides the backbone for the implementation of the core DLLs. It mainly implementents inter-process synchronization and object sharing. It can be seen, from a functional point of view, as a NT kernel (even if the APIs and protocols used between Wine's DLL and the Wine server are Wine specific).
Wine uses the Unix drivers to access the various hardware pieces on the box. However, in some cases, Wine will provide a driver (in Windows sense) to a physical hardware device. This driver will be a proxy to the Unix driver (this is the case, for example, for the graphical part with X11 or SDL drivers, audio with OSS or ALSA drivers...).
All DLLs provided by Wine try to stick as much as possible
to the exported APIs from the Windows platforms. There are
rare cases where this is not the case, and have been
propertly documented (Wine DLLs export some Wine specific
APIs). Usually, those are prefixed with
__wine
.
Let's now review in greater details all of those components.
The Wine server is among the most confusing concepts in
Wine. What is its function in Wine? Well, to be brief, it
provides Inter-Process Communication (IPC),
synchronization, and process/thread management. When the
Wine server launches, it creates a Unix socket for the
current host based on (see below) your home directory's
.wine subdirectory (or wherever the
WINEPREFIX
environment variable
points to) - all Wine processes launched later connects to
the Wine server using this socket. (If a Wine server was
not already running, the first Wine process will start up
the Wine server in auto-terminate mode (i.e. the Wine
server will then terminate itself once the last Wine
process has terminated).)
In earlier versions of Wine the master socket mentioned
above was actually created in the configuration directory;
either your home directory's /wine
subdirectory or wherever the
WINEPREFIX
environment variable
points>. Since that might not be possible the socket is
actually created within the /tmp
directory with a name that reflects the configuration
directory. This means that there can actually be several
separate copies of the Wine server running; one per
combination of user and configuration directory. Note that
you should not have several users using the same
configuration directory at the same time; they will have
different copies of the Wine server running and this could
well lead to problems with the registry information that
they are sharing.
Every thread in each Wine process has its own request
buffer, which is shared with the Wine server. When a
thread needs to synchronize or communicate with any other
thread or process, it fills out its request buffer, then
writes a command code through the socket. The Wine server
handles the command as appropriate, while the client
thread waits for a reply. In some cases, like with the
various WaitFor???
synchronization
primitives, the server handles it by marking the client
thread as waiting and does not send it a reply before the
wait condition has been satisfied.
The Wine server itself is a single and separate Unix
process and does not have its own threading - instead, it
is built on top of a large poll()
loop that alerts the Wine server whenever anything
happens, such as a client having sent a command, or a wait
condition having been satisfied. There is thus no danger
of race conditions inside the Wine server itself - it is
often called upon to do operations that look completely
atomic to its clients.
Because the Wine server needs to manage processes, threads, shared handles, synchronization, and any related issues, all the clients' Win32 objects are also managed by the Wine server, and the clients must send requests to the Wine server whenever they need to know any Win32 object handle's associated Unix file descriptor (in which case the Wine server duplicates the file descriptor, transmits it back to the client, and leaves it to the client to close the duplicate when the client has finished with it).
This section mainly applies to builtin DLLs (DLLs provided by Wine). See section Section 7.3.4 for the details on native vs. builtin DLL handling.
Loading a Windows binary into memory isn't that hard by itself, the hard part is all those various DLLs and entry points it imports and expects to be there and function as expected; this is, obviously, what the entire Wine implementation is all about. Wine contains a range of DLL implementations. You can find the DLLs implementation in the dlls/ directory.
Each DLL (at least, the 32 bit version, see below) is implemented in a Unix shared library. The file name of this shared library is the module name of the DLL with a .dll.so suffix (or .drv.so or any other relevant extension depending on the DLL type). This shared library contains the code itself for the DLL, as well as some more information, as the DLL resources and a Wine specific DLL descriptor.
The DLL descriptor, when the DLL is instanciated, is used to create an in-memory PE header, which will provide access to various information about the DLL, including but not limited to its entry point, its resources, its sections, its debug information...
The DLL descriptor and entry point table is generated by the winebuild tool (previously just named build), taking DLL specification files with the extension .spec as input. Resources (after compilation by wrc) or message tables (after compilation by wmc) are also added to the descriptor by winebuild.
Once an application module wants to import a DLL, Wine will look at:
through its list of registered DLLs (in fact, both
the already loaded DLLs, and the already loaded
shared libraries which has registered a DLL
descriptor). Since, the DLL descriptor is
automatically registered when the shared library is
loaded - remember, registration call is put inside a
shared library constructor - using the
PRELOAD
environment variable
when running a Wine process can force the
registration of some DLL descriptors.
If it's not registered, Wine will look for it on
disk, building the shared library name from the DLL
module name. Directory searched for are specified by
the WINEDLLPATH
environment
variable.
Failing that, it will look for a real Windows .DLL file to use, and look through its imports, etc) and use the loading of native DLLs.
After the DLL has been identified (assuming it's still a
native one), it's mapped into memory using a
dlopen()
call. Note, that Wine doesn't
use the shared library mechanisms for resolving and/or
importing functions between two shared libraries (for two
DLLs). The shared library is only used for providing a way
to load a piece of code on demand. This piece of code,
thanks the DLL descriptor, will provide the same type of
information a native DLL would. Wine can then use the same
code for native and builtin DLL to handle imports/exports.
Wine also relies on the dynamic loading features of the Unix shared libraries to relocate the DLLs if needed (the same DLL can be loaded at different address in two different processes, and even in two consecutive run of the same executable if the order of loading the DLLs differ).
The DLL descriptor is registered in the Wine realm using
some tricks. The winebuild tool, while
creating the code for DLL descriptor, also creates a
constructor, that will be called when the shared library is
loaded into memory. This constructor will actually register
the descriptor to the Wine DLL loader. Hence, before the
dlopen
call returns, the DLL descriptor
will be known and registered. This also helps to deal with
the cases where there's still dependencies (at the ELF
shared lib level, not at the embedded DLL level) between
different shared libraries: the embedded DLLs will be
properly registered, and even loaded (from a Windows point
of view).
Since Wine is 32-bit code itself, and if the compiler supports Windows' calling convention, stdcall (gcc does), Wine can resolve imports into Win32 code by substituting the addresses of the Wine handlers directly without any thunking layer in between. This eliminates the overhead most people associate with "emulation", and is what the applications expect anyway.
However, if the user specified WINEDEBUG=+relay
, a thunk layer is inserted between the
application imports and the Wine handlers (actually the
export table of the DLL is modified, and a thunk is
inserted in the table); this layer is known as "relay"
because all it does is print out the arguments/return
values (by using the argument lists in the DLL
descriptor's entry point table), then pass the call on,
but it's invaluable for debugging misbehaving calls into
Wine code. A similar mechanism also exists between Windows
DLLs - Wine can optionally insert thunk layers between
them, by using WINEDEBUG=+snoop
,
but since no DLL descriptor information exists for
non-Wine DLLs, this is less reliable and may lead to
crashes.
For Win16 code, there is no way around thunking - Wine needs to relay between 16-bit and 32-bit code. These thunks switch between the app's 16-bit stack and Wine's 32-bit stack, copies and converts arguments as appropriate (an int is 16 bit 16-bit and 32 bits in 32-bit, pointers are segmented in 16 bit (and also near or far) but are 32 bit linear values in 32 bit), and handles the Win16 mutex. Some finer control can be obtained on the conversion, see winebuild reference manual for the details. Suffice to say that the kind of intricate stack content juggling this results in, is not exactly suitable study material for beginners.
A DLL descriptor is also created for every 16 bit DLL. However, this DLL normally paired with a 32 bit DLL. Either, it's the 16 bit counterpart of the 16 bit DLL (KRNL386.EXE for KERNEL32, USER for USER32...), or a 16 bit DLL directly linked to a 32 bit DLL (like SYSTEM for KERNEL32, or DDEML for USER32). In those cases, the 16 bit descriptor(s) is (are) inserted in the same shared library as the the corresponding 32 bit DLL. Wine will also create symbolic links between kernel32.dll.so and system.dll.so so that loading of either kernel32.dll or system.dll will end up on the same shared library.
This document mainly deals with the status of current DLL support by Wine. The Wine ini file currently supports settings to change the load order of DLLs. The load order depends on several issues, which results in different settings for various DLLs.
Native DLLs of course guarantee 100% compatibility for routines they implement. For example, using the native USER DLL would maintain a virtually perfect and Windows 95-like look for window borders, dialog controls, and so on. Using the built-in Wine version of this library, on the other hand, would produce a display that does not precisely mimic that of Windows 95. Such subtle differences can be engendered in other important DLLs, such as the common controls library COMMCTRL or the common dialogs library COMMDLG, when built-in Wine DLLs outrank other types in load order.
More significant, less aesthetically-oriented problems can result if the built-in Wine version of the SHELL DLL is loaded before the native version of this library. SHELL contains routines such as those used by installer utilities to create desktop shortcuts. Some installers might fail when using Wine's built-in SHELL.
Not every application performs better under native DLLs. If a library tries to access features of the rest of the system that are not fully implemented in Wine, the native DLL might work much worse than the corresponding built-in one, if at all. For example, the native Windows GDI library must be paired with a Windows display driver, which of course is not present under Intel Unix and Wine.
Finally, occasionally built-in Wine DLLs implement more features than the corresponding native Windows DLLs. Probably the most important example of such behavior is the integration of Wine with X provided by Wine's built-in USER DLL. Should the native Windows USER library take load-order precedence, such features as the ability to use the clipboard or drag-and-drop between Wine windows and X windows will be lost.
Clearly, there is no one rule-of-thumb regarding which load-order to use. So, you must become familiar with what specific DLLs do and which other DLLs or features a given library interacts with, and use this information to make a case-by-case decision.
Using the DLL sections from the wine configuration file, the load order can be tweaked to a high degree. In general it is advised not to change the settings of the configuration file. The default configuration specifies the right load order for the most important DLLs.
The default load order follows this algorithm: for all DLLs which have a fully-functional Wine implementation, or where the native DLL is known not to work, the built-in library will be loaded first. In all other cases, the native DLL takes load-order precedence.
The DefaultLoadOrder
from the
[DllDefaults] section specifies for all DLLs which version
to try first. See manpage for explanation of the arguments.
The [DllOverrides] section deals with DLLs, which need a different-from-default treatment.
The [DllPairs] section is for DLLs, which must be loaded in pairs. In general, these are DLLs for either 16-bit or 32-bit applications. In most cases in Windows, the 32-bit version cannot be used without its 16-bit counterpart. For Wine, it is customary that the 16-bit implementations rely on the 32-bit implementations and cast the results back to 16-bit arguments. Changing anything in this section is bound to result in errors.
For the future, the Wine implementation of Windows DLL seems to head towards unifying the 16 and 32 bit DLLs wherever possible, resulting in larger DLLs. They are stored in the dlls/ subdirectory using the 32-bit name.
Every Win32 process in Wine has its own dedicated native process on the host system, and therefore its own address space. This section explores the layout of the Windows address space and how it is emulated.
Firstly, a quick recap of how virtual memory works. Physical
memory in RAM chips is split into
frames, and the memory that each
process sees is split into pages. Each
process has its own 4 gigabytes of address space (4gig being
the maximum space addressable with a 32 bit pointer). Pages
can be mapped or unmapped: attempts to access an unmapped
page cause an
EXCEPTION_ACCESS_VIOLATION
which has
the easily recognizable code of
0xC0000005
. Any page can be mapped to
any frame, therefore you can have multiple addresses which
actually "contain" the same memory. Pages can also be mapped
to things like files or swap space, in which case accessing
that page will cause a disk access to read the contents into
a free frame.
When a Win32 process starts, it does not have a clear address space to use as it pleases. Many pages are already mapped by the operating system. In particular, the EXE file itself and any DLLs it needs are mapped into memory, and space has been reserved for the stack and a couple of heaps (zones used to allocate memory to the app from). Some of these things need to be at a fixed address, and others can be placed anywhere.
The EXE file itself is usually mapped at address 0x400000 and up: indeed, most EXEs have their relocation records stripped which means they must be loaded at their base address and cannot be loaded at any other address.
DLLs are internally much the same as EXE files but they have relocation records, which means that they can be mapped at any address in the address space. Remember we are not dealing with physical memory here, but rather virtual memory which is different for each process. Therefore OLEAUT32.DLL may be loaded at one address in one process, and a totally different one in another. Ensuring all the functions loaded into memory can find each other is the job of the Windows dynamic linker, which is a part of NTDLL.
So, we have the EXE and its DLLs mapped into memory. Two
other very important regions also exist: the stack and the
process heap. The process heap is simply the equivalent of
the libc malloc
arena on UNIX: it's a
region of memory managed by the OS which
malloc
/HeapAlloc
partitions and hands out to the application. Windows
applications can create several heaps but the process heap
always exists.
Windows 9x also implements another kind of heap: the shared heap. The shared heap is unusual in that anything allocated from it will be visible in every other process.
So far we've assumed the entire 4 gigs of address space is available for the application. In fact that's not so: only the lower 2 gigs are available, the upper 2 gigs are on Windows NT used by the operating system and hold the kernel (from 0x80000000). Why is the kernel mapped into every address space? Mostly for performance: while it's possible to give the kernel its own address space too - this is what Ingo Molnars 4G/4G VM split patch does for Linux - it requires that every system call into the kernel switches address space. As that is a fairly expensive operation (requires flushing the translation lookaside buffers etc) and syscalls are made frequently it's best avoided by keeping the kernel mapped at a constant position in every processes address space.
Basically, the comparison of memory mappings looks as follows:
Table 7-2. Memory layout (Windows and Wine)
Address | Windows 9x | Windows NT | Linux |
---|---|---|---|
00000000-7fffffff | User | User | User |
80000000-bfffffff | Shared | User | User |
c0000000-ffffffff | Kernel | Kernel | Kernel |
On Windows 9x, in fact only the upper gigabyte
(0xC0000000
and up) is used by the
kernel, the region from 2 to 3 gigs is a shared area used
for loading system DLLs and for file mappings. The bottom
2 gigs on both NT and 9x are available for the programs
memory allocation and stack.
Wine (with a bit of black magic) is able to map all items at the correct locations as depicted above.
Wine also implements the shared heap so native win9x DLLs
can be used. This heap is always created at the
SYSTEM_HEAP_BASE
address or
0x80000000
and defaults to 16
megabytes in size.
There are a few other magic locations. The bottom 64k of memory is deliberately left unmapped to catch null pointer dereferences. The region from 64k to 1mb+64k are reserved for DOS compatibility and contain various DOS data structures. Finally, the address space also contains mappings for the Wine binary itself, any native libaries Wine is using, the glibc malloc arena and so on.
Up until about the start of 2004, the Linux address space very much resembled the Windows 9x layout: the kernel sat in the top gigabyte, the bottom pages were unmapped to catch null pointer dereferences, and the rest was free. The kernels mmap algorithm was predictable: it would start by mapping files at low addresses and work up from there.
The development of a series of new low level patches violated many of these assumptions, and resulted in Wine needing to force the Win32 address space layout upon the system. This section looks at why and how this is done.
The exec-shield patch increases security by randomizing the kernels mmap algorithms. Rather than consistently choosing the same addresses given the same sequence of requests, the kernel will now choose randomized addresses. Because the Linux dynamic linker (ld-linux.so.2) loads DSOs into memory by using mmap, this means that DSOs are no longer loaded at predictable addresses, so making it harder to attack software by using buffer overflows. It also attempts to relocate certain binaries into a special low area of memory known as the ASCII armor so making it harder to jump into them when using string based attacks.
Prelink is a technology that enhances startup times by precalculating ELF global offset tables then saving the results inside the native binaries themselves. By grid fitting each DSO into the address space, the dynamic linker does not have to perform as many relocations so allowing applications that heavily rely on dynamic linkage to be loaded into memory much quicker. Complex C++ applications such as Mozilla, OpenOffice and KDE can especially benefit from this technique.
The 4G VM split patch was developed by Ingo Molnar. It gives the Linux kernel its own address space, thereby allowing processes to access the maximum addressable amount of memory on a 32-bit machine: 4 gigabytes. It allows people with lots of RAM to fully utilise that in any given process at the cost of performance: the reason behind giving the kernel a part of each processes address space was to avoid the overhead of switching on each syscall.
Each of these changes alter the address space in a way incompatible with Windows. Prelink and exec-shield mean that the libraries Wine uses can be placed at any point in the address space: typically this meant that a library was sitting in the region that the EXE you wanted to run had to be loaded (remember that unlike DLLs, EXE files cannot be moved around in memory). The 4G VM split means that programs could receive pointers to the top gigabyte of address space which some are not prepared for (they may store extra information in the high bits of a pointer, for instance). In particular, in combination with exec-shield this one is especially deadly as it's possible the process heap could be allocated beyond ADDRESS_SPACE_LIMIT which causes Wine initialization to fail.
The solution to these problems is for Wine to reserve particular parts of the address space so that areas that we don't want the system to use will be avoided. We later on (re/de)allocate those areas as needed. One problem is that some of these mappings are put in place automatically by the dynamic linker: for instance any libraries that Wine is linked to (like libc, libwine, libpthread etc) will be mapped into memory before Wine even gets control. In order to solve that, Wine overrides the default ELF initialization sequence at a low level and reserves the needed areas by using direct syscalls into the kernel (ie without linking against any other code to do it) before restarting the standard initialization and letting the dynamic linker continue. This is referred to as the preloader and is found in loader/preloader.c.
Once the usual ELF boot sequence has been completed, some native libraries may well have been mapped above the 3gig limit: however, this doesn't matter as 3G is a Windows limit, not a Linux limit. We still have to prevent the system from allocating anything else above there (like the heap or other DLLs) though so Wine performs a binary search over the upper gig of address space in order to iteratively fill in the holes with MAP_NORESERVE mappings so the address space is allocated but the memory to actually back it is not. This code can be found in libs/wine/mmap.c:reserve_area.
Let's take a closer look at the way Wine loads and run processes in memory.
When starting a Wine process from command line (we'll get later on to the differences between NE, PE and Winelib executables), there are a couple of things Wine need to do first. A first executable is run to check the threading model of the underlying OS (see Section 8.1.5 for the details) and will start the real Wine loader corresponding to the choosen threading model.
Then Wine graps a few elements from the Unix world: the environment, the program arguments. Then the ntdll.dll.so is loaded into memory using the standard shared library dynamic loader. When loaded, NTDLL will mainly first create a decent Windows environment:
create a PEB and a TEB
set up the connection to the Wine server - and eventually launching the Wine server if none runs
create the process heap
Then Kernel32 is loaded (but now
using the Windows dynamic loading capabilities) and a Wine
specific entry point is called
__wine_kernel_init
. This function
will actually handle all the logic of the process loading
and execution, and will never return from it's call.
__wine_kernel_init
will undergo the
following tasks:
initialization of program arguments from Unix program arguments
lookup of executable in the file system
If the file is not found, then an error is printed and the Wine loader stops.
We'll cover the non-PE file type later on, so assume for now it's a PE file. The PE module is loaded in memory using the Windows shared library mechanism. Note that the dependencies on the module are not resolved at this point.
A new stack is created, which size is given in the PE header, and this stack is made the one of the running thread (which is still the only one in the process). The stack used at startup will no longer be used.
Which this new stack,
ntdll.LdrInitializeThunk
is
called which performs the remaining initialization
parts, including resolving all the DLL imports on
the PE module, and doing the init of the TLS slots.
Control can now be passed to the
EntryPoint
of the PE module,
which will let the executable run.
The steps used are closely link to what is done in the previous case.
There are however a few points to look at a bit more
closely. The inner implementation creates the child
process using the fork()
and
exec()
calls. This means that we
don't need to check again for the threading model, we can
use what the parent (or the grand-parent process...)
started from command line has found.
The Win32 process creation allows to pass a lot of information between the parent and the child. This includes object handles, windows title, console parameters, environment strings... Wine makes use of both the standard Unix inheritance mechanisms (for environment for example) and the Wine server (to pass from parent to child a chunk of data containing the relevant information).
The previously described loading mechanism will check in the Wine server if such a chunk exists, and, if so, will perform the relevant initialization.
Some further synchronization is also put in place: a parent will wait until the child has started, or has failed. The Wine server is also used to perform those tasks.
Before going into the gory details, let's first go back to what a Winelib application is. It can be either a regular Unix executable, or a more specific Wine beast. This later form in fact creates two files for a given executable (say foo.exe). The first one, named foo will be a symbolic link to the Wine loader (wine). The second one, named foo.exe.so, is the equivalent of the .dll.so files we've already described for DLLs. As in Windows, an executable is, among other things, a module with its import and export information, as any DLL, it makes sense Wine uses the same mechanisms for loading native executables and DLLs.
When starting a Winelib application from the command line (say with foo arg1 arg2), the Unix shell will execute foo as a Unix executable. Since this is in fact the Wine loader, Wine will fire up. However, will notice that it hasn't been started as wine but as foo, and hence, will try to load (using Unix shared library mechanism) the second file foo.exe.so. Wine will recognize a 32 bit module (with its descriptor) embedded in the shared library, and once the shared library loaded, it will proceed the same path as when loading a standard native PE executable.
Wine needs to implement this second form of executable in
order to maintain the order of initialization of some
elements in the executable. One particular issue is when
dealing with global C++ objects. In standard Unix
executable, the call of the constructor to such objects is
stored in the specific section of the executable
(.init
not to name it). All
constructors in this section are called before the
main
function is called. Creating a
Wine executable using the first form mentionned above will
let those constructors being called before Wine gets a
chance to initialize itself. So, any constructor using a
Windows API will fail, because Wine infrastructure isn't
in place. The use of the second form for Winelib
executables ensures that we do the initialization using
the following steps:
initialize the Wine infrastructure
load the executable into memory
handle the import sections for the executable
call the global object constructors (if any). They now can properly call the Windows APIs
call the executable entry point
The attentive reader would have noted that the resolution
of imports for the executable is done, as for a DLL, when
the executable/DLL descriptor is registered. However, this
is done also by adding a specific constructor in the
.init
section. For the above describe
scheme to function properly, this constructor must be the
first constructor to be called, before all the other
constructors, generated by the executable itself. The Wine
build chain takes care of that, and also generating the
executable/DLL descriptor for the Winelib executable.
When Wine is requested to run a NE (Win 16 process), it will in fact hand over the execution of it to a specific executable winevdm. VDM stands for Virtual DOS Machine. This winevdm will in fact set up the correct 16 bit environment to run the executable. Any new 16 bit process created by this executable (or its children) will run into the same winevdm instance. Among one instance, several functionalities will be provided to those 16 bit processes, including the cooperative multitasking, sharing the same address space, managing the selectors for the 16 bit segments needed for code, data and stack.
Note that several winevdm instances can run in the same Wine session, but the functionalities described above are only shared among a given instance, not among all the instances. winevdm is built as Winelib application, and hence has access to any facility a 32 bit application has.
The behaviour we just described also applies to DOS executables, which are handled the same way by winevdm.
Wine will not allow running native Windows drivers under Unix. This comes mainly because (look at the generic architecture schemas) Wine doesn't implement the kernel features of Windows (kernel here really means the kernel, not the KERNEL32 DLL), but rather sets up a proxy layer on top of the Unix kernel to provide the NTDLL and KERNEL32 features. This means that Wine doesn't provide the inner infrastructure to run native drivers, either from the Win9x family or from the NT family.
In other words, Wine will only be able to provide access to a specific device, if and only if, 1/ this device is supported in Unix (there is Unix-driver to talk to it), 2/ Wine has implemented the proxy code to make the glue between the API of a Windows driver, and the Unix interface of the Unix driver.
Wine, however, tries to implement in the various DLLs needing to access devices to do it through the standard Windows APIs for device drivers in user space. This is for example the case for the multimedia drivers, where Wine loads Wine builtin DLLs to talk to the OSS interface, or the ALSA interface. Those DLLs implement the same interface as any user space audio driver in Windows.