Create a spec file

From ReactOS Wiki
Jump to: navigation, search

Prerequisites

  • Python 2.7 (and at least the ability to read it, and modify the code shown here to suit your needs).
  • pefile (Very simple / nice to use PE file library, if you don't have it, install with: pip install pefile)
  • A building checkout of ROS (Example source is in R:/src)
  • A dll to base the stubs on (Stored in d:/dll/w2k3/vssapi.dll for this example)

Getting started

As a first step, let's get accustomed to pefile a bit, and examine the exports from our target dll. The usage examples of pefile show how to iterate exports.

Let's apply that (and don't forget to change the path to your local copy of the dll):

import pefile

pe = pefile.PE('d:/dll/w2k3/vssapi.dll')
for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
    print entry.ordinal, entry.name

This lists all functions exported, and their ordinal. A small snippet of the output:

8 ??0CVssJetWriter@@QAE@XZ
9 ??0CVssWriter@@QAE@XZ
10 ??1CVssJetWriter@@UAE@XZ
11 ??1CVssWriter@@UAE@XZ
12 ?AreComponentsSelected@CVssWriter@@IBG_NXZ
.. snip ..

If you are wondering about the '?' and '@' in the function names: These are so called 'decorated' or 'mangled' exports, which means that the compiler encoded extra info in the function name.

All right, we have a list of all exports, so let's continue to the next step.

Adding a new dll with CMake

  1. Navigate to R:/src/win32
  2. Create a new folder, and name it the same as your target dll (vssapi in the example, substitute with your target).
  3. Open R:/src/win32/CMakeLists.txt with a texteditor, and add a line: add_subdirectory(vssapi) (and again, substitute it). This tells cmake to also include the directory named vssapi when looking for stuff to include.
  4. Open the new folder (R:/src/win32/vssapi), and create a new (empty) file: R:/src/win32/vssapi/vssapi.spec (where you again substitute vssapi with your target).
  5. Create another file: R:/src/win32/vssapi/CMakeLists.txt, with content:

include_directories(${REACTOS_SOURCE_DIR}/include/reactos/wine)
add_definitions(-D__WINESRC__)
spec2def(vssapi.dll vssapi.spec ADD_IMPORTLIB)

list(APPEND SOURCE
    ${CMAKE_CURRENT_BINARY_DIR}/vssapi_stubs.c)

add_library(vssapi SHARED
    ${SOURCE}
    ${CMAKE_CURRENT_BINARY_DIR}/vssapi.def)

set_module_type(vssapi win32dll)
target_link_libraries(vssapi wine)
add_importlibs(vssapi msvcrt kernel32 ntdll)
add_cd_file(TARGET vssapi DESTINATION reactos/system32 FOR all)

