How to replace Windows DLL with Wine DLL

This article describes tips for building a DLL for Windows applications running under Wine on Linux. Although many Windows applications will run fine with the original Windows DLLs, some may require parts of them to be adapted to Linux. Here you will find some tips on how to implement a DLL, which replaces the original DLL, and calls native Linux API functions and libraries.

In my specific case I wanted to run a Windows application on Linux. Everything worked fine with Wine, but hardware attached to USB could not be accessed because there is no support for Windows drivers in Wine. So I decided to replace the application's Windows DLL for USB communication with Wine DLL, which uses native Linux library.

I had application's sources available. If you don''t have them, a good DLL documentation with function declarations is a must.

Since there are other pages available which describe the procedure, this page emphasizes the information which I did not find elsewhere, but turned out to be important.

1. Which calling convention is used?

This piece of information is very important - find out what is the calling convention for your DLL. If you are going to replace a DLL, which is a part of Windows API, then the __stdcall calling convention is used. If you are going to replace a custom DLL, which comes with an application, then exported functions may be compiled with __stdcall or __cdecl calling convention. If you don't have source code, then you'll need good documentation for exported functions. If this piece of information is missing, you can try it first with one calling convention and then the other. If one works and the other one crashes application, you know which one is right.

Note: Functions without parameters work with both calling conventions, so test it on functions with parameters.

As described in the next steps of this tutorial, calling convention is specified in spec file. If the standard calling convention is used, you must also specify it in C code.

Tip: If you have sources, it is easy to find out the calling convention. If macros are used for function

declarations and you are not sure how do they expand, replace gcc option -o <objFile> with option -E and the preprocessed source will be printed to stdout.

2. The spec file

Spec files are needed for building a Wine DLL. You can write one manually, or use the winedump utilty to generate it. In this section we'll create a spec file for the following exported functions:

    DWORD usbOpenUSB(const char *fName, int usbNumber);
    DWORD usbOpenLog(const char *fName);
    DWORD usbLog(const char *text);
    DWORD usbLogLn(const char *text);
    DWORD usbLogInt(int value);
    DWORD usbCloseLog(void);

Creating the spec file manually

If you don't like the generated file or would like to understand it, look into man page on the web or type:

man winebuild

and scroll down a few screens.

Example of the spec file contents:

    @ cdecl usbOpenUSB(str long)
    @ cdecl usbOpenLog(str)
    @ cdecl usbLog(str)
    @ cdecl usbLogInt(long)
    @ cdecl usbLogLn(str)
    @ cdecl usbCloseLog()

In this example the __cdecl calling convention is used.

Generating the spec file

For succesful generation of the spec file and C sources, the winedump utility requires the Windows DLL binary and C source code with function headers (either H or C files). The folder with sources must be specified with the -I option. This option is mandatory for source generation. In the following example we have source files in the current directory (option -I . is used), and we want to TRACE arguments (option -t). We know that the original DLL is using C calling convention (option -C):

winedump spec mycalc.dll -I . -t -C

What we've got is a good starting point! For other options of the utility type man winedump.

Summary:

Input:

- Source of the original dll

- the original DLL binary

Output:

- spec file

- C source and header files

- Makefile.in

3. The C source

It is time to write bodies of functions. For this example there will be just simple writing to log file:

DWORD __cdecl usbOpenUSB(char * text, int usbNumber)
{
    TRACE("((char *)%s,(int)%d): stub\n",text, usbNumber);
    return (DWORD) 0;
}
DWORD __cdecl usbOpenLog(char * fName)
{
    TRACE("((char *)%s): stub\n",fName);
    fp = fopen(fName, "w");
    return (DWORD) 0;
}
DWORD __cdecl usbLog(char * text)
{
    TRACE("((char *)%s): stub\n",text);
    fprintf(fp, "%s", text);
    return (DWORD) 0;
}
DWORD __cdecl usbLogInt(unsigned int value)
{
    TRACE("((unsigned int)%d): stub\n", value);
    fprintf(fp, "%d\n", value);
    return (DWORD) 0;
}
DWORD __cdecl usbLogLn(char * text)
{
    TRACE("((char *)%s): stub\n",text);
    fprintf(fp, "%s\n", text);
    return (DWORD) 0;
}
DWORD __cdecl usbCloseLog(void)
{
    TRACE("(void): stub\n");
    fclose(fp);
    return (DWORD) 0;
}

