blob: b23a7c07bfa8ca87e81529bf838842cf2366aa2a [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Creates an import library from an import description file."""
import ast
import logging
import optparse
import os
import os.path
import shutil
import subprocess
import sys
import tempfile
_USAGE = """\
Usage: %prog [options] [imports-file]
Creates an import library from imports-file.
Note: this script uses the microsoft assembler (ml.exe) and the library tool
(lib.exe), both of which must be in path.
; This file is autogenerated by, do not edit.
; Stubs to provide mangled names to lib.exe for the
; correct generation of import libs.
; This file is autogenerated by, do not edit.
; Export declarations for generating import libs.
_LOGGER = logging.getLogger()
class _Error(Exception):
class _ImportLibraryGenerator(object):
def __init__(self, temp_dir):
self._temp_dir = temp_dir
def _Shell(self, cmd, **kw):
ret =, **kw)'Running "%s" returned %d.', cmd, ret)
if ret != 0:
raise _Error('Command "%s" returned %d.' % (cmd, ret))
def _ReadImportsFile(self, imports_file):
# Slurp the imports file.
return ast.literal_eval(open(imports_file).read())
def _WriteStubsFile(self, import_names, output_file):
for name in import_names:
output_file.write('%s PROC\n' % name)
output_file.write('%s ENDP\n' % name)
def _WriteDefFile(self, dll_name, import_names, output_file):
output_file.write('NAME %s\n' % dll_name)
for name in import_names:
name = name.split('@')[0]
output_file.write(' %s\n' % name)
def _CreateObj(self, dll_name, imports):
"""Writes an assembly file containing empty declarations.
For each imported function of the form:
AddClipboardFormatListener@4 PROC
AddClipboardFormatListener@4 ENDP
The resulting object file is then supplied to lib.exe with a .def file
declaring the corresponding non-adorned exports as they appear on the
exporting DLL, e.g.
In combination, the .def file and the .obj file cause lib.exe to generate
an x86 import lib with public symbols named like
"__imp__AddClipboardFormatListener@4", binding to exports named like
All of this is perpetrated in a temporary directory, as the intermediate
artifacts are quick and easy to produce, and of no interest to anyone
after the fact."""
# Create an .asm file to provide stdcall-like stub names to lib.exe.
asm_name = dll_name + '.asm''Writing asm file "%s".', asm_name)
with open(os.path.join(self._temp_dir, asm_name), 'wb') as stubs_file:
self._WriteStubsFile(imports, stubs_file)
# Invoke on the assembler to compile it to .obj.
obj_name = dll_name + '.obj'
cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name]
self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull))
return obj_name
def _CreateImportLib(self, dll_name, imports, architecture, output_file):
"""Creates an import lib binding imports to dll_name for architecture.
On success, writes the import library to output file.
obj_file = None
# For x86 architecture we have to provide an object file for correct
# name mangling between the import stubs and the exported functions.
if architecture == 'x86':
obj_file = self._CreateObj(dll_name, imports)
# Create the corresponding .def file. This file has the non stdcall-adorned
# names, as exported by the destination DLL.
def_name = dll_name + '.def''Writing def file "%s".', def_name)
with open(os.path.join(self._temp_dir, def_name), 'wb') as def_file:
self._WriteDefFile(dll_name, imports, def_file)
# Invoke on lib.exe to create the import library.
# We generate everything into the temporary directory, as the .exp export
# files will be generated at the same path as the import library, and we
# don't want those files potentially gunking the works.
dll_base_name, ext = os.path.splitext(dll_name)
lib_name = dll_base_name + '.lib'
cmdline = [
'/machine:%s' % architecture,
'/def:%s' % def_name,
'/out:%s' % lib_name
if obj_file:
self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull))
# Copy the .lib file to the output directory.
shutil.copyfile(os.path.join(self._temp_dir, lib_name), output_file)'Created "%s".', output_file)
def CreateImportLib(self, imports_file, output_file):
# Read the imports file.
imports = self._ReadImportsFile(imports_file)
# Creates the requested import library in the output directory.
self._CreateImportLib(imports['dll_name'], imports['imports'],
imports.get('architecture', 'x86'), output_file)
def main():
parser = optparse.OptionParser(usage=_USAGE)
'-o', '--output-file', help='Specifies the output file path.')
help='Keep the temporary directory.')
'-v', '--verbose', action='store_true', help='Verbose logging.')
options, args = parser.parse_args()
if len(args) != 1:
parser.error('You must provide an imports file.')
if not options.output_file:
parser.error('You must provide an output file.')
options.output_file = os.path.abspath(options.output_file)
if options.verbose:
temp_dir = tempfile.mkdtemp()'Created temporary directory "%s."', temp_dir)
# Create a generator and create the import lib.
generator = _ImportLibraryGenerator(temp_dir)
ret = generator.CreateImportLib(args[0], options.output_file)
except Exception, e:
_LOGGER.exception('Failed to create import lib.')
ret = 1
if not options.keep_temp_dir:
shutil.rmtree(temp_dir)'Deleted temporary directory "%s."', temp_dir)
return ret
if __name__ == '__main__':