And of course, substitute every occurrence of vssapi with your dll name. This file tells cmake to use the vssapi.spec file to create a dll, and that the dll should be stored in reactos/system32. (And some more stuff, but that's a story for another day.)

Now we should be able to build an empty dll, so re-run cmake (just building anything else should work, cmake will detect the change in r:/src/dll/win32/CMakeLists.txt).

After re-running cmake, there should be a new target: vssapi.dll, so let's build that.

Filling in the blanks

So, to recap: we can build an empty dll, and we can list all exports from a dll. Let's combine this to build a dll with stubbed functions!

Looking at a few .spec files now, the format seems to be very simple: [ordinal or @] [stub|stdcall] [functionname] So all we have to do is print 'stub' inbetween the ordinal and name we already printed, and that should be it (see The finished script for the result)! Now we can paste the output from the script in the .spec file, rebuild the dll and we have a stubbed dll!

The finished script


import pefile

pe = pefile.PE('d:/dll/w2k3/vssapi.dll')
for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
    print entry.ordinal, 'stub', entry.name

Bonus feature

And since everyone likes neat stuff, how about we order the exports by ordinal?

import pefile

pe = pefile.PE('d:/dll/w2k3/vssapi.dll')
exports = []
for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
    exports.append(entry)
for export in sorted(exports, key=lambda exp: exp.ordinal):
    print export.ordinal, 'stub', export.name


A new do-it all script, thatll output a CMakeLists.txt and spec file (mkspec.py)

import argparse
import os
import sys
try:
    import pefile
except ImportError:
    print 'You need to install the pefile module from https://github.com/erocarrera/pefile'
    print 'Or install it with `pip install pefile`'
    exit(1)

# Those functions need to be exported with -private, and without ordinal.
PRIVATE_EXPORTS = {
    'DllCanUnloadNow': 'DllCanUnloadNow()',
    'DllGetClassObject': 'DllGetClassObject(ptr ptr ptr)',
    'DllRegisterServer': 'DllRegisterServer()',
    'DllUnregisterServer': 'DllUnregisterServer()'
}

CMAKE_TEMPLATE = """
include_directories(${{REACTOS_SOURCE_DIR}}/include/reactos/wine)
add_definitions(-D__WINESRC__)
spec2def({dll}.dll {dll}.spec ADD_IMPORTLIB)

list(APPEND SOURCE
    ${{CMAKE_CURRENT_BINARY_DIR}}/{dll}_stubs.c)

add_library({dll} SHARED
    ${{SOURCE}}
    ${{CMAKE_CURRENT_BINARY_DIR}}/{dll}.def)

set_module_type({dll} win32dll)
target_link_libraries({dll} wine)
add_importlibs({dll} msvcrt kernel32 ntdll)
add_cd_file(TARGET {dll} DESTINATION reactos/system32 FOR all)
"""

EXPORT_DIR_ID = pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT']

class SpecGen:
    def __init__(self, target):
        self.target = target
        self.basename = os.path.splitext(os.path.basename(target))[0]
        self.pe = pe = pefile.PE(target, fast_load = True)
        self.exports = None

    def _update(self):
        if not hasattr(self.pe, 'DIRECTORY_ENTRY_EXPORT'):
            self.pe.parse_data_directories(EXPORT_DIR_ID)
        exports = []
        for entry in self.pe.DIRECTORY_ENTRY_EXPORT.symbols:
            exports.append(entry)
        self.exports = sorted(exports,key=lambda export: export.ordinal)

    def _write_spec(self, file):
        if self.exports is None:
            self._update()
        for export in self.exports:
            if not export.name:
                file.write('{ord} stub -noname Ordinal{ord}\n'.format(ord=export.ordinal))
            elif export.name in PRIVATE_EXPORTS:
                file.write('@ stub -private {}\n'.format(export.name))
            else:
                file.write('{} stub {}\n'.format(export.ordinal, export.name))

    def _write_cmake(self, file):
        file.write(CMAKE_TEMPLATE.format(dll = self.basename))

    def write_spec(self, dir):
        if dir is None:
            spec = sys.stdout
        else:
            print 'Writing', self.basename + '.spec'
            spec = open(os.path.join(dir, self.basename + '.spec'), 'w')
        self._write_spec(spec)

    def write_cmake(self, dir):
        if dir is None:
            cmake = sys.stdout
        else:
            print 'Writing CMakeLists.txt for', self.basename
            cmake = open(os.path.join(dir, 'CMakeLists.txt'), 'w')
        self._write_cmake(cmake)

def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('--out', help='specify the output dir')
    parser.add_argument('--skip_spec', action='store_true')
    parser.add_argument('--skip_cmake', action='store_true')
    args, files = parser.parse_known_args(args)
    if len(files) == 0:
        print 'Please specify atleast one file to dump'
        exit(1)
    for file in files:
        gen = SpecGen(file)
        dir = os.path.join(args.out, gen.basename) if args.out else None
        if dir and not os.path.isdir(dir):
            os.makedirs(dir)
        if not args.skip_spec:
            gen.write_spec(dir)
        if not args.skip_cmake:
            gen.write_cmake(dir)

if __name__ == '__main__':
    main(sys.argv[1:])