Understanding Wine
DLL and Service Development in Wine
»Index
»Setup
»DLLs
»Services
»Windows
»svchost
»Links
»Notes
»Author
»Wine HQ

This tutorial describes a simple COM object implemented as a DLL. Client programs may create an instance of the counter, increment the counter, and read back the count value. Exciting, eh?

The tutorial demonstrates foundation topics important to COM development and describes how to integrate new COM DLLs into Wine. A basic understanding of COM and C programming is assumed. An installation of Wine is required to compile and run the code developed in this tutorial.

Source code for this tutorial is available from the tutorial's introduction.

Synopsis

  • Generic Structure of a COM DLL

  • Core DLL implementation

  • Wine Specifics for COM DLLs

  • Registering and Testing COM DLLs in Wine

Generic Structure of a COM DLL

COM DLLs often share a great deal of structure that can be easily copied from an existing DLL and updated for new DLL. This includes:

  • Interface specification

  • DllRegisterServer and DllUnregisterServer functions to register and unregister the DLL in the system registry

  • DllMain, DllGetClassObject, and DllCanUnloadNow called by COM when a DLL is used by an application

  • Implementation of the IUnknown interface

  • Implementation of the IClassFactory interface

Interface Specification

An interface specification describes the methods that client programs can use to interact with specific COM object. The interface definition provides flexibility in where the service resides by aiding in the creation of marshaling functions for out of processes services, and provides a language independent description of how clients can call into an interface. While development without an interface specification is possible, doing so would limit the created service to running as an in process server and not allow languages such as Visual basic to interact with the service.

To learn more about authoring a new IDL take a look at MSDNs description of IDL or pick up a good book on the topic.

IDLs for services ported to Wine may be generated using Wine's oleview program. This requires a type library for the service or a DLL that carries the type library within it. If these are not available, the IDL can be rewritten by hand from the services interface documentation that is often available on MSDN. The count IDL should be stored with the other Wine IDLs in $WINEPATH/include.

Projects using code generated from an IDL can use a simple wrapper IDL located in the project's directory. The count DLL uses definitions of class IDs defined in $WINEPATH/include/count.idl. Thus, it uses the local wrapper $WINEPATH/dlls/count/count_local.idl to include the base version of the IDL.

Note
Key Points
  • $WINEPATH is used throughout the tutorials to denote the directory containing your installation of Wine. You can set this as an environment variable in your shell if you wish to copy and past commands. The command export WINEPATH=/path/to/wine (replacing /path/to/wine with the path to wine on your system) sets this variable in bash.

  • The name of the local IDL must be different from that being included to prevent the build system from (recursively) including the local copy.

  • A local copy is used so that widl generated files sit in the local directory, rather than in $WINEPATH/include

DLL (Un)Registration

COM references the registry to find the DLL required to satisfy client requests to create an in-process COM object. A DLL must implement the DllRegisterServer and DllUnregisterServer functions used to register and unregister itself. This is accomplished in the count DLL using the generic registration implementation in $WINEPATH/dlls/count/regsvr.c.

Note
Key Points
  • Use regsvr.c by updating the coclass_list and interface_list to specify the COM classes and interfaces provided by the DLL being developed. These lists must be NULL terminated.

DllMain, DllGetClassObject, and DllCanUnloadNow

DllMain is a DLL's entry point. When an object is requested by a client, COM calls the DllGetClassObject function to access a class factory provided by the DLL, and uses the class factory to provide the client with a pointer to an object instance. DllCanUnloadNow is called by COM to see if it is safe to unload the DLL.

Base implementations of these functions are often quite similar. A first cut for the count DLL is located in $WINEPATH/dlls/count/count_main.c.

Note
Key Points
  • Basic data structures used throughout the count implementation are placed into count_private.h described below.

  • An application specific debugging channel is specified using the WINE_DEFAULT_DEBUG_CHANNEL(count); statement. Calls to debugging functions such as TRACE, FIXME, and WARN are associated with this channel and can be enabled or disabled through the WINEDEBUG environment variable. Definitions of these functions can be found in $WINEPATH/include/wine/debug.h.

  • The DLL_WINE_PREATTACH case in DllMain gives preference to native service DLLs if available. Since the count service is not native to Windows, its inclusion in this sample program is superfluous.

IClassFactory

IClassFactory provides the interface used to create new instances of a COM class and is used by the DllGetClassObject function to provide new object instances to requesting clients. The IDL for the IClassFactory is found in $WINEPATH/include/unknwn.idl The count servers IClassFactory is located in $WINEPATH/dlls/count/factory.c.

Note
Key Points
  • The IClassFactory inherits from the IUnknown interface and consequently implements the IUnknown interface methods.

  • COM development often uses C++ but Wine uses C. Consequently, Wine manually preforms some tasks handled by object oriented languages. An example of this is manual name mangling seen in COUNT_IClassFactory_AddRef, COUNT_IClassFactory_QueryInterface, and COUNT_IClassFactory_Release.

Core DLL implementation

Little remains to be written after the above broiler plate code in place. The core of a new COM object will probably use:

  • A common header file to tie the object together

  • The actual application logic

Common Header File

A common header file is used by the implementation of a DLL to define the object's structure and expose a static instance of the object. The common header file used by count is seen in $WINEPATH/dlls/count/count_private.h.

Note
Key Points
  • A definition of CountImpl is provided that includes vtables for each interface exposed by the class and any member variables used by objects.

  • A static instance of the class is exposed.

Application Logic

The core functionality of count is implemented in $WINEPATH/dlls/count/count.c, including methods for the interfaces defined in $WINEPATH/include/count.idl. The IUnknown interface is used to interact with COM objects and is provided by all COM objects. The IDL for the IUnknown is found in $WINEPATH/include/unknwn.idl. Since ICount descends from IUnknown, the implementation of IUnknown resides within ICount.

