With VS2005, I want to create a DLL and automatically export all symbols without adding __declspec(dllexport) everywhere, and without hand-creating .def files. Is there a way to do this?
With VS2005, I want to create a DLL and automatically export all symbols without adding __declspec(dllexport) everywhere, and without hand-creating .def files. Is there a way to do this?
It can be done...
The way we do it here is to use the /DEF option of the linker to pass a "module definition file" containing a list of our exports. I see from your question that you know about these files. However, we do not do it by hand. The list of exports itself is created by the dumpbin /LINKERMEMBER command, and manipulating the output via a simple script to the format of a module definition file.
It is a lot of work to setup, but it allows us to compile code created without dllexport declarations for Unix on Windows.
I want to create a DLL and automatically export all symbols without adding __declspec(dllexport) everywhere and without hand-creating .def files. Is threre a way to do this?
This is a late answer, but it provides the details for Maks's answer in Section (2). It also avoids scripts and uses a C++ program called dump2def. The source code for dump2def is below.
Finally, the steps below assume you are working from a Visual Studio Developer Prompt, which is a Windows Terminal where vcvarsall.bat has been run. You need to ensure the build tools like cl.exe, lib.exe, link.exe and nmake.exe are on-path.
More info about this is provided by link:
The instruction below use:
static.lib - static library archive (*.a file on Linux)dynamic.dll - dynamic library (*.so file on Linux)import.lib - dynamic library (import library on Windows)Also note that though you are exporting everything from the DLL, clients still must use declspec(dllimport) on all symbols (classes, functions and data) that they use. Also see on MSDN.
First, take your objects and create a static archive:
AR = lib.exe
ARFLAGS = /nologo
CXX_SRCS = a.cpp b.cpp c.cpp ...
LIB_OBJS = a.obj b.obj c.obj ...
static.lib: $(LIB_OBJS)
$(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@
Second, run dumpbin.exe /LINKERMEMEBER on the archive to create a *.dump file:
dynamic.dump:
dumpbin /LINKERMEMBER static.lib > dynamic.dump
Third, run dump2def.exe on the *.dump file to produce the *.def file. The source code for dump2def.exe is below.
dynamic.def: static.lib dynamic.dump
dump2def.exe dynamic.dump dynamic.def
Fourth, build the DLL:
LD = link.exe
LDFLAGS = /OPT:REF /MACHINE:X64
LDLIBS = kernel32.lib
dynamic.dll: $(LIB_OBJS) dynamic.def
$(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@
/IGNORE:4102 is used to avoid this warning. It is expected in this case:
dynamic.def : warning LNK4102: export of deleting destructor 'public: virtual v
oid * __ptr64 __cdecl std::exception::`scalar deleting destructor'(unsigned int)
__ptr64'; image may not run correctly
When the dynamic.dll recipe is invoked, it creates a dynamic.lib import file and dynamic.exp file, too:
> cls && nmake /f test.nmake dynamic.dll
...
Creating library dynamic.lib and object dynamic.exp
And:
C:\Users\Test\testdll>dir *.lib *.dll *.def *.exp
Volume in drive C is Windows
Volume Serial Number is CC36-23BE
Directory of C:\Users\Test\testdll
01/06/2019 08:33 PM 71,501,578 static.lib
01/06/2019 08:33 PM 11,532,052 dynamic.lib
Directory of C:\Users\Test\testdll
01/06/2019 08:35 PM 5,143,552 dynamic.dll
Directory of C:\Users\Test\testdll
01/06/2019 08:33 PM 1,923,070 dynamic.def
Directory of C:\Users\Test\testdll
01/06/2019 08:35 PM 6,937,789 dynamic.exp
5 File(s) 97,038,041 bytes
0 Dir(s) 139,871,186,944 bytes free
Gluing it together here is what the Nmake makefile looks like. It is part of a real Nmake file:
all: test.exe
test.exe: pch.pch static.lib $(TEST_OBJS)
$(LD) $(LDFLAGS) $(TEST_OBJS) static.lib $(LDLIBS) /out:$@
static.lib: $(LIB_OBJS)
$(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@
dynamic.map:
$(LD) $(LDFLAGS) /DLL /MAP /MAPINFO:EXPORTS $(LIB_OBJS) $(LDLIBS) /out:dynamic.dll
dynamic.dump:
dumpbin.exe /LINKERMEMBER static.lib /OUT:dynamic.dump
dynamic.def: static.lib dynamic.dump
dump2def.exe dynamic.dump
dynamic.dll: $(LIB_OBJS) dynamic.def
$(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@
clean:
$(RM) /F /Q pch.pch $(LIB_OBJS) pch.obj static.lib $(TEST_OBJS) test.exe *.pdb
And here is the source code for dump2def.exe:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
typedef std::set<std::string> SymbolMap;
void PrintHelpAndExit(int code)
{
std::cout << "dump2def - create a module definitions file from a dumpbin file" << std::endl;
std::cout << " Written and placed in public domain by Jeffrey Walton" << std::endl;
std::cout << std::endl;
std::cout << "Usage: " << std::endl;
std::cout << " dump2def <infile>" << std::endl;
std::cout << " - Create a def file from <infile> and write it to a file with" << std::endl;
std::cout << " the same name as <infile> but using the .def extension" << std::endl;
std::cout << " dump2def <infile> <outfile>" << std::endl;
std::cout << " - Create a def file from <infile> and write it to <outfile>" << std::endl;
std::exit(code);
}
int main(int argc, char* argv[])
{
// ******************** Handle Options ******************** //
// Convenience item
std::vector<std::string> opts;
for (size_t i=0; i<argc; ++i)
opts.push_back(argv[i]);
// Look for help
std::string opt = opts.size() < 3 ? "" : opts[1].substr(0,2);
if (opt == "/h" || opt == "-h" || opt == "/?" || opt == "-?")
PrintHelpAndExit(0);
// Add <outfile> as needed
if (opts.size() == 2)
{
std::string outfile = opts[1];
std::string::size_type pos = outfile.length() < 5 ? std::string::npos : outfile.length() - 5;
if (pos == std::string::npos || outfile.substr(pos) != ".dump")
PrintHelpAndExit(1);
outfile.replace(pos, 5, ".def");
opts.push_back(outfile);
}
// Check or exit
if (opts.size() != 3)
PrintHelpAndExit(1);
// ******************** Read MAP file ******************** //
SymbolMap symbols;
try
{
std::ifstream infile(opts[1].c_str());
std::string::size_type pos;
std::string line;
// Find start of the symbol table
while (std::getline(infile, line))
{
pos = line.find("public symbols");
if (pos == std::string::npos) { continue; }
// Eat the whitespace after the table heading
infile >> std::ws;
break;
}
while (std::getline(infile, line))
{
// End of table
if (line.empty()) { break; }
std::istringstream iss(line);
std::string address, symbol;
iss >> address >> symbol;
symbols.insert(symbol);
}
}
catch (const std::exception& ex)
{
std::cerr << "Unexpected exception:" << std::endl;
std::cerr << ex.what() << std::endl;
std::cerr << std::endl;
PrintHelpAndExit(1);
}
// ******************** Write DEF file ******************** //
try
{
std::ofstream outfile(opts[2].c_str());
// Library name, cryptopp.dll
std::string name = opts[2];
std::string::size_type pos = name.find_last_of(".");
if (pos != std::string::npos)
name.erase(pos);
outfile << "LIBRARY " << name << std::endl;
outfile << "DESCRIPTION \"Crypto++ Library\"" << std::endl;
outfile << "EXPORTS" << std::endl;
outfile << std::endl;
outfile << "\t;; " << symbols.size() << " symbols" << std::endl;
// Symbols from our object files
SymbolMap::const_iterator it = symbols.begin();
for ( ; it != symbols.end(); ++it)
outfile << "\t" << *it << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "Unexpected exception:" << std::endl;
std::cerr << ex.what() << std::endl;
std::cerr << std::endl;
PrintHelpAndExit(1);
}
return 0;
}
I've written a small program to parse the output of "dumpbin /linkermember" on the .lib file. I have upwards of 8,000 function references to export from one DLL.
The problem with doing it on a DLL is that you have to link the DLL without the exported definitions once to create the .lib file, then generate the .def which means you now have to relink the DLL again with the .def file to actually have the references exported.
Working with static libraries is easier. Compile all your sources into static libs, run dumbin, generate a .def with your little program, then link the libs together into a DLL now that the export names are available.
Unfortunately my company won't allow me to show you the source. The work involved is recognizing which "public symbols" in the dump output are not needed in your def file. You have to throw away a lot of those references, NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp*, etc.
Perhaps somebody finds useful my Python script for converting .dump to .def.
import sys, os
functions = []
startPoint = False
# Exclude standard API like sprintf to avoid multiple definition link error
excluded_functions = [ 'sprintf', 'snprintf', 'sscanf', 'fprintf' ]
if len(sys.argv) < 2:
print('Usage: %s <Input .dump file> <Output .def file>.' % sys.argv[0])
print('Example: %s myStaticLib.dump exports.def' % sys.argv[0])
sys.exit(1)
print('%s: Processing %s to %s' % (sys.argv[0], sys.argv[1], sys.argv[2]))
fin = open(sys.argv[1], 'r')
lines = fin.readlines()
fin.close()
# Reading
for l in lines:
l_str = l.strip()
if (startPoint == True) and (l_str == 'Summary'): # end point
break
if (startPoint == False) and ("public symbols" in l_str):
startPoint = True
continue
if (startPoint == True) and l_str is not '':
funcName = l_str.split(' ')[-1]
if funcName not in excluded_functions:
functions.append(" " + funcName)
# Writing
fout = open(sys.argv[2], 'w')
fout.write('EXPORTS\n')
for f in functions:
fout.write('%s\n' % f)
fout.close()
With this script you can get the .def file for your .lib in two steps:
dumpbin /LINKERMEMBER:1 myStaticLib.lib > myExports.dump
python dump2def.py myExports.dump myExports.def
No, you will need a macro that resolves to __declspec(dllexport) when it's included by the .cpp file that implements the exported functions, and resolves to __declspec(dllimport) otherwise.