4. Building

First of all we'll need Wine sources, because Wine headers and libraries are required for building the DLL. To get the Wine sources execute the following command:

    git clone git://source.winehq.org/git/wine.git wine-git

To get the required version of Wine type:

    git checkout wine-1.3.35

Then in wine root diretory type:

    ./configure  
    make
    # make sure you don't already have wine installed from some other source,
    # for example package manager
    make install  

If you have problems building your DLL, go to one of folders of wine DLLs, for example wine-git/dlls/wow32/. See the makefile found there or type:

    make clean
    make

to see the compilation commands.

Example of make output for wow32:

gcc -c -I. -I. -I../../include -I../../include -D__WINESRC__ -D_REENTRANT -fPIC -Wall -pipe -fno-strict-aliasing -Wdeclaration-after-statement -Wempty-body -Wstrict-prototypes -Wtype-limits -Wwrite-strings -fno-omit-frame-pointer -Wpointer-arith -I/usr/include/freetype2 -g -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -o wow_main.o wow_main.c

../../tools/winegcc/winegcc -B../../tools/winebuild --sysroot=../.. -fasynchronous-unwind-tables -shared ./wow32.spec wow_main.o -o wow32.dll.so -lkernel ../../libs/port/libwine_port.a

Replace paths relative to your working directory and names of 'wow32' files with the names of your files.

Note: There are actually two more lines building a 'fake' version of the DLL, but I don't know why they are needed.

5. Tell Wine where to look for the DLL

If the original DLL is located in the application folder, and we want to keep also the new DLL in the application folder, we have to remove the original DLL first, and then copy the generated DLL to the application folder. Then we have two options:

1. Rename the new comm.dll.so --> comm.dll

For this to work the generated function WinMain must not return

FALSE for DLL_WINE_PREATTACH.

If it does, the output of Wine trace looks like:

trace:module:load_builtin_callback loaded icusbcomm.dll 0x3dcbfc0 0x7ddc0000
trace:module:MODULE_InitDLL (0x7ddc0000 L"icusbcomm.dll",WINE_PREATTACH,(nil)) - CALL
trace:module:MODULE_InitDLL (0x7ddc0000,WINE_PREATTACH,(nil)) - RETURN 0
trace:module:load_dll L"Z:\\home\\markok\\tmp\\WinRel9\\icusbcomm.dll" pre-attach returned FALSE, preferring native

The disadvantage of this approach is inability to use debugging channels, see WINEDEBUG in the next option.

2. This activation of the dll is preferred. Set WINEDLLPATH to point to the application folder. For example:

    cd <application folder>  # exe and dlls are located here
    export WINEDLLPATH=`pwd`
    wine <app.exe>

Renaming is not needed in this case, so our library has extension .dll.so. If there are problems loading the DLL, execute:

export WINEDEBUG=+module

and run the application. Check the output.

6. Remarks

In my DLL I've opened a file in the application's working directory - only file name was specified, no path, for example:

    fp = fopen("myfile.log", "w");

On Linux the file was unexpectedly opened in the same directory were Windows application was started, not in the directory set as working directory by the application, as it was the case with the original Windows DLL. That's quite an important difference between original Windows DLL and Linux wrapper DLL.

7. Troubleshooting

If the library is not loaded, the first step should be turning on Wine debug channels with:

export WINEDEBUG=warn+all

One of the reasons may be dependency on other shared libraries, which may not be installed. Use the ldd utilitiy to see dependencies.

Marko Klopčič

Page created: 2012-01-07

Last update: 2012-05-13