Note
Key Points
  • This file uses count_private.h header and Wine debugging channel.

  • The IUnknown interface AddRef, QueryInterface, and Release are defined. Notice again the manual name mangling used since development is in C.

  • The ICOM_THIS_MULTI macro manipulates the incoming LPUNKNOWN iface pointer such that it assumes the IUnknown interface (unkVtbl) of a count object (CountImpl). The resulting pointer is stored in a pointer named This. This functionality emulates the this pointer resulting from casting a C++ object to a related class. The macro is defined in count_private.h.

  • This generic implementation of the IUnknown interface can be reused in new DLLs with minimal changes.

  • Member variables of a count object are stored in the CountImpl structure. These variables are accessed through a pointer to an instance of ICount.

  • A static count instance is created by combining the vtables for each implemented interface with member variables. The base structure is defined in $WINEPATH/dlls/count/count_private.h and instantiated as COUNT_Count.

Wine Specifics for COM DLLs

Most of the description up to this point is applicable to general COM development. We now transition to points specific to development of a COM object in Wine. These Wine specifics include:

  • Exported function specification file

  • Makefile generation

Specification of Exported Functions

All COM DLLs will export at least the following four functions: DllCanUnloadNow, DllGetClassObject, DllRegisterServer, and DllUnregisterServer. A DLL must explicitly export functions that will be called by COM. In Wine this accomplished using a spec file to specify the functions to export. Count's spec is located in $WINEPATH/dlls/count/count.spec:

@ stdcall -private DllCanUnloadNow()
@ stdcall -private DllGetClassObject(ptr ptr ptr)
@ stdcall -private DllRegisterServer()
@ stdcall -private DllUnregisterServer()

Makefile Generation

Wine's build system eases the authoring of Makefiles. A new DLL requires Makefile.in describing key properties of the DLL. For count this is located in $WINEPATH/dlls/count/Makefile.in:

TOPSRCDIR = @top_srcdir@
TOPOBJDIR = ../..
SRCDIR    = @srcdir@
VPATH     = @srcdir@
MODULE    = count.dll
IMPORTLIB = libcount.$(IMPLIBEXT)
IMPORTS   = ole32 user32 advapi32 kernel32
EXTRALIBS = -luuid

C_SRCS = \
        count_main.c \
        factory.c \
        count.c \
        regsvr.c

IDL_I_SRCS = count_local.idl

@MAKE_DLL_RULES@

@DEPENDENCIES@  # everything below this line is overwritten by make depend
Note
Key Points
  • Dependent libraries are specified with the IMPORTS variable. The library required for a function is noted in the function`s documentation provided by MSDN.

  • IDL_I_SRCS instructs widl to generate the interface identifiers from an IDL file.

  • Any new Makefile.in must be added to the build system for it to become active. Users of git accomplish this using git add on the new Makefile.in and then running ./tools/make_makefiles. This is then followed with a fresh ./config.status; make depend.

After generating the Makefile the DLL can then be built using make. Be warned that building all of Wine takes a long time. For this reason the -C option is your good friend. The count DLL can be built using:

make -C $WINEPATH/dlls/count

Registering and Testing COM DLLs in Wine

Registering a DLL

After the DLL is built, it must be registered in the registry so that COM knows the DLL to load when a client requests an instance of the object. As in Windows, DLL registration is accomplished using the regsvr32 program from the base Wine directory. For count running in Wine this is accomplished using:

cd $WINEPATH
$WINEPATH/programs/regsvr32/regsvr32 count

This adds to the registry entries similar to:

[Software\\Classes\\CLSID\\{55E9B534-76AA-11DC-8B8E-F6CC56D89593}] 1193091471
@="Count"

[Software\\Classes\\CLSID\\{55E9B534-76AA-11DC-8B8E-F6CC56D89593}\\InProcServer32] 1193091471
@="count.dll"
"ThreadingModel"="Both"

[Software\\Classes\\Interface\\{3EE455D8-76AA-11DC-86B3-C4CC56D89593}] 1193091471
@="ICount"

[Software\\Classes\\Interface\\{3EE455D8-76AA-11DC-86B3-C4CC56D89593}\\NumMethods] 1193091471
@="2"

By default this registry is located in ~/.wine/system.reg. A module is unregistered using /u option with regsvr32 (but don't do this if you want to test the count DLL in the next step!):

cd $WINEPATH
$WINEPATH/programs/regsvr32/regsvr32 /u count

Testing

Each DLL in Wine should include a suite of tests. The tests are located in the DLLs tests directory. The client program using the count DLL is $WINEPATH/dlls/count/tests/count_test.c.

Note
Key Points
  • Conditions can be tested using the ok function to specify a condition expected to be true and an error statement to print if the condition evaluates to false.

  • Failed tests should not cascade causing other tests to fail or, even worse, crash. Calls to skip can be used to alert users that a test failed causing other tests not to be executed.

  • The START_TEST macro is used to create the entry point for the test. The input to this macro must be the same as the name of the file containing the test.

  • Tests should minimize their output. Most tests need nothing more than the output generated from failed calls to ok.

Test Makefile

Tests use a Makefile.in similar to that used for DLLs. Count's Makefile is located in $WINEPATH/dlls/count/tests/Makefile.in.

Note
Key Points
  • This Makefile.in must also be added to the build system. Again, this is accomplished using git add, followed by running ./tools/make_makefiles, and then ./config.status; make depend. The test can then be built using make -C $WINEPATH/dlls/count/tests.

Running Tests

The count tests can now be run within Wine:

$WINEPATH/wine $WINEPATH/dlls/count/tests/count_test.exe.so