Added spec generation tool specmaker.
This commit is contained in:
parent
8d91b501dd
commit
c7a3fec5be
|
@ -6966,6 +6966,7 @@ scheduler/Makefile
|
|||
server/Makefile
|
||||
tools/Makefile
|
||||
tools/cvdump/Makefile
|
||||
tools/specmaker/Makefile
|
||||
tools/winebuild/Makefile
|
||||
tools/winelauncher
|
||||
tools/wmc/Makefile
|
||||
|
@ -7209,6 +7210,7 @@ scheduler/Makefile
|
|||
server/Makefile
|
||||
tools/Makefile
|
||||
tools/cvdump/Makefile
|
||||
tools/specmaker/Makefile
|
||||
tools/winebuild/Makefile
|
||||
tools/winelauncher
|
||||
tools/wmc/Makefile
|
||||
|
|
|
@ -1284,6 +1284,7 @@ scheduler/Makefile
|
|||
server/Makefile
|
||||
tools/Makefile
|
||||
tools/cvdump/Makefile
|
||||
tools/specmaker/Makefile
|
||||
tools/winebuild/Makefile
|
||||
tools/winelauncher
|
||||
tools/wmc/Makefile
|
||||
|
|
|
@ -11,11 +11,13 @@ C_SRCS = makedep.c fnt2bdf.c bin2res.c
|
|||
|
||||
SUBDIRS = \
|
||||
cvdump \
|
||||
specmaker \
|
||||
winebuild \
|
||||
wmc \
|
||||
wrc
|
||||
|
||||
INSTALLSUBDIRS = \
|
||||
specmaker \
|
||||
winebuild \
|
||||
wmc \
|
||||
wrc
|
||||
|
@ -26,7 +28,7 @@ EXTRASUBDIRS = \
|
|||
winapi_check/win32 \
|
||||
wineconf.libs
|
||||
|
||||
all: $(PROGRAMS) winebuild wmc wrc
|
||||
all: $(PROGRAMS) specmaker winebuild wmc wrc
|
||||
|
||||
@MAKE_RULES@
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Makefile
|
||||
specmaker
|
|
@ -0,0 +1,35 @@
|
|||
DEFS = -D__WINE__
|
||||
TOPSRCDIR = @top_srcdir@
|
||||
TOPOBJDIR = ../..
|
||||
SRCDIR = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
PROGRAMS = specmaker
|
||||
MODULE = none
|
||||
|
||||
C_SRCS = \
|
||||
dll.c \
|
||||
main.c \
|
||||
misc.c \
|
||||
msmangle.c \
|
||||
output.c \
|
||||
search.c \
|
||||
symbol.c
|
||||
|
||||
all: $(PROGRAMS)
|
||||
|
||||
@MAKE_RULES@
|
||||
|
||||
specmaker: $(OBJS)
|
||||
$(CC) $(CFLAGS) -o specmaker $(OBJS) $(LDFLAGS)
|
||||
|
||||
install:: $(PROGRAMS)
|
||||
[ -d $(bindir) ] || $(MKDIR) $(bindir)
|
||||
$(INSTALL_PROGRAM) specmaker $(bindir)/specmaker
|
||||
$(INSTALL_PROGRAM) function_grep.pl $(bindir)/function_grep.pl
|
||||
|
||||
uninstall::
|
||||
$(RM) $(bindir)/specmaker
|
||||
$(RM) $(bindir)/function_grep.pl
|
||||
|
||||
### Dependencies:
|
|
@ -0,0 +1,560 @@
|
|||
Specmaker - A Wine DLL tool
|
||||
---------------------------
|
||||
|
||||
Background
|
||||
----------
|
||||
|
||||
Most of the functions available in Windows, and in Windows applications, are
|
||||
made available to applications from DLL's. Wine implements the Win32 API by
|
||||
providing replacement's for the essential Windows DLLs in the form of Unix
|
||||
shared library (.so) files, and provides a tool, winebuild, to allow Winelib
|
||||
applications to link to functions exported from shared libraries/DLLs.
|
||||
|
||||
The first thing to note is that there are many DLLs that aren't yet
|
||||
implemented in Wine. Mostly this doesn't present a problem because the native
|
||||
Win32 versions of lots of DLLs can be used without problems, at least on
|
||||
x86 platforms. However, one of Wine's goals is the eventual replacement of
|
||||
every essential O/S DLL so that the whole API is implemented. This not only
|
||||
means that a copy of the real O/S is not needed, but also that non-x86
|
||||
platforms can run most Win32 programs after recompiling.
|
||||
|
||||
The second thing to note is that applications commonly use their own or 3rd
|
||||
party DLLs to provide functionality. In order to call these functions with
|
||||
a Winelib program, some 'glue' is needed. This 'glue' comes in the form of
|
||||
a .spec file. The .spec file, along with some dummy code, is used to create
|
||||
a Wine .so corresponding to the Windows DLL. The winebuild program can then
|
||||
resolve calls made to DLL functions to call your dummy DLL. You then tell
|
||||
Wine to only use the native Win32 version of the DLL, and at runtime your
|
||||
calls will be made to the Win32 DLL. If you want to reimplement the dll,
|
||||
you simply add the code for the DLL calls to your stub .so, and then tell
|
||||
Wine to use the .so version instead [1].
|
||||
|
||||
These two factors mean that if you are:
|
||||
|
||||
A: Reimplementing a Win32 DLL for use within Wine, or
|
||||
B: Compiling a Win32 application with Winelib that uses x86 DLLs
|
||||
|
||||
Then you will need to create a .spec file (amongst other things). If you
|
||||
won't be doing either of the above, then you won't need specmaker.
|
||||
|
||||
Creating a .spec file is a labour intensive task during which it is easy
|
||||
to make a mistake. The idea of specmaker is to automate this task and create
|
||||
the majority of the support code needed for your DLL. In addition you can
|
||||
have specmaker create code to help you reimplement a DLL, by providing
|
||||
tracing of calls to the DLL, and (in some cases) automatically determining
|
||||
the parameters, calling conventions, and return values of the DLLs functions.
|
||||
|
||||
You can think of specmaker as somewhat similar to the IMPLIB tool when
|
||||
only its basic functionality is used.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Specmaker is a command line tool. Running it with no arguments or passing
|
||||
it '-h' on the command line lists the available options:
|
||||
|
||||
Usage: specmaker [options] -d dll
|
||||
|
||||
Options:
|
||||
-d dll Use dll for input file (mandatory)
|
||||
-h Display this help message
|
||||
-I dir Look for prototypes in 'dir' (implies -c)
|
||||
-o name Set the output dll name (default: dll)
|
||||
-c Generate skeleton code (requires -I)
|
||||
-t TRACE arguments (implies -c)
|
||||
-f dll Forward calls to 'dll' (implies -t)
|
||||
-D Generate documentation
|
||||
-C Assume __cdecl calls (default: __stdcall)
|
||||
-s num Start prototype search after symbol 'num'
|
||||
-e num End prototype search after symbol 'num'
|
||||
-q Don't show progress (quiet).
|
||||
-v Show lots of detail while working (verbose).
|
||||
|
||||
|
||||
Basic options
|
||||
-------------
|
||||
|
||||
OPTION: -d dll Use dll for input file (mandatory)
|
||||
|
||||
The -d option tells specmaker which DLL you want to create a .spec file
|
||||
for. You *must* give this option.
|
||||
|
||||
16 bit DLL's are not currently supported (Note that Winelib is intended
|
||||
only for Win32 programs).
|
||||
|
||||
OPTION: -o name Set the output dll name (default: dll)
|
||||
|
||||
By default, if specmaker is run on DLL 'foo', it creates files called
|
||||
'foo.spec', 'foo_main.c' etc, and prefixes any functions generated
|
||||
with 'FOO_'. If '-o bar' is given, these will become 'bar.spec',
|
||||
'bar_main.c' and 'BAR_' respectively.
|
||||
|
||||
This option is mostly useful when generating a forwarding DLL. See below
|
||||
for more information.
|
||||
|
||||
OPTION: -q Don't show progress (quiet).
|
||||
-v Show lots of detail while working (verbose).
|
||||
|
||||
There are 3 levels of output while specmaker is running. The default level,
|
||||
when neither -q or -v are given, prints the number of exported functions
|
||||
found in the dll, followed by the name of each function as it is processed,
|
||||
and a status indication of whether it was processed OK. With -v given, a
|
||||
lot of information is dumped while specmaker works: this is intended to help
|
||||
debug any problems. Giving -q means nothing will be printed unless a fatal
|
||||
error occurs, and could be used when calling specmaker from a script.
|
||||
|
||||
|
||||
OPTION: -C Assume __cdecl calls (default: __stdcall)
|
||||
|
||||
This option determines the default calling convention used by the functions
|
||||
in the DLL. If specbuild cannot determine the convention, __stdcall is
|
||||
used by default, unless this option has been given.
|
||||
|
||||
Unless -q is given, a warning will be printed for every function that
|
||||
specmaker determines the calling convention for and which does not match
|
||||
the assumed calling convention.
|
||||
|
||||
|
||||
Generating stub DLLS
|
||||
--------------------
|
||||
|
||||
If all you want to do is generate a stub DLL to allow you to link your
|
||||
Winelib application to an x86 DLL, the above options are all you need.
|
||||
|
||||
As an example, lets assume the application you are porting uses functions
|
||||
from a 3rd party dll called 'zipextra.dll', and the functions in the DLL
|
||||
use the __stdcall calling convention. Copy zipextra.dll to an empty directory,
|
||||
change to it, and run specmaker as follows:
|
||||
|
||||
specmaker -d zipextra (Note: this assumes specmaker is in your path)
|
||||
|
||||
The output will look something like the following:
|
||||
|
||||
22 exported symbols in DLL ...
|
||||
Export 1 - '_OpenZipFile' ... [Ignoring]
|
||||
Export 2 - '_UnZipFile' ... [Ignoring]
|
||||
...
|
||||
|
||||
"[Ignoring]" Just tells you that specmaker isn't trying to determine the
|
||||
parameters or return types of the functions, its just creating stubs.
|
||||
|
||||
The following files are created:
|
||||
|
||||
zipextra.spec
|
||||
This is the .spec file. Each exported function is listed as a stub:
|
||||
|
||||
@ stub _OpenZipFile
|
||||
@ stub _UnZipFile
|
||||
...
|
||||
|
||||
This means that winebuild will generate dummy code for this function. That
|
||||
doesn't concern us, because all we want is for winebuild to allow the
|
||||
symbols to be resolved. At run-time, the functions in the native DLL will
|
||||
be called; this just allows us to link.
|
||||
|
||||
zipextra_dll.h zipextra_main.c
|
||||
These are source code files containing the minimum set of code to build
|
||||
a stub DLL. The C file contains one function, ZIPEXTRA_Init, which does
|
||||
nothing.
|
||||
|
||||
Makefile.in
|
||||
This is a template for 'configure' to produce a makefile. It is designed
|
||||
for a DLL that will be inserted into the Wine source tree. If your DLL
|
||||
will not be part of Wine, or you don't wish to build it this way,
|
||||
you should look at the Wine tool 'winemaker' to generate a DLL project.
|
||||
|
||||
FIXME: winemaker could run this tool automatically when generating projects
|
||||
that use extra DLL's (*.lib in the "ADD LINK32" line in .dsp) ....
|
||||
|
||||
zipextra_install
|
||||
A shell script for adding zipextra to the Wine source tree (see below).
|
||||
|
||||
|
||||
Inserting a stub DLL into the Wine tree
|
||||
---------------------------------------
|
||||
|
||||
To build your stub DLL as part of Wine, do the following:
|
||||
|
||||
chmod a+x ./zipextra_install
|
||||
./zipextra_install <wine-path>
|
||||
cd <wine-path>
|
||||
autoconf
|
||||
./configure
|
||||
make depend && make
|
||||
make install
|
||||
|
||||
Your application can now link with the DLL.
|
||||
|
||||
NOTE: **DO NOT** submit patches to Wine for 3rd party DLLs! Building DLLs
|
||||
into your copy of the tree is just a simple way for you to link. When
|
||||
you release your application you won't be distributing the Unix .so
|
||||
anyway, just the Win32 DLL. As you update your version of Wine
|
||||
you can simply re-run the procedure above (Since no patches are
|
||||
involved, it should be pretty resiliant to changes).
|
||||
|
||||
|
||||
Advanced Options
|
||||
----------------
|
||||
|
||||
This section discusses features of specmaker that are useful to Wine Hackers
|
||||
or developers looking to reimplement a Win32 DLL for Unix. Using these
|
||||
features means you will need to be able to resolve compilation problems and
|
||||
have a general understanding of Wine programming.
|
||||
|
||||
|
||||
OPTION: -I dir Look for prototypes in 'dir' (implies -c)
|
||||
|
||||
For all advanced functionality, you must give specmaker a directoryor file that
|
||||
contains prototypes for the DLL. In the case of Windows DLLs, this could be
|
||||
either the standard include directory from your compiler, or an SDK include
|
||||
directory. If you have a text document with prototypes (such as documentation)
|
||||
that can be used also, however you may need to delete some non-code lines to
|
||||
ensure that prototypes are parsed correctly.
|
||||
|
||||
The 'dir' argument can also be a file specification (e.g. "include/*"). If
|
||||
it contains wildcards you must quote it to prevent the shell from expanding it.
|
||||
|
||||
If you have no prototypes, specify /dev/null for 'dir'. Specmaker may still
|
||||
be able to generate some working stub code for you.
|
||||
|
||||
Once you have created your DLL, if you generated code (see below), you can
|
||||
backup the DLL header file created and use it for rebuilding the DLL (you
|
||||
should remove the DLLNAME_ prefix from the prototypes to make this work). This
|
||||
allows you to add names to the function arguments, for example, so that the
|
||||
comments and prototype in the regenerated DLL will be clearer.
|
||||
|
||||
Specmaker searches for prototypes using 'grep', and then retrieves each
|
||||
prototype by calling 'function_grep.pl', a Perl script. When you pass the -v
|
||||
option on the command line, the calls to both of these programs are logged.
|
||||
This allows you to see where each function definition has come from. Should
|
||||
specmaker take an excessively long time to locate a prototype, you can check
|
||||
that it is searching the right files; you may want to limit the number of files
|
||||
searched if locating the prototype takes too long.
|
||||
|
||||
You can compile function_grep.pl for a slight increase in performance; see
|
||||
'man perlcc' for details.
|
||||
|
||||
|
||||
OPTION: -s num Start prototype search after symbol 'num'
|
||||
-e num End prototype search after symbol 'num'
|
||||
|
||||
By passing the -s or -e options you can have specmaker try to generate code
|
||||
for only some functions in your DLL. This may be used to generate a single
|
||||
function, for example, if you wanted to add functionality to an existing DLL.
|
||||
|
||||
They is also useful for debugging problems, in conjunction with -v.
|
||||
|
||||
|
||||
OPTION: -D Generate documentation
|
||||
|
||||
By default, specmaker generates a standard comment at the header of each
|
||||
function it generates. Passing this option makes specmaker output a full
|
||||
header template for standard Wine documentation, listing the parameters
|
||||
and return value of the function.
|
||||
|
||||
|
||||
OPTION: -c Generate skeleton code (requires -I)
|
||||
|
||||
This option tells specmaker that you want to create function stubs for
|
||||
each function in the DLL. This is the most basic level of code generation.
|
||||
As specmaker reads each exported symbol from the source DLL, it first tries
|
||||
to demangle the name. If the name is a C++ symbol, the arguments, class and
|
||||
return value are all encoded into the symbol name. Specmaker converts this
|
||||
information into a C function prototype. If this fails, the file(s) specified
|
||||
in the -I argument are scanned for a function prototype. If one is found it
|
||||
is used for the next step of the process, code generation.
|
||||
|
||||
Note: C++ name demangling is currently under development. Since the algorithm
|
||||
used is not documented, it must be decoded. Many simple prototypes are already
|
||||
working however.
|
||||
|
||||
If specmaker does not find a prototype, it emits code like the following:
|
||||
|
||||
In the .spec file:
|
||||
|
||||
@stub _OpenZipFile
|
||||
|
||||
in the header file:
|
||||
|
||||
/* __cdecl ZIPEXTRA__OpenZipFile() */
|
||||
|
||||
in the C source file:
|
||||
|
||||
/*********************************************************************
|
||||
* _OpenZipFile (ZIPEXTRA.@)
|
||||
*
|
||||
*/
|
||||
#if 0
|
||||
__stdcall ZIPEXTRA__OpenZipFile()
|
||||
{
|
||||
/* '@Stubbed'ed in .spec */
|
||||
}
|
||||
#endif
|
||||
|
||||
If a prototype is found, or correctly demangled, the following is emitted:
|
||||
|
||||
.spec:
|
||||
@ stdcall _OpenZipFile ZIPEXTRA__OpenZipFile
|
||||
|
||||
.h:
|
||||
BOOL __stdcall ZIPEXTRA__OpenZipFile(LPCSTR pszFileName);
|
||||
|
||||
.c:
|
||||
BOOL __stdcall ZIPEXTRA__OpenZipFile(LPCSTR pszFileName)
|
||||
{
|
||||
TRACE("stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Note that if the prototype does not contain argument names, specmaker will
|
||||
add them following the convention arg0, arg1 ... argN. If the function is
|
||||
demangled C++, the first argument will be called '_this' if an implicit this
|
||||
pointer is passed (i.e. the function is a non-static class member function).
|
||||
|
||||
|
||||
OPTION: -t TRACE arguments (implies -c)
|
||||
|
||||
This option produces the same code as -c, except that arguments are printed
|
||||
out when the function is called, so the FIXME in the above example becomes:
|
||||
|
||||
FIXME("(%s) stub", pszFileName);
|
||||
|
||||
Structs that are passed by value are printed as "struct", and functions
|
||||
that take variable argument lists print "...".
|
||||
|
||||
|
||||
OPTION: -f dll Forward calls to 'dll' (implies -t)
|
||||
|
||||
This is the most complicated level of code generation. The same code is
|
||||
generated as -t, however support is added for forwarding calls to another
|
||||
DLL. The DLL to forward to is given as 'dll'. Lets suppose we built the
|
||||
examples above using "-f real_zipextra". The code generated will look like
|
||||
the following:
|
||||
|
||||
.spec
|
||||
As for -c, except if a function prototype was not found:
|
||||
|
||||
@ forward _OpenZipFile real_zipextra._OpenZipFile
|
||||
|
||||
In this case the function is forwarded to the destination DLL rather
|
||||
than stubbed.
|
||||
|
||||
.h
|
||||
As for -c.
|
||||
|
||||
.c
|
||||
|
||||
A variable "hDLL" is added to hold a pointer to the DLL to forward to, and
|
||||
the initialisation code in ZIPEXTRA_Init is changed to load and free the
|
||||
forward DLL automatically:
|
||||
|
||||
HMODULE hDLL = 0; /* DLL to call through to */
|
||||
|
||||
BOOL WINAPI ZIPEXTRA_Init(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
||||
{
|
||||
TRACE("(0x%08x, %ld, %p)\n", hinstDLL, fdwReason, lpvReserved);
|
||||
|
||||
if (fdwReason == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
hDLL = LoadLibraryA( "real_zipextra" );
|
||||
TRACE ("Forwarding DLL (real_zipextra) loaded\n" );
|
||||
}
|
||||
else if (fdwReason == DLL_PROCESS_DETACH)
|
||||
{
|
||||
FreeLibrary( hDLL );
|
||||
TRACE ("Forwarding DLL (real_zipextra) freed\n" );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
The stub function is changed to call the forwarding DLL and return that value.
|
||||
|
||||
BOOL __stdcall ZIPEXTRA__OpenZipFile(LPCSTR pszFileName)
|
||||
{
|
||||
BOOL (__stdcall *pFunc)(LPCSTR) = (void*)GetProcAddress(hDLL,"_OpenZipFile");
|
||||
BOOL retVal;
|
||||
TRACE("((LPCSTR)%s) stub", pszFileName);
|
||||
retVal = pFunc(pszFileName);
|
||||
TRACE("returned (%ld)\n",(LONG)retVal));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
This allows you to investigate the workings of a DLL without interfering in
|
||||
its operation in any way (unless you want to).
|
||||
|
||||
In the example I have been using, we probably should have used the -o option
|
||||
to change the ouput name of our DLL to something else, and used the -f
|
||||
option to forward to the real zipextra DLL:
|
||||
|
||||
specmaker -d zipextra -f zipextra -o myzipextra -I "~/zipextra/include/*h"
|
||||
|
||||
Then in the .spec file for our Winelib application, we add the line:
|
||||
|
||||
import myzipextra
|
||||
|
||||
When we build our application, winebuild resolves the calls to our Unix .so.
|
||||
As our application runs we can see the values of all parameters passed to
|
||||
the DLL, and any values returned, without having to write code to dump
|
||||
them ourselves (see below for a better way to wrap a DLL for forwarding).
|
||||
|
||||
This isn't a very realistic example of the usefulness of this feature,
|
||||
however, since we could print out the results anyway, because it is our
|
||||
application making the calls to the DLL. Where DLL forwarding is most useful
|
||||
is where an application or DLL we didn't write calls functions in the DLL.
|
||||
In this case we can capture the sequence of calls made, and the values passed
|
||||
around. This is an aid in reimplementing the DLL, since we can add code for a
|
||||
function, print the results, and then call the real DLL and compare. Only
|
||||
when our code is the same do we need to remove the function pointer and the
|
||||
call to the real DLL. A similar feature in wine is +relay debugging. Using a
|
||||
fowarding DLL allows more granular reporting of arguments, because you can
|
||||
write code to dump out the contents of types/structures rather than just
|
||||
their address in memory. A future version of specmaker may generate this
|
||||
code automatically for common Win32 types.
|
||||
|
||||
See below for more information on setting up a forwarding DLL.
|
||||
|
||||
|
||||
Problems compiling a DLL containing generated code
|
||||
--------------------------------------------------
|
||||
|
||||
Unless you are very lucky, you will need to do a small amount of work to
|
||||
get a DLL generated with -c, -t or -f to compile. The reason for this is
|
||||
that most DLLs will use custom types such as structs whose definition
|
||||
is not known to the code in the DLL.
|
||||
|
||||
Heres an example prototype from crtdll:
|
||||
|
||||
double __cdecl _cabs(struct _complex arg0)
|
||||
|
||||
The definition for the _complex struct needs to be given. Since it is passed
|
||||
by value, its size also needs to be correct in order to forward the call
|
||||
correctly to a native DLL. In this case the structure is 8 bytes in size, which
|
||||
means that the gcc compile flag -freg-struct-return must be given when
|
||||
compiling the function in order to be compatable with the native DLL. (In
|
||||
general this is not an issue, but you need to be aware of such issues if you
|
||||
encounter problems with your forwarding DLL).
|
||||
|
||||
For third party (non C++) DLL's, the header(s) supplied with the DLL can
|
||||
normally be added as an include to the generated DLL header. For other DLLs
|
||||
I suggest creating a seperate header in the DLL directory and adding any
|
||||
needed types to that. This allows you to rebuild the DLL at whim, for example
|
||||
if a new version of specmaker brings increased functionality, then you
|
||||
only have to overwrite the generated files and re-include the header to take
|
||||
advantage of it.
|
||||
|
||||
Usually there isn't much work to do to get the DLL to compile if you have
|
||||
headers. As an example, building a forwarded crtdll, which contains 520
|
||||
functions, required 20 types to be defined before it compiled. Of these,
|
||||
about half were structures, so about 35 lines of code were needed. The only
|
||||
change to the generated code was one line in the header to include the type
|
||||
definitions.
|
||||
|
||||
To save some typing in case you don't have headers for your DLL type, specmaker
|
||||
will dump dummy declarations for unknown classes and types it encounters,
|
||||
if you use the -v option. These can be piped directly into a fix-up header
|
||||
file for use in compiling your DLL. For example, if specmaker encounters the
|
||||
(C++ ) symbol:
|
||||
|
||||
??0foobar@@QAE@ABV0@@Z (Which is a constructor for a foobar object)
|
||||
|
||||
It will emit the following with -v set:
|
||||
|
||||
struct foobar { int _FIXME; };
|
||||
|
||||
(Classes are mapped to C structs when generating code).
|
||||
|
||||
The output should be piped through 'sort' and 'uniq' to remove multiple
|
||||
declarations, e.g:
|
||||
|
||||
specmaker -d foo -c -I "inc/*.h" -v | grep FIXME | sort | uniq > fixup.h
|
||||
|
||||
By adding '#include "fixup.h"' to foobar_dll.h your compile errors will be
|
||||
greatly reduced.
|
||||
|
||||
If specmaker encounters a type it doesnt know that is passed by value (as in
|
||||
the _cabs example above), it also prints a FIXME message like:
|
||||
|
||||
/* FIXME: By value type: Assumed 'int' */ typedef int ldiv_t;
|
||||
|
||||
If the type is not an int, you will need to change the code and possibly
|
||||
the .spec entry in order to forward correctly. Otherwise, include the typedef
|
||||
in your fixup header to avoid compile errors.
|
||||
|
||||
|
||||
Using a forwarding DLL
|
||||
----------------------
|
||||
|
||||
To create and use a forwarding DLL to trace DLL calls, you need to first
|
||||
create a DLL using the -f option as outlined above, and get it to compile.
|
||||
In order to forward calls the following procedure can be used (for this
|
||||
example we are going to build a forwarding msvcrt.dll for the purpose
|
||||
of reimplementing it).
|
||||
|
||||
First we create the forwarding DLL. We will rename the real msvcrt.dll on our
|
||||
system to ms_msvcrt.dll, and our msvcrt implementation will call it:
|
||||
|
||||
specmaker -d msvcrt -C -f ms_msvcrt -I "inc/*.h"
|
||||
|
||||
We then install this DLL into the Wine tree and add the types we need to
|
||||
make it compile. Once the DLL compiles, we create a dummy ms_msvcrt DLL so
|
||||
winebuild will resolve our forward calls to it (for the cases where specmaker
|
||||
couldn't generate code and has placed an '@forward' line in the .spec file):
|
||||
|
||||
specmaker -d msvcrt -C -o ms_msvcrt
|
||||
|
||||
Install this DLL into the wine tree (since its a stub DLL, no changes are
|
||||
needed to the code).
|
||||
|
||||
Now uncomment the line that specmaker inserted into msvcrt.spec:
|
||||
|
||||
#inport ms_msvcrt.dll
|
||||
|
||||
And recompile Wine.
|
||||
|
||||
Finally, we must tell Wine to only use the builtin msvcrt.dll and to only use
|
||||
the native (Win32) ms_msvcrt.dll. Add the following two lines to ~/.wine/config
|
||||
under the [DllOverrides] section:
|
||||
|
||||
;Use our implmentation of msvcrt
|
||||
"msvcrt" = "builtin, so"
|
||||
;Use only the Win32 ms_msvcrt
|
||||
"ms_msvcrt" = "native"
|
||||
|
||||
At this point, when any call is made to msvcrt.dll, Our libmsvcrt.so recieves
|
||||
the call. It then forwards or calls ms_msvcrt.dll, which is the native dll. We
|
||||
recieve a return value and pass it back to our caller, having TRACEd the
|
||||
arguments on the way.
|
||||
|
||||
At this point you are ready to start reimplementing the calls.
|
||||
|
||||
|
||||
Final comments
|
||||
--------------
|
||||
|
||||
If you have any suggestions for improving this tool, please let me know.
|
||||
If anyone can help answer the FIXME questions in msmangle.c or can fill me in
|
||||
on any aspect of the C++ mangling scheme, I would appreciate it. In particular
|
||||
I want to know what _E and _G represent.
|
||||
|
||||
If you encounter a C++ symbol that doesn't demangle **AND** you have the
|
||||
prototype for it, please send me the symbol as reported by specmaker and the
|
||||
prototype. The more examples I have the easier it is to decypher the scheme,
|
||||
and generating them myself is very slow.
|
||||
|
||||
Finally, although it is easy to generate a DLL, I _very strongly_ suggest that
|
||||
you dont submit a generated DLL for inclusion into Wine unless you have
|
||||
actually implemented a fairly reasonable portion of it. Even then, you should
|
||||
only send the portions of the DLL you have implemented. Thousands of lines of
|
||||
stub code don't help the project at all.
|
||||
|
||||
Please send questions and bug reports to jon_p_griffiths@yahoo.com.
|
||||
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
[1] See the Wine and Wine.conf man pages for details on how to tell Wine
|
||||
whether to use native (Win32) or internal DLLs.
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* DLL symbol extraction
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*/
|
||||
#include "specmaker.h"
|
||||
|
||||
/* DOS/PE Header details */
|
||||
#define DOS_HEADER_LEN 64
|
||||
#define DOS_MAGIC 0x5a4d
|
||||
#define DOS_PE_OFFSET 60
|
||||
#define PE_HEADER_LEN 248
|
||||
#define PE_MAGIC 0x4550
|
||||
#define PE_COUNT_OFFSET 6
|
||||
#define PE_EXPORTS_OFFSET 120
|
||||
#define PE_EXPORTS_SIZE PE_EXPORTS_OFFSET + 4
|
||||
#define SECTION_HEADER_LEN 40
|
||||
#define SECTION_ADDR_OFFSET 12
|
||||
#define SECTION_ADDR_SIZE SECTION_ADDR_OFFSET + 4
|
||||
#define SECTION_POS_OFFSET SECTION_ADDR_SIZE + 4
|
||||
#define EXPORT_COUNT_OFFSET 24
|
||||
#define EXPORT_NAME_OFFSET EXPORT_COUNT_OFFSET + 8
|
||||
|
||||
/* Minimum memory needed to read both headers into a buffer */
|
||||
#define MIN_HEADER_LEN (PE_HEADER_LEN * sizeof (unsigned char))
|
||||
|
||||
/* Normalise a pointer in the exports section */
|
||||
#define REBASE(x) ((x) - exports)
|
||||
|
||||
/* Module globals */
|
||||
static FILE *dll_file = NULL;
|
||||
static char **dll_symbols = NULL;
|
||||
static size_t dll_num_exports = 0;
|
||||
|
||||
|
||||
/* Get a short from a memory block */
|
||||
static inline size_t get_short (const char *mem)
|
||||
{
|
||||
return *((const unsigned char *)mem) +
|
||||
(*((const unsigned char *)mem + 1) << 8);
|
||||
}
|
||||
|
||||
/* Get an integer from a memory block */
|
||||
static inline size_t get_int (const char *mem)
|
||||
{
|
||||
assert (sizeof (char) == (size_t)1);
|
||||
return get_short (mem) + (get_short (mem + 2) << 16);
|
||||
}
|
||||
|
||||
static void dll_close (void);
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* dll_open
|
||||
*
|
||||
* Open a DLL and read in exported symbols
|
||||
*/
|
||||
void dll_open (const char *dll_name)
|
||||
{
|
||||
size_t code = 0, code_len = 0, exports, exports_len, count, symbol_data;
|
||||
char *buff = NULL;
|
||||
dll_file = open_file (dll_name, ".dll", "r");
|
||||
|
||||
atexit (dll_close);
|
||||
|
||||
/* Read in the required DOS and PE Headers */
|
||||
if (!(buff = (char *) malloc (MIN_HEADER_LEN)))
|
||||
fatal ("Out of memory");
|
||||
|
||||
if (fread (buff, DOS_HEADER_LEN, 1, dll_file) != 1 ||
|
||||
get_short (buff) != DOS_MAGIC)
|
||||
fatal ("Error reading DOS header");
|
||||
|
||||
if (fseek (dll_file, get_int (buff + DOS_PE_OFFSET), SEEK_SET) == -1)
|
||||
fatal ("Error seeking PE header");
|
||||
|
||||
if (fread (buff, PE_HEADER_LEN, 1, dll_file) != 1 ||
|
||||
get_int (buff) != PE_MAGIC)
|
||||
fatal ("Error reading PE header");
|
||||
|
||||
exports = get_int (buff + PE_EXPORTS_OFFSET);
|
||||
exports_len = get_int (buff + PE_EXPORTS_SIZE);
|
||||
|
||||
if (!exports || !exports_len)
|
||||
fatal ("No exports in DLL");
|
||||
|
||||
if (!(count = get_short (buff + PE_COUNT_OFFSET)))
|
||||
fatal ("No sections in DLL");
|
||||
|
||||
if (VERBOSE)
|
||||
printf ("DLL has %d sections\n", count);
|
||||
|
||||
/* Iterate through sections until we find exports */
|
||||
while (count--)
|
||||
{
|
||||
if (fread (buff, SECTION_HEADER_LEN, 1, dll_file) != 1)
|
||||
fatal ("Section read error");
|
||||
|
||||
code = get_int (buff + SECTION_ADDR_OFFSET);
|
||||
code_len = get_int (buff + SECTION_ADDR_SIZE);
|
||||
|
||||
if (code <= exports && code + code_len > exports)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!count)
|
||||
fatal ("No export section");
|
||||
|
||||
code_len -= (exports - code);
|
||||
|
||||
if (code_len < exports_len)
|
||||
fatal ("Corrupt exports");
|
||||
|
||||
/* Load exports section */
|
||||
if (fseek (dll_file, get_int (buff + SECTION_POS_OFFSET)
|
||||
+ exports - code, SEEK_SET) == -1)
|
||||
fatal ("Export section seek error");
|
||||
|
||||
if (VERBOSE)
|
||||
printf ("Export data size = %d bytes\n", code_len);
|
||||
|
||||
if (!(buff = (char *) realloc (buff, code_len)))
|
||||
fatal ("Out of memory");
|
||||
|
||||
if (fread (buff, code_len, 1, dll_file) != 1)
|
||||
fatal ("Read error");
|
||||
|
||||
dll_close();
|
||||
|
||||
/* Locate symbol names */
|
||||
symbol_data = REBASE( get_int (buff + EXPORT_NAME_OFFSET));
|
||||
|
||||
if (symbol_data > code_len)
|
||||
fatal ("Corrupt exports section");
|
||||
|
||||
if (!(dll_num_exports = get_int (buff + EXPORT_COUNT_OFFSET)))
|
||||
fatal ("No export count");
|
||||
|
||||
if (!(dll_symbols = (char **) malloc (dll_num_exports * sizeof (char *))))
|
||||
fatal ("Out of memory");
|
||||
|
||||
/* Read symbol names into 'dll_symbols' */
|
||||
count = 0;
|
||||
while (count < dll_num_exports)
|
||||
{
|
||||
const int symbol_offset = get_int (buff + symbol_data + count * 4);
|
||||
const char *symbol_name_ptr = REBASE (buff + symbol_offset);
|
||||
|
||||
assert(symbol_name_ptr);
|
||||
dll_symbols[count] = strdup (symbol_name_ptr);
|
||||
assert(dll_symbols[count]);
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
if (NORMAL)
|
||||
printf ("%d exported symbols in DLL\n", dll_num_exports);
|
||||
|
||||
free (buff);
|
||||
|
||||
/* Set DLL output names */
|
||||
if ((buff = strrchr (globals.input_name, '/')))
|
||||
globals.input_name = buff + 1; /* Strip path */
|
||||
|
||||
OUTPUT_UC_DLL_NAME = str_toupper( strdup (OUTPUT_DLL_NAME));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* dll_next_symbol
|
||||
*
|
||||
* Get next exported symbol from dll
|
||||
*/
|
||||
char* dll_next_symbol ()
|
||||
{
|
||||
static unsigned int current_export = 0;
|
||||
|
||||
assert (current_export <= dll_num_exports);
|
||||
|
||||
if (current_export == dll_num_exports)
|
||||
return NULL;
|
||||
|
||||
assert (dll_symbols);
|
||||
assert (dll_symbols [current_export]);
|
||||
|
||||
return strdup (dll_symbols [current_export++]);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* dll_close
|
||||
*
|
||||
* Free resources used by DLL
|
||||
*/
|
||||
static void dll_close (void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (dll_file)
|
||||
{
|
||||
fclose (dll_file);
|
||||
dll_file = NULL;
|
||||
}
|
||||
|
||||
if (dll_symbols)
|
||||
{
|
||||
for (i = 0; i < dll_num_exports; i++)
|
||||
if (dll_symbols [i])
|
||||
free (dll_symbols [i]);
|
||||
free (dll_symbols);
|
||||
dll_symbols = NULL;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
#! /usr/bin/perl
|
||||
|
||||
# Copyright 2000 Patrik Stridvall
|
||||
|
||||
use strict;
|
||||
|
||||
my $invert = 0;
|
||||
my $pattern;
|
||||
my @files = ();
|
||||
|
||||
while(defined($_ = shift)) {
|
||||
if(/^-/) {
|
||||
if(/^-v$/) {
|
||||
$invert = 1;
|
||||
}
|
||||
} else {
|
||||
if(!defined($pattern)) {
|
||||
$pattern = $_;
|
||||
} else {
|
||||
push @files, $_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $file (@files) {
|
||||
open(IN, "< $file");
|
||||
|
||||
my $level = 0;
|
||||
my $extern_c = 0;
|
||||
|
||||
my $again = 0;
|
||||
my $lookahead = 0;
|
||||
while($again || defined(my $line = <IN>)) {
|
||||
if(!$again) {
|
||||
chomp $line;
|
||||
if($lookahead) {
|
||||
$lookahead = 0;
|
||||
$_ .= "\n" . $line;
|
||||
} else {
|
||||
$_ = $line;
|
||||
}
|
||||
} else {
|
||||
$again = 0;
|
||||
}
|
||||
|
||||
# remove C comments
|
||||
if(s/^(.*?)(\/\*.*?\*\/)(.*)$/$1 $3/s) {
|
||||
$again = 1;
|
||||
next;
|
||||
} elsif(/^(.*?)\/\*/s) {
|
||||
$lookahead = 1;
|
||||
next;
|
||||
}
|
||||
|
||||
# remove C++ comments
|
||||
while(s/^(.*?)\/\/.*?$/$1\n/s) { $again = 1; }
|
||||
if($again) { next; }
|
||||
|
||||
# remove empty rows
|
||||
if(/^\s*$/) { next; }
|
||||
|
||||
# remove preprocessor directives
|
||||
if(s/^\s*\#/\#/m) {
|
||||
if(/^\#.*?\\$/m) {
|
||||
$lookahead = 1;
|
||||
next;
|
||||
} elsif(s/^\#\s*(.*?)(\s+(.*?))?\s*$//m) {
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# Remove extern "C"
|
||||
if(s/^\s*extern\s+"C"\s+\{//m) {
|
||||
$extern_c = 1;
|
||||
$again = 1;
|
||||
next;
|
||||
}
|
||||
|
||||
if($level > 0)
|
||||
{
|
||||
my $line = "";
|
||||
while(/^[^\{\}]/) {
|
||||
s/^([^\{\}\'\"]*)//s;
|
||||
$line .= $1;
|
||||
if(s/^\'//) {
|
||||
$line .= "\'";
|
||||
while(/^./ && !s/^\'//) {
|
||||
s/^([^\'\\]*)//s;
|
||||
$line .= $1;
|
||||
if(s/^\\//) {
|
||||
$line .= "\\";
|
||||
if(s/^(.)//s) {
|
||||
$line .= $1;
|
||||
if($1 eq "0") {
|
||||
s/^(\d{0,3})//s;
|
||||
$line .= $1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$line .= "\'";
|
||||
} elsif(s/^\"//) {
|
||||
$line .= "\"";
|
||||
while(/^./ && !s/^\"//) {
|
||||
s/^([^\"\\]*)//s;
|
||||
$line .= $1;
|
||||
if(s/^\\//) {
|
||||
$line .= "\\";
|
||||
if(s/^(.)//s) {
|
||||
$line .= $1;
|
||||
if($1 eq "0") {
|
||||
s/^(\d{0,3})//s;
|
||||
$line .= $1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$line .= "\"";
|
||||
}
|
||||
}
|
||||
|
||||
if(s/^\{//) {
|
||||
$_ = $'; $again = 1;
|
||||
$line .= "{";
|
||||
$level++;
|
||||
} elsif(s/^\}//) {
|
||||
$_ = $'; $again = 1;
|
||||
$line .= "}" if $level > 1;
|
||||
$level--;
|
||||
if($level == -1 && $extern_c) {
|
||||
$extern_c = 0;
|
||||
$level = 0;
|
||||
}
|
||||
}
|
||||
|
||||
next;
|
||||
} elsif(/^class[^\}]*{/) {
|
||||
$_ = $'; $again = 1;
|
||||
$level++;
|
||||
next;
|
||||
} elsif(/^class[^\}]*$/) {
|
||||
$lookahead = 1;
|
||||
next;
|
||||
} elsif(/^typedef[^\}]*;/) {
|
||||
next;
|
||||
} elsif(/(extern\s+|static\s+)?
|
||||
(?:__inline__\s+|__inline\s+|inline\s+)?
|
||||
((struct\s+|union\s+|enum\s+)?(?:\w+(?:\:\:(?:\s*operator\s*[^\)\s]+)?)?)+((\s*(?:\*|\&))+\s*|\s+))
|
||||
((__cdecl|__stdcall|CDECL|VFWAPIV|VFWAPI|WINAPIV|WINAPI|CALLBACK)\s+)?
|
||||
((?:\w+(?:\:\:)?)+(\(\w+\))?)\s*\(([^\)]*)\)\s*
|
||||
(?:\w+(?:\s*\([^\)]*\))?\s*)*\s*
|
||||
(\{|\;)/sx)
|
||||
{
|
||||
$_ = $'; $again = 1;
|
||||
if($11 eq "{") {
|
||||
$level++;
|
||||
}
|
||||
|
||||
my $linkage = $1;
|
||||
my $return_type = $2;
|
||||
my $calling_convention = $7;
|
||||
my $name = $8;
|
||||
my $arguments = $10;
|
||||
|
||||
if(!defined($linkage)) {
|
||||
$linkage = "";
|
||||
}
|
||||
|
||||
if(!defined($calling_convention)) {
|
||||
$calling_convention = "";
|
||||
}
|
||||
|
||||
$linkage =~ s/\s*$//;
|
||||
|
||||
$return_type =~ s/\s*$//;
|
||||
$return_type =~ s/\s*\*\s*/*/g;
|
||||
$return_type =~ s/(\*+)/ $1/g;
|
||||
|
||||
$arguments =~ y/\t\n/ /;
|
||||
$arguments =~ s/^\s*(.*?)\s*$/$1/;
|
||||
if($arguments eq "") { $arguments = "void" }
|
||||
|
||||
my @argument_types;
|
||||
my @argument_names;
|
||||
my @arguments = split(/,/, $arguments);
|
||||
foreach my $n (0..$#arguments) {
|
||||
my $argument_type = "";
|
||||
my $argument_name = "";
|
||||
my $argument = $arguments[$n];
|
||||
$argument =~ s/^\s*(.*?)\s*$/$1/;
|
||||
# print " " . ($n + 1) . ": '$argument'\n";
|
||||
$argument =~ s/^(IN OUT(?=\s)|IN(?=\s)|OUT(?=\s)|\s*)\s*//;
|
||||
$argument =~ s/^(const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s)|\s*)\s*//;
|
||||
if($argument =~ /^\.\.\.$/) {
|
||||
$argument_type = "...";
|
||||
$argument_name = "...";
|
||||
} elsif($argument =~ /^
|
||||
((?:struct\s+|union\s+|enum\s+|(?:signed\s+|unsigned\s+)
|
||||
(?:short\s+(?=int)|long\s+(?=int))?)?(?:\w+(?:\:\:)?)+)\s*
|
||||
((?:const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s))?\s*(?:\*\s*?)*)\s*
|
||||
(?:const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s))?\s*
|
||||
(\w*)\s*
|
||||
(?:\[\]|\s+OPTIONAL)?/x)
|
||||
{
|
||||
$argument_type = "$1";
|
||||
if($2 ne "") {
|
||||
$argument_type .= " $2";
|
||||
}
|
||||
$argument_name = $3;
|
||||
|
||||
$argument_type =~ s/\s*const\s*/ /;
|
||||
$argument_type =~ s/^\s*(.*?)\s*$/$1/;
|
||||
|
||||
$argument_name =~ s/^\s*(.*?)\s*$/$1/;
|
||||
} else {
|
||||
die "$file: $.: syntax error: '$argument'\n";
|
||||
}
|
||||
$argument_types[$n] = $argument_type;
|
||||
$argument_names[$n] = $argument_name;
|
||||
# print " " . ($n + 1) . ": '$argument_type': '$argument_name'\n";
|
||||
}
|
||||
if($#argument_types == 0 && $argument_types[0] =~ /^void$/i) {
|
||||
$#argument_types = -1;
|
||||
$#argument_names = -1;
|
||||
}
|
||||
|
||||
@arguments = ();
|
||||
foreach my $n (0..$#argument_types) {
|
||||
if($argument_names[$n] && $argument_names[$n] ne "...") {
|
||||
if($argument_types[$n] !~ /\*$/) {
|
||||
$arguments[$n] = $argument_types[$n] . " " . $argument_names[$n];
|
||||
} else {
|
||||
$arguments[$n] = $argument_types[$n] . $argument_names[$n];
|
||||
}
|
||||
} else {
|
||||
$arguments[$n] = $argument_types[$n];
|
||||
}
|
||||
}
|
||||
|
||||
$arguments = join(", ", @arguments);
|
||||
if(!$arguments) { $arguments = "void"; }
|
||||
|
||||
if((!$invert && $name =~ /$pattern/) || ($invert && $name !~ /$pattern/)) {
|
||||
if($calling_convention) {
|
||||
print "$return_type $calling_convention $name($arguments)\n";
|
||||
} else {
|
||||
if($return_type =~ /\*$/) {
|
||||
print "$return_type$name($arguments)\n";
|
||||
} else {
|
||||
print "$return_type $name($arguments)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} elsif(/\'[^\']*\'/s) {
|
||||
$_ = $'; $again = 1;
|
||||
} elsif(/\"[^\"]*\"/s) {
|
||||
$_ = $'; $again = 1;
|
||||
} elsif(/;/s) {
|
||||
$_ = $'; $again = 1;
|
||||
} elsif(/extern\s+"C"\s+{/s) {
|
||||
$_ = $'; $again = 1;
|
||||
} elsif(/\{/s) {
|
||||
$_ = $'; $again = 1;
|
||||
$level++;
|
||||
} else {
|
||||
$lookahead = 1;
|
||||
}
|
||||
}
|
||||
close(IN);
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Option processing and main()
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*/
|
||||
#include "specmaker.h"
|
||||
|
||||
|
||||
_globals globals; /* All global variables */
|
||||
|
||||
|
||||
static void do_include (const char *arg)
|
||||
{
|
||||
globals.directory = arg;
|
||||
globals.do_code = 1;
|
||||
}
|
||||
|
||||
|
||||
static inline const char* strip_ext (const char *str)
|
||||
{
|
||||
char *ext = strstr(str, ".dll");
|
||||
if (ext)
|
||||
return str_substring (str, ext);
|
||||
else
|
||||
return strdup (str);
|
||||
}
|
||||
|
||||
|
||||
static void do_name (const char *arg)
|
||||
{
|
||||
globals.dll_name = strip_ext (arg);
|
||||
}
|
||||
|
||||
|
||||
static void do_input (const char *arg)
|
||||
{
|
||||
globals.input_name = strip_ext (arg);
|
||||
}
|
||||
|
||||
|
||||
static void do_code (void)
|
||||
{
|
||||
globals.do_code = 1;
|
||||
}
|
||||
|
||||
|
||||
static void do_trace (void)
|
||||
{
|
||||
globals.do_trace = 1;
|
||||
globals.do_code = 1;
|
||||
}
|
||||
|
||||
|
||||
static void do_forward (const char *arg)
|
||||
{
|
||||
globals.forward_dll = arg;
|
||||
globals.do_trace = 1;
|
||||
globals.do_code = 1;
|
||||
}
|
||||
|
||||
static void do_document (void)
|
||||
{
|
||||
globals.do_documentation = 1;
|
||||
}
|
||||
|
||||
static void do_cdecl (void)
|
||||
{
|
||||
globals.do_cdecl = 1;
|
||||
}
|
||||
|
||||
|
||||
static void do_quiet (void)
|
||||
{
|
||||
globals.do_quiet = 1;
|
||||
}
|
||||
|
||||
|
||||
static void do_start (const char *arg)
|
||||
{
|
||||
globals.start_ordinal = atoi (arg);
|
||||
if (!globals.start_ordinal)
|
||||
fatal ("Invalid -s option (must be numeric)");
|
||||
}
|
||||
|
||||
|
||||
static void do_end (const char *arg)
|
||||
{
|
||||
globals.end_ordinal = atoi (arg);
|
||||
if (!globals.end_ordinal)
|
||||
fatal ("Invalid -e option (must be numeric)");
|
||||
}
|
||||
|
||||
|
||||
static void do_verbose (void)
|
||||
{
|
||||
globals.do_verbose = 1;
|
||||
}
|
||||
|
||||
|
||||
struct option
|
||||
{
|
||||
const char *name;
|
||||
int has_arg;
|
||||
void (*func) ();
|
||||
const char *usage;
|
||||
};
|
||||
|
||||
|
||||
static const struct option option_table[] = {
|
||||
{"-d", 1, do_input, "-d dll Use dll for input file (mandatory)"},
|
||||
{"-h", 0, do_usage, "-h Display this help message"},
|
||||
{"-I", 1, do_include, "-I dir Look for prototypes in 'dir' (implies -c)"},
|
||||
{"-o", 1, do_name, "-o name Set the output dll name (default: dll)"},
|
||||
{"-c", 0, do_code, "-c Generate skeleton code (requires -I)"},
|
||||
{"-t", 0, do_trace, "-t TRACE arguments (implies -c)"},
|
||||
{"-f", 1, do_forward, "-f dll Forward calls to 'dll' (implies -t)"},
|
||||
{"-D", 0, do_document, "-D Generate documentation"},
|
||||
{"-C", 0, do_cdecl, "-C Assume __cdecl calls (default: __stdcall)"},
|
||||
{"-s", 1, do_start, "-s num Start prototype search after symbol 'num'"},
|
||||
{"-e", 1, do_end, "-e num End prototype search after symbol 'num'"},
|
||||
{"-q", 0, do_quiet, "-q Don't show progress (quiet)."},
|
||||
{"-v", 0, do_verbose, "-v Show lots of detail while working (verbose)."},
|
||||
{NULL, 0, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
void do_usage (void)
|
||||
{
|
||||
const struct option *opt;
|
||||
printf ("Usage: specmaker [options] -d dll\n\nOptions:\n");
|
||||
for (opt = option_table; opt->name; opt++)
|
||||
printf (" %s\n", opt->usage);
|
||||
puts ("\n");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* parse_options
|
||||
*
|
||||
* Parse options from the argv array
|
||||
*/
|
||||
static void parse_options (char *argv[])
|
||||
{
|
||||
const struct option *opt;
|
||||
char *const *ptr;
|
||||
const char *arg = NULL;
|
||||
|
||||
ptr = argv + 1;
|
||||
|
||||
while (*ptr != NULL)
|
||||
{
|
||||
for (opt = option_table; opt->name; opt++)
|
||||
{
|
||||
if (opt->has_arg && !strncmp (*ptr, opt->name, strlen (opt->name)))
|
||||
{
|
||||
arg = *ptr + strlen (opt->name);
|
||||
if (*arg == '\0')
|
||||
{
|
||||
ptr++;
|
||||
arg = *ptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!strcmp (*ptr, opt->name))
|
||||
{
|
||||
arg = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!opt->name)
|
||||
fatal ("Unrecognized option");
|
||||
|
||||
if (opt->has_arg && arg != NULL)
|
||||
opt->func (arg);
|
||||
else
|
||||
opt->func ("");
|
||||
|
||||
ptr++;
|
||||
}
|
||||
|
||||
if (globals.do_code && !globals.directory)
|
||||
fatal ("-I must be used if generating code");
|
||||
|
||||
if (!globals.input_name)
|
||||
fatal ("Option -d is mandatory");
|
||||
|
||||
if (VERBOSE && QUIET)
|
||||
fatal ("Options -v and -q are mutually exclusive");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* main
|
||||
*/
|
||||
#ifdef __GNUC__
|
||||
int main (int argc __attribute__((unused)), char *argv[])
|
||||
#else
|
||||
int main (int argc, char *argv[])
|
||||
#endif
|
||||
{
|
||||
parsed_symbol symbol;
|
||||
int count = 0;
|
||||
|
||||
parse_options (argv);
|
||||
|
||||
dll_open (globals.input_name);
|
||||
|
||||
output_spec_preamble ();
|
||||
output_header_preamble ();
|
||||
output_c_preamble ();
|
||||
|
||||
memset (&symbol, 0, sizeof (parsed_symbol));
|
||||
|
||||
while ((symbol.symbol = dll_next_symbol ()))
|
||||
{
|
||||
count++;
|
||||
|
||||
if (NORMAL)
|
||||
printf ("Export %3d - '%s' ...%c", count, symbol.symbol,
|
||||
VERBOSE ? '\n' : ' ');
|
||||
|
||||
if (globals.do_code && count >= globals.start_ordinal
|
||||
&& (!globals.end_ordinal || count <= globals.end_ordinal))
|
||||
{
|
||||
/* Attempt to get information about the symbol */
|
||||
int result = symbol_demangle (&symbol);
|
||||
|
||||
if (result)
|
||||
result = symbol_search (&symbol);
|
||||
|
||||
if (!result)
|
||||
/* Clean up the prototype */
|
||||
symbol_clean_string (symbol.function_name);
|
||||
|
||||
if (NORMAL)
|
||||
puts (result ? "[Not Found]" : "[OK]");
|
||||
}
|
||||
else if (NORMAL)
|
||||
puts ("[Ignoring]");
|
||||
|
||||
output_spec_symbol (&symbol);
|
||||
output_header_symbol (&symbol);
|
||||
output_c_symbol (&symbol);
|
||||
|
||||
symbol_clear (&symbol);
|
||||
}
|
||||
|
||||
output_makefile ();
|
||||
output_install_script ();
|
||||
|
||||
if (VERBOSE)
|
||||
puts ("Finished, Cleaning up...");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Misc functions
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*/
|
||||
#include "specmaker.h"
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* str_create
|
||||
*
|
||||
* Create a single string from many substrings
|
||||
*/
|
||||
char *str_create(size_t num_str, ...)
|
||||
{
|
||||
va_list args;
|
||||
size_t len = 1, i = 0;
|
||||
char *tmp, *t;
|
||||
|
||||
va_start (args, num_str);
|
||||
for (i = 0; i < num_str; i++)
|
||||
if ((t = va_arg(args, char *)))
|
||||
len += strlen (t);
|
||||
va_end (args);
|
||||
|
||||
if (!(tmp = (char *) malloc (len)))
|
||||
fatal ("Out of memory");
|
||||
|
||||
tmp[0] = '\0';
|
||||
|
||||
va_start (args, num_str);
|
||||
for (i = 0; i < num_str; i++)
|
||||
if ((t = va_arg(args, char *)))
|
||||
strcat (tmp, t);
|
||||
va_end (args);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* str_create_num
|
||||
*
|
||||
* Create a single string from many substrings, terminating in a number
|
||||
*/
|
||||
char *str_create_num(size_t num_str, int num, ...)
|
||||
{
|
||||
va_list args;
|
||||
size_t len = 8, i = 0;
|
||||
char *tmp, *t;
|
||||
|
||||
va_start (args, num);
|
||||
for (i = 0; i < num_str; i++)
|
||||
if ((t = va_arg(args, char *)))
|
||||
len += strlen (t);
|
||||
va_end (args);
|
||||
|
||||
if (!(tmp = (char *) malloc (len)))
|
||||
fatal ("Out of memory");
|
||||
|
||||
tmp[0] = '\0';
|
||||
|
||||
va_start (args, num);
|
||||
for (i = 0; i < num_str; i++)
|
||||
if ((t = va_arg(args, char *)))
|
||||
strcat (tmp, t);
|
||||
va_end (args);
|
||||
sprintf (tmp + len - 8, "%d", num);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* str_substring
|
||||
*
|
||||
* Create a new substring from a string
|
||||
*/
|
||||
char *str_substring(const char *start, const char *end)
|
||||
{
|
||||
char *newstr;
|
||||
|
||||
assert (start && end && end > start);
|
||||
|
||||
if (!(newstr = (char *) malloc (end - start + 1)))
|
||||
fatal ("Out of memory");
|
||||
|
||||
memcpy (newstr, start, end - start);
|
||||
newstr [end - start] = '\0';
|
||||
|
||||
return newstr;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* str_replace
|
||||
*
|
||||
* Swap two strings in another string, in place
|
||||
* Modified PD code from 'snippets'
|
||||
*/
|
||||
char *str_replace (char *str, const char *oldstr, const char *newstr)
|
||||
{
|
||||
int oldlen, newlen;
|
||||
char *p, *q;
|
||||
|
||||
if (!(p = strstr(str, oldstr)))
|
||||
return p;
|
||||
oldlen = strlen (oldstr);
|
||||
newlen = strlen (newstr);
|
||||
memmove (q = p + newlen, p + oldlen, strlen (p + oldlen) + 1);
|
||||
memcpy (p, newstr, newlen);
|
||||
return q;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* str_match
|
||||
*
|
||||
* Locate one string in another, ignoring spaces
|
||||
*/
|
||||
const char *str_match (const char *str, const char *match, int *found)
|
||||
{
|
||||
assert(str && match && found);
|
||||
|
||||
for (; *str == ' '; str++);
|
||||
if (!strncmp (str, match, strlen (match)))
|
||||
{
|
||||
*found = 1;
|
||||
str += strlen (match);
|
||||
for (; *str == ' '; str++);
|
||||
}
|
||||
else
|
||||
*found = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* str_find_set
|
||||
*
|
||||
* Locate the first occurence of a set of characters in a string
|
||||
*/
|
||||
const char *str_find_set (const char *str, const char *findset)
|
||||
{
|
||||
assert(str && findset);
|
||||
|
||||
while (*str)
|
||||
{
|
||||
const char *p = findset;
|
||||
while (*p)
|
||||
if (*p++ == *str)
|
||||
return str;
|
||||
str++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* str_toupper
|
||||
*
|
||||
* Uppercase a string
|
||||
*/
|
||||
char *str_toupper (char *str)
|
||||
{
|
||||
char *save = str;
|
||||
while (*str)
|
||||
{
|
||||
*str = toupper (*str);
|
||||
str++;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* open_file
|
||||
*
|
||||
* Open a file returning only on success
|
||||
*/
|
||||
FILE *open_file (const char *name, const char *ext, const char *mode)
|
||||
{
|
||||
char fname[128];
|
||||
FILE *fp;
|
||||
|
||||
if (((unsigned)snprintf (fname, sizeof (fname), "%s%s%s",
|
||||
*mode == 'w' ? "./" : "", name, ext) > sizeof (fname)))
|
||||
fatal ("File name too long");
|
||||
|
||||
if (VERBOSE)
|
||||
printf ("Open file %s\n", fname);
|
||||
|
||||
fp = fopen (fname, mode);
|
||||
if (!fp)
|
||||
fatal ("Cant open file");
|
||||
return fp;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* fatal
|
||||
*
|
||||
* Fatal error handling
|
||||
*/
|
||||
void fatal (const char *message)
|
||||
{
|
||||
if (errno)
|
||||
perror (message);
|
||||
else
|
||||
puts (message);
|
||||
do_usage ();
|
||||
}
|
|
@ -0,0 +1,560 @@
|
|||
/*
|
||||
* Demangle VC++ symbols into C function prototypes
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*/
|
||||
#include "specmaker.h"
|
||||
|
||||
/* Type for parsing mangled types */
|
||||
typedef struct _compound_type
|
||||
{
|
||||
char dest_type;
|
||||
int flags;
|
||||
int have_qualifiers;
|
||||
char *expression;
|
||||
} compound_type;
|
||||
|
||||
|
||||
/* Initialise a compound type structure */
|
||||
#define INIT_CT(ct) do { memset (&ct, 0, sizeof (ct)); } while (0)
|
||||
|
||||
/* free the memory used by a compound structure */
|
||||
#define FREE_CT(ct) do { if (ct.expression) free (ct.expression); } while (0)
|
||||
|
||||
|
||||
/* Internal functions */
|
||||
static char *demangle_datatype (char **str, compound_type *ct,
|
||||
parsed_symbol* sym);
|
||||
|
||||
static char *get_constraints_convention_1 (char **str, compound_type *ct);
|
||||
|
||||
static char *get_constraints_convention_2 (char **str, compound_type *ct);
|
||||
|
||||
static char *get_type_string (const char c, const int constraints);
|
||||
|
||||
static int get_type_constant (const char c, const int constraints);
|
||||
|
||||
static char *get_pointer_type_string (compound_type *ct,
|
||||
const char *expression);
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* demangle_symbol
|
||||
*
|
||||
* Demangle a C++ linker symbol into a C prototype
|
||||
*/
|
||||
int symbol_demangle (parsed_symbol *sym)
|
||||
{
|
||||
compound_type ct;
|
||||
int is_static = 0, is_const = 0;
|
||||
char *function_name = NULL;
|
||||
char *class_name = NULL;
|
||||
char *name;
|
||||
static unsigned int hash = 0; /* In case of overloaded functions */
|
||||
|
||||
assert (globals.do_code);
|
||||
assert (sym && sym->symbol);
|
||||
|
||||
hash++;
|
||||
|
||||
/* MS mangled names always begin with '?' */
|
||||
name = sym->symbol;
|
||||
if (*name++ != '?')
|
||||
return -1;
|
||||
|
||||
if (VERBOSE)
|
||||
puts ("Attempting to demangle symbol");
|
||||
|
||||
/* Then function name or operator code */
|
||||
if (*name == '?')
|
||||
{
|
||||
/* C++ operator code (one character, or two if the first is '_') */
|
||||
switch (*++name)
|
||||
{
|
||||
case '0': function_name = strdup ("ctor"); break;
|
||||
case '1': function_name = strdup ("dtor"); break;
|
||||
case '2': function_name = strdup ("operator_new"); break;
|
||||
case '3': function_name = strdup ("operator_delete"); break;
|
||||
case '4': function_name = strdup ("operator_equals"); break;
|
||||
case '5': function_name = strdup ("operator_5"); break;
|
||||
case '6': function_name = strdup ("operator_6"); break;
|
||||
case '7': function_name = strdup ("operator_7"); break;
|
||||
case '8': function_name = strdup ("operator_equals_equals"); break;
|
||||
case '9': function_name = strdup ("operator_not_equals"); break;
|
||||
case 'E': function_name = strdup ("operator_plus_plus"); break;
|
||||
case 'H': function_name = strdup ("operator_plus"); break;
|
||||
case '_':
|
||||
/* FIXME: Seems to be some kind of escape character - overloads? */
|
||||
switch (*++name)
|
||||
{
|
||||
case '7': /* FIXME: Compiler generated default copy/assignment ctor? */
|
||||
return -1;
|
||||
case 'E': function_name = strdup ("_unknown_E"); break;
|
||||
case 'G': function_name = strdup ("_unknown_G"); break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* FIXME: Other operators */
|
||||
return -1;
|
||||
}
|
||||
name++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Type or function name terminated by '@' */
|
||||
function_name = name;
|
||||
while (*name && *name++ != '@') ;
|
||||
if (!*name)
|
||||
return -1;
|
||||
function_name = str_substring (function_name, name - 1);
|
||||
}
|
||||
|
||||
/* Either a class name, or '@' if the symbol is not a class member */
|
||||
if (*name == '@')
|
||||
{
|
||||
class_name = strdup ("global"); /* Non member function (or a datatype) */
|
||||
name++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Class the function is associated with, terminated by '@@' */
|
||||
class_name = name;
|
||||
while (*name && *name++ != '@') ;
|
||||
if (*name++ != '@')
|
||||
return -1;
|
||||
class_name = str_substring (class_name, name - 2);
|
||||
}
|
||||
|
||||
/* Note: This is guesswork on my part, but it seems to work:
|
||||
* 'Q' Means the function is passed an implicit 'this' pointer.
|
||||
* 'S' Means static member function, i.e. no implicit 'this' pointer.
|
||||
* 'Y' Is used for datatypes and functions, so there is no 'this' pointer.
|
||||
* This character also implies some other things:
|
||||
* 'Y','S' = The character after the calling convention is always the
|
||||
* start of the return type code.
|
||||
* 'Q' Character after the calling convention is 'const'ness code
|
||||
* (only non static member functions can be const).
|
||||
* 'U' also occurs, it seems to behave like Q, but probably implies
|
||||
* something else.
|
||||
*/
|
||||
switch(*name++)
|
||||
{
|
||||
case 'U' :
|
||||
case 'Q' :
|
||||
/* Implicit 'this' pointer */
|
||||
sym->arg_text [sym->argc] = str_create (3, "struct ", class_name, " *");
|
||||
sym->arg_type [sym->argc] = ARG_POINTER;
|
||||
sym->arg_flag [sym->argc] = 0;
|
||||
sym->arg_name [sym->argc++] = strdup ("_this");
|
||||
/* New struct definitions can be 'grep'ed out for making a fixup header */
|
||||
if (VERBOSE)
|
||||
printf ("struct %s { int _FIXME; };\n", class_name);
|
||||
break;
|
||||
case 'S' :
|
||||
is_static = 1;
|
||||
break;
|
||||
case 'Y' :
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Next is the calling convention */
|
||||
switch (*name++)
|
||||
{
|
||||
case 'A':
|
||||
sym->calling_convention = strdup ("__cdecl");
|
||||
break;
|
||||
case 'B': /* FIXME: Something to do with __declspec(dllexport)? */
|
||||
case 'I': /* __fastcall */
|
||||
case 'G':
|
||||
sym->calling_convention = strdup ("__stdcall");
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If the symbol is associated with a class, its 'const' status follows */
|
||||
if (sym->argc)
|
||||
{
|
||||
if (*name == 'B')
|
||||
is_const = 1;
|
||||
else if (*name != 'E')
|
||||
return -1;
|
||||
name++;
|
||||
}
|
||||
|
||||
/* Return type, or @ if 'void' */
|
||||
if (*name == '@')
|
||||
{
|
||||
sym->return_text = strdup ("void");
|
||||
sym->return_type = ARG_VOID;
|
||||
name++;
|
||||
}
|
||||
else
|
||||
{
|
||||
INIT_CT (ct);
|
||||
if (!demangle_datatype (&name, &ct, sym))
|
||||
return -1;
|
||||
sym->return_text = ct.expression;
|
||||
sym->return_type = get_type_constant(ct.dest_type, ct.flags);
|
||||
ct.expression = NULL;
|
||||
FREE_CT (ct);
|
||||
}
|
||||
|
||||
/* Now come the function arguments */
|
||||
while (*name && *name != 'Z')
|
||||
{
|
||||
/* Decode each data type and append it to the argument list */
|
||||
if (*name != '@')
|
||||
{
|
||||
INIT_CT (ct);
|
||||
if (!demangle_datatype(&name, &ct, sym))
|
||||
return -1;
|
||||
|
||||
if (strcmp (ct.expression, "void"))
|
||||
{
|
||||
sym->arg_text [sym->argc] = ct.expression;
|
||||
ct.expression = NULL;
|
||||
sym->arg_type [sym->argc] = get_type_constant (ct.dest_type, ct.flags);
|
||||
sym->arg_flag [sym->argc] = ct.flags;
|
||||
sym->arg_name[sym->argc] = str_create_num (1, sym->argc, "arg");
|
||||
sym->argc++;
|
||||
}
|
||||
else
|
||||
break; /* 'void' terminates an argument list */
|
||||
FREE_CT (ct);
|
||||
}
|
||||
else
|
||||
name++;
|
||||
}
|
||||
|
||||
while (*name == '@')
|
||||
name++;
|
||||
|
||||
/* Functions are always terminated by 'Z'. If we made it this far and
|
||||
* Don't find it, we have incorrectly identified a data type.
|
||||
*/
|
||||
if (*name != 'Z')
|
||||
return -1;
|
||||
|
||||
/* Note: '()' after 'Z' means 'throws', but we don't care here */
|
||||
|
||||
/* Create the function name. Include a unique number because otherwise
|
||||
* overloaded functions could have the same c signature.
|
||||
*/
|
||||
sym->function_name = str_create_num (4, hash, class_name, "_",
|
||||
function_name, is_static ? "_static" : is_const ? "_const" : "_");
|
||||
|
||||
assert (sym->return_text);
|
||||
assert (sym->calling_convention);
|
||||
assert (sym->function_name);
|
||||
|
||||
free (class_name);
|
||||
free (function_name);
|
||||
|
||||
if (VERBOSE)
|
||||
puts ("Demangled symbol OK");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* demangle_datatype
|
||||
*
|
||||
* Attempt to demangle a C++ data type, which may be compound.
|
||||
* a compound type is made up of a number of simple types. e.g:
|
||||
* char** = (pointer to (pointer to (char)))
|
||||
*
|
||||
* Uses a simple recursive descent algorithm that is broken
|
||||
* and/or incomplete, without a doubt ;-)
|
||||
*/
|
||||
static char *demangle_datatype (char **str, compound_type *ct,
|
||||
parsed_symbol* sym)
|
||||
{
|
||||
char *iter;
|
||||
|
||||
assert (str && *str);
|
||||
assert (ct);
|
||||
|
||||
iter = *str;
|
||||
|
||||
if (!get_constraints_convention_1 (&iter, ct))
|
||||
return NULL;
|
||||
|
||||
switch (*iter)
|
||||
{
|
||||
case 'C': case 'D': case 'E': case 'F': case 'G':
|
||||
case 'H': case 'I': case 'J': case 'K': case 'M':
|
||||
case 'N': case 'O': case 'X': case 'Z':
|
||||
/* Simple data types */
|
||||
ct->dest_type = *iter++;
|
||||
if (!get_constraints_convention_2 (&iter, ct))
|
||||
return NULL;
|
||||
ct->expression = get_type_string (ct->dest_type, ct->flags);
|
||||
break;
|
||||
case 'U':
|
||||
case 'V':
|
||||
/* Class/struct/union */
|
||||
ct->dest_type = *iter++;
|
||||
if (*iter == '0' || *iter == '1')
|
||||
{
|
||||
/* Referring to class type (implicit 'this') */
|
||||
char *stripped;
|
||||
if (!sym->argc)
|
||||
return NULL;
|
||||
|
||||
iter++;
|
||||
/* Apply our constraints to the base type (struct xxx *) */
|
||||
stripped = strdup (sym->arg_text [0]);
|
||||
if (!stripped)
|
||||
fatal ("Out of Memory");
|
||||
|
||||
/* If we're a reference, re-use the pointer already in the type */
|
||||
if (!ct->flags & CT_BY_REFERENCE)
|
||||
stripped[ strlen (stripped) - 2] = '\0'; /* otherwise, strip it */
|
||||
|
||||
ct->expression = str_create (2, ct->flags & CT_CONST ? "const " :
|
||||
ct->flags & CT_VOLATILE ? "volatile " : "", stripped);
|
||||
free (stripped);
|
||||
}
|
||||
else if (*iter == '_')
|
||||
{
|
||||
/* The name of the class/struct, followed by '@@' */
|
||||
char *struct_name = ++iter;
|
||||
while (*iter && *iter++ != '@') ;
|
||||
if (*iter++ != '@')
|
||||
return NULL;
|
||||
struct_name = str_substring (struct_name, iter - 2);
|
||||
ct->expression = str_create (4, ct->flags & CT_CONST ? "const " :
|
||||
ct->flags & CT_VOLATILE ? "volatile " : "", "struct ",
|
||||
struct_name, ct->flags & CT_BY_REFERENCE ? " *" : "");
|
||||
free (struct_name);
|
||||
}
|
||||
break;
|
||||
case 'Q': /* FIXME: Array Just treated as pointer currently */
|
||||
case 'P': /* Pointer */
|
||||
{
|
||||
compound_type sub_ct;
|
||||
INIT_CT (sub_ct);
|
||||
|
||||
ct->dest_type = *iter++;
|
||||
if (!get_constraints_convention_2 (&iter, ct))
|
||||
return NULL;
|
||||
|
||||
/* FIXME: P6 = Function pointer, others who knows.. */
|
||||
if (isdigit (*iter))
|
||||
return NULL;
|
||||
|
||||
/* Recurse to get the pointed-to type */
|
||||
if (!demangle_datatype (&iter, &sub_ct, sym))
|
||||
return NULL;
|
||||
|
||||
ct->expression = get_pointer_type_string (ct, sub_ct.expression);
|
||||
|
||||
FREE_CT (sub_ct);
|
||||
}
|
||||
break;
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
/* Referring back to previously parsed type */
|
||||
if (sym->argc >= (size_t)('0' - *iter))
|
||||
return NULL;
|
||||
ct->dest_type = sym->arg_type ['0' - *iter];
|
||||
ct->expression = strdup (sym->arg_text ['0' - *iter]);
|
||||
iter++;
|
||||
break;
|
||||
default :
|
||||
return NULL;
|
||||
}
|
||||
if (!ct->expression)
|
||||
return NULL;
|
||||
|
||||
return (char *)(*str = iter);
|
||||
}
|
||||
|
||||
|
||||
/* Constraints:
|
||||
* There are two conventions for specifying data type constaints. I
|
||||
* don't know how the compiler chooses between them, but I suspect it
|
||||
* is based on ensuring that linker names are unique.
|
||||
* Convention 1. The data type modifier is given first, followed
|
||||
* by the data type it operates on. '?' means passed by value,
|
||||
* 'A' means passed by reference. Note neither of these characters
|
||||
* is a valid base data type. This is then followed by a character
|
||||
* specifying constness or volatilty.
|
||||
* Convention 2. The base data type (which is never '?' or 'A') is
|
||||
* given first. The character modifier is optionally given after
|
||||
* the base type character. If a valid character mofifier is present,
|
||||
* then it only applies to the current data type if the character
|
||||
* after that is not 'A' 'B' or 'C' (Because this makes a convention 1
|
||||
* constraint for the next data type).
|
||||
*
|
||||
* The conventions are usually mixed within the same symbol.
|
||||
* Since 'C' is both a qualifier and a data type, I suspect that
|
||||
* convention 1 allows specifying e.g. 'volatile signed char*'. In
|
||||
* convention 2 this would be 'CC' which is ambigious (i.e. Is it two
|
||||
* pointers, or a single pointer + modifier?). In convention 1 it
|
||||
* is encoded as '?CC' which is not ambigious. This probably
|
||||
* holds true for some other types as well.
|
||||
*/
|
||||
|
||||
/*******************************************************************
|
||||
* get_constraints_convention_1
|
||||
*
|
||||
* Get type constraint information for a data type
|
||||
*/
|
||||
static char *get_constraints_convention_1 (char **str, compound_type *ct)
|
||||
{
|
||||
char *iter = *str, **retval = str;
|
||||
|
||||
if (ct->have_qualifiers)
|
||||
return (char *)*str; /* Previously got constraints for this type */
|
||||
|
||||
if (*iter == '?' || *iter == 'A')
|
||||
{
|
||||
ct->have_qualifiers = 1;
|
||||
ct->flags |= (*iter++ == '?' ? 0 : CT_BY_REFERENCE);
|
||||
|
||||
switch (*iter++)
|
||||
{
|
||||
case 'A' :
|
||||
break; /* non-const, non-volatile */
|
||||
case 'B' :
|
||||
ct->flags |= CT_CONST;
|
||||
break;
|
||||
case 'C' :
|
||||
ct->flags |= CT_VOLATILE;
|
||||
break;
|
||||
default :
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return (char *)(*retval = iter);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* get_constraints_convention_2
|
||||
*
|
||||
* Get type constraint information for a data type
|
||||
*/
|
||||
static char *get_constraints_convention_2 (char **str, compound_type *ct)
|
||||
{
|
||||
char *iter = *str, **retval = str;
|
||||
|
||||
/* FIXME: Why do arrays have both convention 1 & 2 constraints? */
|
||||
if (ct->have_qualifiers && ct->dest_type != 'Q')
|
||||
return (char *)*str; /* Previously got constraints for this type */
|
||||
|
||||
ct->have_qualifiers = 1; /* Even if none, we've got all we're getting */
|
||||
|
||||
switch (*iter)
|
||||
{
|
||||
case 'A' :
|
||||
if (iter[1] != 'A' && iter[1] != 'B' && iter[1] != 'C')
|
||||
iter++;
|
||||
break;
|
||||
case 'B' :
|
||||
ct->flags |= CT_CONST;
|
||||
iter++;
|
||||
break;
|
||||
case 'C' :
|
||||
/* See note above, if we find 'C' it is _not_ a signed char */
|
||||
ct->flags |= CT_VOLATILE;
|
||||
iter++;
|
||||
break;
|
||||
}
|
||||
|
||||
return (char *)(*retval = iter);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* get_type_string
|
||||
*
|
||||
* Return a string containing the name of a data type
|
||||
*/
|
||||
static char *get_type_string (const char c, const int constraints)
|
||||
{
|
||||
char *type_string;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'C': /* Signed char, fall through */
|
||||
case 'D': type_string = "char"; break;
|
||||
case 'E': type_string = "unsigned char"; break;
|
||||
case 'F': type_string = "short int"; break;
|
||||
case 'G': type_string = "unsigned short int"; break;
|
||||
case 'H': type_string = "int"; break;
|
||||
case 'I': type_string = "unsigned int"; break;
|
||||
case 'J': type_string = "long"; break;
|
||||
case 'K': type_string = "unsigned long"; break;
|
||||
case 'M': type_string = "float"; break;
|
||||
case 'N': type_string = "double"; break;
|
||||
case 'O': type_string = "long double"; break;
|
||||
case 'U':
|
||||
case 'V': type_string = "struct"; break;
|
||||
case 'X': return strdup ("void");
|
||||
case 'Z': return strdup ("...");
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return str_create (3, constraints & CT_CONST ? "const " :
|
||||
constraints & CT_VOLATILE ? "volatile " : "", type_string,
|
||||
constraints & CT_BY_REFERENCE ? " *" : "");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* get_type_constant
|
||||
*
|
||||
* Get the ARG_* constant for this data type
|
||||
*/
|
||||
static int get_type_constant (const char c, const int constraints)
|
||||
{
|
||||
/* Any reference type is really a pointer */
|
||||
if (constraints & CT_BY_REFERENCE)
|
||||
return ARG_POINTER;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':
|
||||
case 'J': case 'K':
|
||||
return ARG_LONG;
|
||||
case 'M':
|
||||
return -1; /* FIXME */
|
||||
case 'N': case 'O':
|
||||
return ARG_DOUBLE;
|
||||
case 'P': case 'Q':
|
||||
return ARG_POINTER;
|
||||
case 'U': case 'V':
|
||||
return ARG_STRUCT;
|
||||
case 'X':
|
||||
return ARG_VOID;
|
||||
case 'Z':
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* get_pointer_type_string
|
||||
*
|
||||
* Return a string containing 'pointer to expression'
|
||||
*/
|
||||
static char *get_pointer_type_string (compound_type *ct,
|
||||
const char *expression)
|
||||
{
|
||||
/* FIXME: set a compound flag for bracketing expression if needed */
|
||||
return str_create (3, ct->flags & CT_CONST ? "const " :
|
||||
ct->flags & CT_VOLATILE ? "volatile " : "", expression,
|
||||
ct->flags & CT_BY_REFERENCE ? " **" : " *");
|
||||
|
||||
}
|
|
@ -0,0 +1,548 @@
|
|||
/*
|
||||
* Code generation functions
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*/
|
||||
#include "specmaker.h"
|
||||
|
||||
/* Output files */
|
||||
static FILE *specfile = NULL;
|
||||
static FILE *hfile = NULL;
|
||||
static FILE *cfile = NULL;
|
||||
|
||||
static void output_spec_postamble (void);
|
||||
static void output_header_postamble (void);
|
||||
static void output_c_postamble (void);
|
||||
static void output_prototype (FILE *file, const parsed_symbol *sym);
|
||||
static void output_c_banner (const parsed_symbol *sym);
|
||||
static const char *get_format_str (int type);
|
||||
static const char *get_in_or_out (const parsed_symbol *sym, size_t arg);
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_spec_preamble
|
||||
*
|
||||
* Write the first part of the .spec file
|
||||
*/
|
||||
void output_spec_preamble (void)
|
||||
{
|
||||
specfile = open_file (OUTPUT_DLL_NAME, ".spec", "w");
|
||||
|
||||
atexit (output_spec_postamble);
|
||||
|
||||
if (VERBOSE)
|
||||
puts ("Creating .spec preamble");
|
||||
|
||||
fprintf (specfile,
|
||||
"# Generated from %s.dll by specmaker\nname %s\n"
|
||||
"type win32\ninit %s_Init\n\nimport kernel32.dll\n"
|
||||
"import ntdll.dll\n", globals.input_name, OUTPUT_DLL_NAME,
|
||||
OUTPUT_UC_DLL_NAME);
|
||||
|
||||
if (globals.forward_dll)
|
||||
fprintf (specfile,"#import %s.dll\n", globals.forward_dll);
|
||||
|
||||
fprintf (specfile, "\n\ndebug_channels (%s)\n\n", OUTPUT_DLL_NAME);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_spec_symbol
|
||||
*
|
||||
* Write a symbol to the .spec file
|
||||
*/
|
||||
void output_spec_symbol (const parsed_symbol *sym)
|
||||
{
|
||||
assert (specfile);
|
||||
assert (sym && sym->symbol);
|
||||
|
||||
if (!globals.do_code || !sym->function_name)
|
||||
{
|
||||
if (globals.forward_dll)
|
||||
fprintf (specfile, "@ forward %s %s.%s\n", sym->symbol,
|
||||
globals.forward_dll, sym->symbol);
|
||||
else
|
||||
{
|
||||
if (!symbol_is_valid_c (sym))
|
||||
fputc ('#', specfile);
|
||||
fprintf (specfile, "@ stub %s\n", sym->symbol);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
fprintf (specfile, "@ %s %s(", sym->varargs ? "varargs" :
|
||||
symbol_is_cdecl (sym) ? "cdecl" : "stdcall", sym->symbol);
|
||||
|
||||
for (i = 0; i < sym->argc; i++)
|
||||
fprintf (specfile, " %s", symbol_get_spec_type(sym, i));
|
||||
|
||||
if (sym->argc)
|
||||
fputc (' ', specfile);
|
||||
fprintf (specfile, ") %s_%s\n", OUTPUT_UC_DLL_NAME, sym->function_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_spec_postamble
|
||||
*
|
||||
* Write the last part of the .spec file
|
||||
*/
|
||||
static void output_spec_postamble (void)
|
||||
{
|
||||
if (specfile)
|
||||
fclose (specfile);
|
||||
specfile = NULL;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_header_preamble
|
||||
*
|
||||
* Write the first part of the .h file
|
||||
*/
|
||||
void output_header_preamble (void)
|
||||
{
|
||||
hfile = open_file (OUTPUT_DLL_NAME, "_dll.h", "w");
|
||||
|
||||
atexit (output_header_postamble);
|
||||
|
||||
fprintf (hfile,
|
||||
"/*\n * %s.dll\n *\n * Generated from %s.dll by specmaker.\n *\n"
|
||||
" * DO NOT SEND GENERATED DLLS FOR INCLUSION INTO WINE !\n * \n */"
|
||||
"\n#ifndef __WINE_%s_DLL_H\n#define __WINE_%s_DLL_H\n\n#include "
|
||||
"\"config.h\"\n#include \"windef.h\"\n#include \"debugtools.h\"\n"
|
||||
"#include \"winbase.h\"\n#include \"winnt.h\"\n\n\n",
|
||||
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_UC_DLL_NAME,
|
||||
OUTPUT_UC_DLL_NAME);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_header_symbol
|
||||
*
|
||||
* Write a symbol to the .h file
|
||||
*/
|
||||
void output_header_symbol (const parsed_symbol *sym)
|
||||
{
|
||||
assert (hfile);
|
||||
assert (sym && sym->symbol);
|
||||
|
||||
if (!globals.do_code)
|
||||
return;
|
||||
|
||||
if (!sym->function_name)
|
||||
fprintf (hfile, "/* %s %s_%s(); */\n", CALLING_CONVENTION,
|
||||
OUTPUT_UC_DLL_NAME, sym->symbol);
|
||||
else
|
||||
{
|
||||
output_prototype (hfile, sym);
|
||||
fputs (";\n", hfile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_header_postamble
|
||||
*
|
||||
* Write the last part of the .h file
|
||||
*/
|
||||
static void output_header_postamble (void)
|
||||
{
|
||||
if (hfile)
|
||||
{
|
||||
fprintf (hfile, "\n\n\n#endif\t/* __WINE_%s_DLL_H */\n",
|
||||
OUTPUT_UC_DLL_NAME);
|
||||
fclose (hfile);
|
||||
hfile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_c_preamble
|
||||
*
|
||||
* Write the first part of the .c file
|
||||
*/
|
||||
void output_c_preamble (void)
|
||||
{
|
||||
cfile = open_file (OUTPUT_DLL_NAME, "_main.c", "w");
|
||||
|
||||
atexit (output_c_postamble);
|
||||
|
||||
fprintf (cfile,
|
||||
"/*\n * %s.dll\n *\n * Generated from %s.dll by specmaker.\n *\n"
|
||||
" * DO NOT SUBMIT GENERATED DLLS FOR INCLUSION INTO WINE!\n * \n */"
|
||||
"\n\n#include \"%s_dll.h\"\n\nDEFAULT_DEBUG_CHANNEL(%s);\n\n",
|
||||
OUTPUT_DLL_NAME, globals.input_name, OUTPUT_DLL_NAME,
|
||||
OUTPUT_DLL_NAME);
|
||||
|
||||
if (globals.forward_dll)
|
||||
{
|
||||
if (VERBOSE)
|
||||
puts ("Creating a forwarding DLL");
|
||||
|
||||
fputs ("\nHMODULE hDLL=0;\t/* DLL to call */\n\n\n", cfile);
|
||||
}
|
||||
|
||||
fprintf (cfile,
|
||||
"BOOL WINAPI %s_Init(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID "
|
||||
"lpvReserved)\n{\n\tTRACE(\"(0x%%08x, %%ld, %%p)\\n\",hinstDLL,"
|
||||
"fdwReason,lpvReserved);\n\n\t"
|
||||
"if (fdwReason == DLL_PROCESS_ATTACH)\n\t{\n\t\t",
|
||||
OUTPUT_UC_DLL_NAME);
|
||||
|
||||
if (globals.forward_dll)
|
||||
{
|
||||
fprintf (cfile,
|
||||
"hDLL = LoadLibraryA( \"%s\" );\n\t\t"
|
||||
"TRACE(\":Forwarding DLL (%s) loaded (%%ld)\\n\",(LONG)hDLL);",
|
||||
globals.forward_dll, globals.forward_dll);
|
||||
}
|
||||
else
|
||||
fputs ("/* FIXME: Initialisation */", cfile);
|
||||
|
||||
fputs ("\n\t}\n\telse if (fdwReason == DLL_PROCESS_DETACH)\n\t{\n\t\t",
|
||||
cfile);
|
||||
|
||||
if (globals.forward_dll)
|
||||
{
|
||||
fprintf (cfile,
|
||||
"FreeLibrary( hDLL );\n\t\tTRACE(\":Forwarding DLL (%s)"
|
||||
" freed\\n\");", globals.forward_dll);
|
||||
}
|
||||
else
|
||||
fputs ("/* FIXME: Cleanup */", cfile);
|
||||
|
||||
fputs ("\n\t}\n\n\treturn TRUE;\n}\n\n\n", cfile);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_c_symbol
|
||||
*
|
||||
* Write a symbol to the .c file
|
||||
*/
|
||||
void output_c_symbol (const parsed_symbol *sym)
|
||||
{
|
||||
unsigned int i;
|
||||
int is_void;
|
||||
|
||||
assert (cfile);
|
||||
assert (sym && sym->symbol);
|
||||
|
||||
if (!globals.do_code)
|
||||
return;
|
||||
|
||||
output_c_banner(sym);
|
||||
|
||||
if (!sym->function_name)
|
||||
{
|
||||
/* #ifdef'd dummy */
|
||||
fprintf (cfile, "#if 0\n%s %s_%s()\n{\n\t%s in .spec */\n}\n#endif\n\n\n",
|
||||
CALLING_CONVENTION, OUTPUT_UC_DLL_NAME, sym->symbol,
|
||||
globals.forward_dll ? "/* @forward" : "/* @stub");
|
||||
return;
|
||||
}
|
||||
|
||||
is_void = !strcmp (sym->return_text, "void");
|
||||
|
||||
output_prototype (cfile, sym);
|
||||
fputs ("\n{\n", cfile);
|
||||
|
||||
if (!globals.do_trace)
|
||||
{
|
||||
fputs ("\tFIXME(\":stub\\n\");\n", cfile);
|
||||
if (!is_void)
|
||||
fprintf (cfile, "\treturn (%s) 0;\n", sym->return_text);
|
||||
fputs ("}\n\n\n", cfile);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Tracing, maybe forwarding as well */
|
||||
if (globals.forward_dll)
|
||||
{
|
||||
/* Write variables for calling */
|
||||
fprintf (cfile, "\t%s (%s *pFunc)(", sym->return_text,
|
||||
sym->calling_convention);
|
||||
|
||||
for (i = 0; i < sym->argc; i++)
|
||||
fprintf (cfile, "%s%s", i ? ", " : "", sym->arg_text [i]);
|
||||
|
||||
fprintf (cfile, "%s)=(void*)GetProcAddress(hDLL,\"%s\");\n%s",
|
||||
sym->varargs ? ",..." : sym->argc ? "" : "void", sym->symbol,
|
||||
sym->varargs ? "\tva_list valist;\n" : "");
|
||||
|
||||
if (!is_void)
|
||||
fprintf (cfile, "\t%s retVal;\n", sym->return_text);
|
||||
}
|
||||
|
||||
/* TRACE input arguments */
|
||||
fprintf (cfile, "\tTRACE(\"(%s", !sym->argc ? "void" : "");
|
||||
|
||||
for (i = 0; i < sym->argc; i++)
|
||||
fprintf (cfile, "%s(%s)%s", i ? "," : "", sym->arg_text [i],
|
||||
get_format_str (sym->arg_type [i]));
|
||||
|
||||
fprintf (cfile, "%s): %s\\n\"", sym->varargs ? ",..." : "",
|
||||
globals.forward_dll ? "forward" : "stub");
|
||||
|
||||
for (i = 0; i < sym->argc; i++)
|
||||
if (sym->arg_type[i] != ARG_STRUCT)
|
||||
fprintf(cfile, ",%s%s%s%s", sym->arg_type[i] == ARG_LONG ? "(LONG)" : "",
|
||||
sym->arg_type[i] == ARG_WIDE_STRING ? "debugstr_w(" : "",
|
||||
sym->arg_name[i],
|
||||
sym->arg_type[i] == ARG_WIDE_STRING ? ")" : "");
|
||||
|
||||
fputs (");\n", cfile);
|
||||
|
||||
if (!globals.forward_dll)
|
||||
{
|
||||
if (!is_void)
|
||||
fprintf (cfile, "\treturn (%s) 0;\n", sym->return_text);
|
||||
fputs ("}\n\n\n", cfile);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Call the DLL */
|
||||
if (sym->varargs)
|
||||
fprintf (cfile, "\tva_start(valist,%s);\n", sym->arg_name[sym->argc-1]);
|
||||
|
||||
fprintf (cfile, "\t%spFunc(", !is_void ? "retVal = " : "");
|
||||
|
||||
for (i = 0; i < sym->argc; i++)
|
||||
fprintf (cfile, "%s%s", i ? "," : "", sym->arg_name [i]);
|
||||
|
||||
fputs (sym->varargs ? ",valist);\n\tva_end(valist);" : ");", cfile);
|
||||
|
||||
/* TRACE return value */
|
||||
fprintf (cfile, "\n\tTRACE(\"Returned (%s)\\n\"",
|
||||
get_format_str (sym->return_type));
|
||||
|
||||
if (!is_void)
|
||||
{
|
||||
if (sym->return_type == ARG_WIDE_STRING)
|
||||
fputs (",debugstr_w(retVal)", cfile);
|
||||
else
|
||||
fprintf (cfile, ",%s%s", sym->return_type == ARG_LONG ? "(LONG)" : "",
|
||||
sym->return_type == ARG_STRUCT ? "" : "retVal");
|
||||
fputs (");\n\treturn retVal;\n", cfile);
|
||||
}
|
||||
else
|
||||
fputs (");\n", cfile);
|
||||
|
||||
fputs ("}\n\n\n", cfile);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_c_postamble
|
||||
*
|
||||
* Write the last part of the .c file
|
||||
*/
|
||||
static void output_c_postamble (void)
|
||||
{
|
||||
if (cfile)
|
||||
fclose (cfile);
|
||||
cfile = NULL;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_makefile
|
||||
*
|
||||
* Write a Wine compatable makefile.in
|
||||
*/
|
||||
void output_makefile (void)
|
||||
{
|
||||
FILE *makefile = open_file ("Makefile", ".in", "w");
|
||||
|
||||
if (VERBOSE)
|
||||
puts ("Creating makefile");
|
||||
|
||||
fprintf (makefile,
|
||||
"# Generated from %s.dll by specmaker.\nTOPSRCDIR = @top_srcdir@\n"
|
||||
"TOPOBJDIR = ../..\nSRCDIR = @srcdir@\nVPATH = @srcdir@\n"
|
||||
"MODULE = %s\nEXTRALIBS = $(LIBUNICODE)\n\n"
|
||||
"LDDLLFLAGS = @LDDLLFLAGS@\nSYMBOLFILE = $(MODULE).tmp.o\n\n"
|
||||
"C_SRCS = \\\n\t%s_main.c\n\n@MAKE_DLL_RULES@\n\n### Dependencies:",
|
||||
globals.input_name, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME);
|
||||
|
||||
fclose (makefile);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_install_script
|
||||
*
|
||||
* Write a script to insert the DLL into Wine
|
||||
*
|
||||
* Rather than using diff/patch, several sed calls are generated
|
||||
* so the script can be re-run at any time without breaking.
|
||||
*/
|
||||
void output_install_script (void)
|
||||
{
|
||||
char cmd[128];
|
||||
FILE *install_file = open_file (OUTPUT_DLL_NAME, "_install", "w");
|
||||
|
||||
if (VERBOSE)
|
||||
puts ("Creating install script");
|
||||
|
||||
fprintf (install_file,
|
||||
"#!/bin/bash\n# Generated from %s.dll by specmaker.\n\n"
|
||||
"if [ $# -ne 1 ] || [ ! -d $1 ] || [ ! -f"
|
||||
" $1/AUTHORS ]; then\n\t[ $# -eq 1 ] && echo \"Invalid path\"\n"
|
||||
"\techo \"Usage: $0 wine-base-dir\"\n\texit 1\nfi\n\n"
|
||||
"if [ -d $1/dlls/%s ]; then\n\techo \"DLL is already present\"\n"
|
||||
"\texit 1\nfi\n\necho Adding DLL %s to Wine build tree...\n"
|
||||
"echo\n\nmkdir $1/dlls/%s\ncp %s.spec $1/dlls/%s\n"
|
||||
"cp %s_main.c $1/dlls/%s\ncp %s_dll.h $1/dlls/%s\n"
|
||||
"cp Makefile.in $1/dlls/%s\necho Copied DLL files\n\n"
|
||||
"cd $1\n\nsed '/dlls\\/"
|
||||
"x11drv\\/Makefile/{G;s/$/dlls\\/%s\\/Makefile/;}' configure.in"
|
||||
" >t.tmp\nmv -f t.tmp configure.in\necho Patched configure.in\n\n"
|
||||
"sed '/ws2_32/{G;s/$/\\^%s \\\\/;}' Make.rules.in | tr ^ \\\\t"
|
||||
" >t.tmp\nmv -f t.tmp Make.rules.in\necho Patched Make.rules.in"
|
||||
"\n\nsed '/DLLFILES =/{G;s/$/\\^%s\\/lib%s.so \\\\/;}'"
|
||||
" dlls/Makefile.in| tr ^ \\\\t >t.tmp\n"
|
||||
"sed '/SUBDIRS =/{G;s/$/\\^%s \\\\/;}' t.tmp | tr ^ \\\\t >t.tmp2"
|
||||
"\nsed '/Map library name /{G;s/$/^\\$(RM) \\$\\@ \\&\\& \\$\\"
|
||||
"(LN_S\\) %s\\/lib%s.\\@LIBEXT\\@ \\$\\@/;}' t.tmp2 | tr ^ \\\\t"
|
||||
" > t.tmp\nsed '/Map library name /{G;s/$/lib%s.\\@LIBEXT\\@: "
|
||||
"%s\\/lib%s.\\@LIBEXT\\@/;}' t.tmp > t.tmp2\nsed '/dll "
|
||||
"dependencies /{G;s/$/%s\\/lib%s.\\@LIBEXT\\@\\: libkernel32."
|
||||
"\\@LIBEXT\\@ libntdll.\\@LIBEXT\\@/;}' t.tmp2 > t.tmp\n"
|
||||
"mv -f t.tmp dlls/Makefile.in\nrm -f t.tmp2\necho Patched dlls/"
|
||||
"Makefile.in\n\necho\necho ...done.\necho Run \\'autoconf\\', "
|
||||
"\\'./configure\\' then \\'make\\' to rebuild Wine\n\n",
|
||||
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
|
||||
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
|
||||
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
|
||||
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
|
||||
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME,
|
||||
OUTPUT_DLL_NAME, OUTPUT_DLL_NAME, OUTPUT_DLL_NAME);
|
||||
|
||||
fclose (install_file);
|
||||
snprintf (cmd, sizeof (cmd), "chmod a+x %s_install", OUTPUT_DLL_NAME);
|
||||
system (cmd);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_prototype
|
||||
*
|
||||
* Write a C prototype for a parsed symbol
|
||||
*/
|
||||
static void output_prototype (FILE *file, const parsed_symbol *sym)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
fprintf (file, "%s %s %s_%s(", sym->return_text, sym->calling_convention,
|
||||
OUTPUT_UC_DLL_NAME, sym->function_name);
|
||||
|
||||
if (!sym->argc)
|
||||
fputs ("void", file);
|
||||
else
|
||||
for (i = 0; i < sym->argc; i++)
|
||||
fprintf (file, "%s%s %s", i ? ", " : "", sym->arg_text [i],
|
||||
sym->arg_name [i]);
|
||||
if (sym->varargs)
|
||||
fputs (", ...", file);
|
||||
fputc (')', file);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* output_c_banner
|
||||
*
|
||||
* Write a function banner to the .c file
|
||||
*/
|
||||
void output_c_banner (const parsed_symbol *sym)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
fprintf (cfile, "/*********************************************************"
|
||||
"*********\n *\t\t%s (%s.@)\n *\n", sym->symbol,
|
||||
OUTPUT_UC_DLL_NAME);
|
||||
|
||||
if (globals.do_documentation && sym->function_name)
|
||||
{
|
||||
fputs (" *\n * PARAMS\n *\n", cfile);
|
||||
|
||||
if (!sym->argc)
|
||||
fputs (" * None.\n *\n", cfile);
|
||||
else
|
||||
{
|
||||
for (i = 0; i < sym->argc; i++)
|
||||
fprintf (cfile, " * %s [%s]%s\n", sym->arg_name [i],
|
||||
get_in_or_out(sym, i),
|
||||
strcmp (sym->arg_name [i], "_this") ? "" :
|
||||
" Pointer to the class object");
|
||||
|
||||
if (sym->varargs)
|
||||
fputs (" * ...[I]\n", cfile);
|
||||
fputs (" *\n", cfile);
|
||||
}
|
||||
|
||||
fputs (" * RETURNS\n *\n", cfile);
|
||||
|
||||
if (sym->return_text && !strcmp (sym->return_text, "void"))
|
||||
fputs (" * Nothing.\n", cfile);
|
||||
else
|
||||
fprintf (cfile, " * %s\n", sym->return_text);
|
||||
}
|
||||
fputs (" *\n */\n", cfile);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* get_format_str
|
||||
*
|
||||
* Get a string containing the correct format string for a type
|
||||
*/
|
||||
static const char *get_format_str (int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ARG_VOID: return "void";
|
||||
case ARG_FLOAT: return "%f";
|
||||
case ARG_DOUBLE: return "%g";
|
||||
case ARG_POINTER: return "%p";
|
||||
case ARG_WIDE_STRING:
|
||||
case ARG_STRING: return "%s";
|
||||
case ARG_LONG: return "%ld";
|
||||
case ARG_STRUCT: return "struct";
|
||||
}
|
||||
assert (0);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* get_in_or_out
|
||||
*
|
||||
* Determin if a parameter is In or In/Out
|
||||
*/
|
||||
static const char *get_in_or_out (const parsed_symbol *sym, size_t arg)
|
||||
{
|
||||
assert (sym && arg < sym->argc);
|
||||
assert (globals.do_documentation);
|
||||
|
||||
if (sym->arg_flag [arg] & CT_CONST)
|
||||
return "In";
|
||||
|
||||
switch (sym->arg_type [arg])
|
||||
{
|
||||
case ARG_FLOAT:
|
||||
case ARG_DOUBLE:
|
||||
case ARG_LONG:
|
||||
case ARG_STRUCT: return "In";
|
||||
case ARG_POINTER:
|
||||
case ARG_WIDE_STRING:
|
||||
case ARG_STRING: return "In/Out";
|
||||
}
|
||||
assert (0);
|
||||
return "";
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* Prototype search and parsing functions
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*/
|
||||
#include "specmaker.h"
|
||||
|
||||
static char *grep_buff = NULL;
|
||||
static char *fgrep_buff = NULL;
|
||||
|
||||
static int symbol_from_prototype (parsed_symbol *sym, const char *prototype);
|
||||
static const char *get_type (parsed_symbol *sym, const char *proto, int arg);
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_search
|
||||
*
|
||||
* Call Patrik Stridvall's 'function_grep.pl' script to retrieve a
|
||||
* function prototype from include file(s)
|
||||
*/
|
||||
int symbol_search (parsed_symbol *sym)
|
||||
{
|
||||
static const size_t MAX_RESULT_LEN = 1024;
|
||||
FILE *grep;
|
||||
int attempt = 0;
|
||||
|
||||
assert (globals.do_code);
|
||||
assert (globals.directory);
|
||||
assert (sym && sym->symbol);
|
||||
|
||||
if (!symbol_is_valid_c (sym))
|
||||
return - 1;
|
||||
|
||||
if (!grep_buff)
|
||||
grep_buff = (char *) malloc (MAX_RESULT_LEN);
|
||||
|
||||
if (!fgrep_buff)
|
||||
fgrep_buff = (char *) malloc (MAX_RESULT_LEN);
|
||||
|
||||
if (!grep_buff || !fgrep_buff)
|
||||
fatal ("Out of Memory");
|
||||
|
||||
/* Use 'grep' to tell us which possible files the function is in,
|
||||
* then use 'function_grep.pl' to get the prototype. If this fails the
|
||||
* first time then give grep a more general query (that doesn't
|
||||
* require an opening argument brace on the line with the function name).
|
||||
*/
|
||||
while (attempt < 2)
|
||||
{
|
||||
FILE *f_grep;
|
||||
char *cmd = str_create (4, "grep -d recurse -l \"", sym->symbol,
|
||||
!attempt ? "[:blank:]*(\" " : "\" ", globals.directory);
|
||||
|
||||
if (VERBOSE)
|
||||
puts (cmd);
|
||||
|
||||
fflush (NULL); /* See 'man popen' */
|
||||
|
||||
if (!(grep = popen (cmd, "r")))
|
||||
fatal ("Cannot execute grep -l");
|
||||
free (cmd);
|
||||
|
||||
while (fgets (grep_buff, MAX_RESULT_LEN, grep))
|
||||
{
|
||||
int i;
|
||||
for (i = 0; grep_buff[i] && grep_buff[i] != '\n' ; i++)
|
||||
;
|
||||
grep_buff[i] = '\0';
|
||||
|
||||
if (VERBOSE)
|
||||
puts (grep_buff);
|
||||
|
||||
cmd = str_create (5, "function_grep.pl ", sym->symbol,
|
||||
" \"", grep_buff, "\"");
|
||||
|
||||
if (VERBOSE)
|
||||
puts (cmd);
|
||||
|
||||
fflush (NULL); /* See 'man popen' */
|
||||
|
||||
if (!(f_grep = popen (cmd, "r")))
|
||||
fatal ("Cannot execute function_grep.pl");
|
||||
free (cmd);
|
||||
|
||||
while (fgets (grep_buff, MAX_RESULT_LEN, f_grep))
|
||||
{
|
||||
char *iter = grep_buff;
|
||||
|
||||
/* Keep only the first line */
|
||||
symbol_clean_string(grep_buff);
|
||||
|
||||
for (i = 0; grep_buff[i] && grep_buff[i] != '\n' ; i++)
|
||||
;
|
||||
grep_buff[i] = '\0';
|
||||
|
||||
if (VERBOSE)
|
||||
puts (grep_buff);
|
||||
|
||||
while ((iter = strstr (iter, sym->symbol)))
|
||||
{
|
||||
if (iter > grep_buff && iter[-1] == ' ' &&
|
||||
(iter[strlen (sym->symbol)] == ' ' ||
|
||||
iter[strlen (sym->symbol)] == '('))
|
||||
{
|
||||
if (VERBOSE)
|
||||
puts ("Prototype looks OK, processing");
|
||||
|
||||
if (!symbol_from_prototype (sym, grep_buff))
|
||||
{
|
||||
pclose (f_grep);
|
||||
pclose (grep);
|
||||
return 0; /* OK */
|
||||
}
|
||||
if (VERBOSE)
|
||||
puts ("Failed, trying next");
|
||||
}
|
||||
else
|
||||
iter += strlen (sym->symbol);
|
||||
}
|
||||
}
|
||||
pclose (f_grep);
|
||||
}
|
||||
pclose (grep);
|
||||
attempt++;
|
||||
}
|
||||
|
||||
return -1; /* Not found */
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_from_prototype
|
||||
*
|
||||
* Convert a C prototype into a symbol
|
||||
*/
|
||||
static int symbol_from_prototype (parsed_symbol *sym, const char *proto)
|
||||
{
|
||||
char *iter;
|
||||
int found;
|
||||
|
||||
proto = get_type (sym, proto, -1); /* Get return type */
|
||||
if (!proto)
|
||||
return -1;
|
||||
|
||||
iter = (char *)str_match (proto, sym->symbol, &found);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
/* Calling Convention */
|
||||
iter = strchr (iter, ' ');
|
||||
if (!iter)
|
||||
return -1;
|
||||
|
||||
sym->calling_convention = str_substring (proto, iter);
|
||||
|
||||
iter = (char *)str_match (iter, sym->symbol, &found);
|
||||
|
||||
if (!found)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
sym->calling_convention = strdup (CALLING_CONVENTION);
|
||||
|
||||
sym->function_name = strdup (sym->symbol);
|
||||
proto = iter;
|
||||
|
||||
/* Now should be the arguments */
|
||||
if (*proto++ != '(')
|
||||
return -1;
|
||||
|
||||
for (; *proto == ' '; proto++);
|
||||
|
||||
if (!strncmp (proto, "void", 4))
|
||||
return 0;
|
||||
|
||||
do
|
||||
{
|
||||
/* Process next argument */
|
||||
str_match (proto, "...", &sym->varargs);
|
||||
if (sym->varargs)
|
||||
return 0;
|
||||
|
||||
if (!(proto = get_type (sym, proto, sym->argc)))
|
||||
return -1;
|
||||
|
||||
sym->argc++;
|
||||
|
||||
if (*proto == ',')
|
||||
proto++;
|
||||
else if (*proto != ')')
|
||||
return -1;
|
||||
|
||||
} while (*proto != ')');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* get_type
|
||||
*
|
||||
* Read a type from a prototype
|
||||
*/
|
||||
static const char *get_type (parsed_symbol *sym, const char *proto, int arg)
|
||||
{
|
||||
int is_const, is_volatile, is_struct, is_signed, is_unsigned, ptrs = 0;
|
||||
char *iter, *type_str, *base_type, *catch_unsigned, dest_type;
|
||||
|
||||
assert (sym && sym->symbol);
|
||||
assert (proto && *proto);
|
||||
assert (arg < 0 || (unsigned)arg == sym->argc);
|
||||
|
||||
type_str = (char *)proto;
|
||||
|
||||
proto = str_match (proto, "const", &is_const);
|
||||
proto = str_match (proto, "volatile", &is_volatile);
|
||||
proto = str_match (proto, "struct", &is_struct);
|
||||
if (!is_struct)
|
||||
proto = str_match (proto, "union", &is_struct);
|
||||
|
||||
catch_unsigned = (char *)proto;
|
||||
|
||||
proto = str_match (proto, "unsigned", &is_unsigned);
|
||||
proto = str_match (proto, "signed", &is_signed);
|
||||
|
||||
/* Can have 'unsigned const' or 'const unsigned' etc */
|
||||
if (!is_const)
|
||||
proto = str_match (proto, "const", &is_const);
|
||||
if (!is_volatile)
|
||||
proto = str_match (proto, "volatile", &is_volatile);
|
||||
|
||||
base_type = (char *)proto;
|
||||
iter = (char *)str_find_set (proto, " ,*)");
|
||||
if (!iter)
|
||||
return NULL;
|
||||
|
||||
if (arg < 0 && (is_signed || is_unsigned))
|
||||
{
|
||||
/* Prevent calling convention from being swallowed by 'un/signed' alone */
|
||||
if (strncmp (base_type, "int", 3) && strncmp (base_type, "long", 4) &&
|
||||
strncmp (base_type, "short", 5) && strncmp (base_type, "char", 4))
|
||||
{
|
||||
iter = (char *)proto;
|
||||
base_type = catch_unsigned;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch_unsigned = NULL;
|
||||
|
||||
/* FIXME: skip const/volatile here too */
|
||||
for (proto = iter; *proto; proto++)
|
||||
if (*proto == '*')
|
||||
ptrs++;
|
||||
else if (*proto != ' ')
|
||||
break;
|
||||
|
||||
if (!*proto)
|
||||
return NULL;
|
||||
|
||||
type_str = str_substring (type_str, proto);
|
||||
if (iter == base_type || catch_unsigned)
|
||||
{
|
||||
/* 'unsigned' with no type */
|
||||
char *tmp = str_create (2, type_str, " int");
|
||||
free (type_str);
|
||||
type_str = tmp;
|
||||
}
|
||||
symbol_clean_string (type_str);
|
||||
|
||||
dest_type = symbol_get_type (type_str);
|
||||
|
||||
if (arg < 0)
|
||||
{
|
||||
sym->return_text = type_str;
|
||||
sym->return_type = dest_type;
|
||||
}
|
||||
else
|
||||
{
|
||||
sym->arg_type [arg] = dest_type;
|
||||
sym->arg_flag [arg] = is_const ? CT_CONST : is_volatile ? CT_VOLATILE : 0;
|
||||
|
||||
if (*proto == ',' || *proto == ')')
|
||||
sym->arg_name [arg] = str_create_num (1, arg, "arg");
|
||||
else
|
||||
{
|
||||
iter = (char *)str_find_set (proto, " ,)");
|
||||
if (!iter)
|
||||
{
|
||||
free (type_str);
|
||||
return NULL;
|
||||
}
|
||||
sym->arg_name [arg] = str_substring (proto, iter);
|
||||
proto = iter;
|
||||
}
|
||||
sym->arg_text [arg] = type_str;
|
||||
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __GNUC__
|
||||
/*******************************************************************
|
||||
* search_cleanup
|
||||
*
|
||||
* Free memory used while searching (a niceity)
|
||||
*/
|
||||
void search_cleanup (void) __attribute__ ((destructor));
|
||||
void search_cleanup (void)
|
||||
{
|
||||
if (grep_buff)
|
||||
free (grep_buff);
|
||||
|
||||
if (fgrep_buff)
|
||||
free (fgrep_buff);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Specmaker - A Wine DLL tool
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*
|
||||
* References:
|
||||
* DLL symbol extraction based on file format from alib (anthonyw.cjb.net).
|
||||
*
|
||||
* Option processing shamelessly cadged from winebuild (www.winehq.com).
|
||||
*
|
||||
* All the cool functionality (prototyping, call tracing, forwarding)
|
||||
* relies on Patrik Stridvall's 'function_grep.pl' script to work.
|
||||
*
|
||||
* http://msdn.microsoft.com/library/periodic/period96/msj/S330.htm
|
||||
* This article provides both a description and freely downloadble
|
||||
* implementation, in source code form, of how to extract symbols
|
||||
* from Win32 PE executables/DLL's.
|
||||
*
|
||||
* http://www.kegel.com/mangle.html
|
||||
* Gives information on the name mangling scheme used by MS compilers,
|
||||
* used as the starting point for the code here. Contains a few
|
||||
* mistakes and some incorrect assumptions, but the lists of types
|
||||
* are pure gold.
|
||||
*/
|
||||
#ifndef __WINE_SPECMAKER_H
|
||||
#define __WINE_SPECMAKER_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* Argument type constants */
|
||||
#define MAX_FUNCTION_ARGS 32
|
||||
|
||||
#define ARG_VOID 0x0
|
||||
#define ARG_STRING 0x1
|
||||
#define ARG_WIDE_STRING 0x2
|
||||
#define ARG_POINTER 0x3
|
||||
#define ARG_LONG 0x4
|
||||
#define ARG_DOUBLE 0x5
|
||||
#define ARG_STRUCT 0x6 /* By value */
|
||||
#define ARG_FLOAT 0x7
|
||||
#define ARG_VARARGS 0x8
|
||||
|
||||
/* Compound type flags */
|
||||
#define CT_BY_REFERENCE 0x1
|
||||
#define CT_VOLATILE 0x2
|
||||
#define CT_CONST 0x4
|
||||
|
||||
/* Structure holding a parsed symbol */
|
||||
typedef struct __parsed_symbol
|
||||
{
|
||||
char *symbol;
|
||||
char *return_text;
|
||||
char return_type;
|
||||
char *calling_convention;
|
||||
char *function_name;
|
||||
unsigned int varargs;
|
||||
unsigned int argc;
|
||||
char arg_type [MAX_FUNCTION_ARGS];
|
||||
char arg_flag [MAX_FUNCTION_ARGS];
|
||||
char *arg_text [MAX_FUNCTION_ARGS];
|
||||
char *arg_name [MAX_FUNCTION_ARGS];
|
||||
} parsed_symbol;
|
||||
|
||||
/* All globals */
|
||||
typedef struct __globals
|
||||
{
|
||||
/* Options */
|
||||
int do_code; /* -c, -t, -f */
|
||||
int do_trace; /* -t, -f */
|
||||
int do_cdecl; /* -C */
|
||||
int do_quiet; /* -q */
|
||||
int do_verbose; /* -v */
|
||||
int do_documentation; /* -D */
|
||||
|
||||
/* Option arguments */
|
||||
int start_ordinal; /* -s */
|
||||
int end_ordinal; /* -e */
|
||||
const char *directory; /* -I */
|
||||
const char *input_name; /* -d */
|
||||
const char *forward_dll; /* -f */
|
||||
const char *dll_name; /* -o */
|
||||
char *uc_dll_name; /* -o */
|
||||
} _globals;
|
||||
|
||||
extern _globals globals;
|
||||
|
||||
/* Names to use for output DLL */
|
||||
#define OUTPUT_DLL_NAME \
|
||||
(globals.dll_name ? globals.dll_name : globals.input_name)
|
||||
#define OUTPUT_UC_DLL_NAME globals.uc_dll_name
|
||||
|
||||
/* Verbosity levels */
|
||||
#define QUIET (globals.do_quiet)
|
||||
#define NORMAL (!QUIET)
|
||||
#define VERBOSE (globals.do_verbose)
|
||||
|
||||
/* Default calling convention */
|
||||
#define CALLING_CONVENTION (globals.do_cdecl ? "__cdecl" : "__stdcall")
|
||||
|
||||
|
||||
/* DLL functions */
|
||||
void dll_open (const char *dll_name);
|
||||
|
||||
char *dll_next_symbol (void);
|
||||
|
||||
/* Symbol functions */
|
||||
int symbol_demangle (parsed_symbol *symbol);
|
||||
|
||||
int symbol_search (parsed_symbol *symbol);
|
||||
|
||||
void symbol_clear(parsed_symbol *sym);
|
||||
|
||||
int symbol_is_valid_c(const parsed_symbol *sym);
|
||||
|
||||
int symbol_is_cdecl(const parsed_symbol *sym);
|
||||
|
||||
const char *symbol_get_spec_type (const parsed_symbol *sym, size_t arg);
|
||||
|
||||
void symbol_clean_string (const char *string);
|
||||
|
||||
int symbol_get_type (const char *string);
|
||||
|
||||
/* Output functions */
|
||||
void output_spec_preamble (void);
|
||||
|
||||
void output_spec_symbol (const parsed_symbol *sym);
|
||||
|
||||
void output_header_preamble (void);
|
||||
|
||||
void output_header_symbol (const parsed_symbol *sym);
|
||||
|
||||
void output_c_preamble (void);
|
||||
|
||||
void output_c_symbol (const parsed_symbol *sym);
|
||||
|
||||
void output_makefile (void);
|
||||
|
||||
void output_install_script (void);
|
||||
|
||||
/* Misc functions */
|
||||
char *str_create (size_t num_str, ...);
|
||||
|
||||
char *str_create_num (size_t num_str, int num, ...);
|
||||
|
||||
char *str_substring(const char *start, const char *end);
|
||||
|
||||
char *str_replace (char *str, const char *oldstr, const char *newstr);
|
||||
|
||||
const char *str_match (const char *str, const char *match, int *found);
|
||||
|
||||
const char *str_find_set (const char *str, const char *findset);
|
||||
|
||||
char *str_toupper (char *str);
|
||||
|
||||
FILE *open_file (const char *name, const char *ext, const char *mode);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void do_usage (void) __attribute__ ((noreturn));
|
||||
#else
|
||||
void do_usage (void);
|
||||
#endif
|
||||
|
||||
void fatal (const char *message);
|
||||
|
||||
|
||||
#endif /* __WINE_SPECMAKER_H */
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Symbol functions
|
||||
*
|
||||
* Copyright 2000 Jon Griffiths
|
||||
*/
|
||||
#include "specmaker.h"
|
||||
|
||||
|
||||
/* Items that are swapped in arguments after the symbol structure
|
||||
* has been populated
|
||||
*/
|
||||
static const char *swap_after[] =
|
||||
{
|
||||
"\r", " ", /* Remove whitespace, normalise pointers and brackets */
|
||||
"\t", " ",
|
||||
" ", " ",
|
||||
" * ", " *",
|
||||
"* *", "**",
|
||||
"* ", "*",
|
||||
" ,", ",",
|
||||
"( ", "(",
|
||||
" )", ")",
|
||||
"wchar_t", "WCHAR", /* Help with Unicode compliles */
|
||||
"wctype_t", "WCHAR",
|
||||
"wint_t", "WCHAR",
|
||||
"unsigned __int64", "__uint64", /* Wine doesn't cope with unsigned i64's */
|
||||
NULL, NULL
|
||||
};
|
||||
|
||||
|
||||
/* Items containing these substrings are assumed to be wide character
|
||||
* strings, unless they contain more that one '*'. A preceeding 'LP'
|
||||
* counts as a '*', so 'LPWCSTR *' is a pointer, not a string
|
||||
*/
|
||||
static const char *wide_strings[] =
|
||||
{
|
||||
"WSTR", "WCSTR", NULL
|
||||
};
|
||||
|
||||
/* Items containing these substrings are assumed to be wide characters,
|
||||
* unless they contain one '*'. A preceeding 'LP' counts as a '*',
|
||||
* so 'WCHAR *' is string, while 'LPWCHAR *' is a pointer
|
||||
*/
|
||||
static const char *wide_chars[] =
|
||||
{
|
||||
"WCHAR", NULL
|
||||
};
|
||||
|
||||
/* Items containing these substrings are assumed to be ASCII character
|
||||
* strings, as above
|
||||
*/
|
||||
static const char *ascii_strings[] =
|
||||
{
|
||||
"STR", "CSTR", NULL
|
||||
};
|
||||
|
||||
|
||||
/* Items containing these substrings are assumed to be ASCII characters,
|
||||
* as above
|
||||
*/
|
||||
static const char *ascii_chars[] =
|
||||
{
|
||||
"CHAR", "char", NULL
|
||||
};
|
||||
|
||||
/* Any type other than the following will produce a FIXME warning with -v
|
||||
* when mapped to a long, to allow fixups
|
||||
*/
|
||||
static const char *known_longs[] =
|
||||
{
|
||||
"char", "CHAR", "float", "int", "INT", "short", "SHORT", "long", "LONG",
|
||||
"WCHAR", "BOOL", "bool", "INT16", NULL
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_clear
|
||||
*
|
||||
* Free the memory used by a symbol and initialise it
|
||||
*/
|
||||
void symbol_clear(parsed_symbol *sym)
|
||||
{
|
||||
int i;
|
||||
|
||||
assert (sym);
|
||||
assert (sym->symbol);
|
||||
|
||||
free (sym->symbol);
|
||||
|
||||
if (sym->return_text)
|
||||
free (sym->return_text);
|
||||
|
||||
if (sym->calling_convention)
|
||||
free (sym->calling_convention);
|
||||
|
||||
if (sym->function_name)
|
||||
free (sym->function_name);
|
||||
|
||||
for (i = sym->argc - 1; i >= 0; i--)
|
||||
{
|
||||
if (sym->arg_text [i])
|
||||
free (sym->arg_text [i]);
|
||||
if (sym->arg_name [i])
|
||||
free (sym->arg_name [i]);
|
||||
}
|
||||
memset (sym, 0, sizeof (parsed_symbol));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_is_valid_c
|
||||
*
|
||||
* Check if a symbol is a valid C identifier
|
||||
*/
|
||||
int symbol_is_valid_c(const parsed_symbol *sym)
|
||||
{
|
||||
char *name;
|
||||
|
||||
assert (sym);
|
||||
assert (sym->symbol);
|
||||
|
||||
name = sym->symbol;
|
||||
|
||||
while (*name)
|
||||
{
|
||||
if (!isalnum (*name) && *name != '_')
|
||||
return 0;
|
||||
name++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_is_cdecl
|
||||
*
|
||||
* Check if a symbol is cdecl
|
||||
*/
|
||||
int symbol_is_cdecl(const parsed_symbol *sym)
|
||||
{
|
||||
assert (sym);
|
||||
assert (sym->symbol);
|
||||
|
||||
if (sym->calling_convention && (strstr (sym->calling_convention, "cdecl")
|
||||
|| strstr (sym->calling_convention, "CDECL")))
|
||||
return 1;
|
||||
else if (!sym->calling_convention)
|
||||
return globals.do_cdecl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_get_spec_type
|
||||
*
|
||||
* Get the .spec file text for a symbols argument
|
||||
*/
|
||||
const char *symbol_get_spec_type (const parsed_symbol *sym, size_t arg)
|
||||
{
|
||||
assert (arg < sym->argc);
|
||||
switch (sym->arg_type [arg])
|
||||
{
|
||||
case ARG_STRING: return "str";
|
||||
case ARG_WIDE_STRING: return "wstr";
|
||||
case ARG_POINTER: return "ptr";
|
||||
case ARG_DOUBLE: return "double";
|
||||
case ARG_STRUCT:
|
||||
case ARG_FLOAT:
|
||||
case ARG_LONG: return "long";
|
||||
}
|
||||
assert (0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_get_type
|
||||
*
|
||||
* Get the ARG_ constant for a type string
|
||||
*/
|
||||
int symbol_get_type (const char *string)
|
||||
{
|
||||
const char *iter = string;
|
||||
const char **tab;
|
||||
int ptrs = 0;
|
||||
|
||||
while (*iter)
|
||||
{
|
||||
if (*iter == '*' || (*iter == 'L' && iter[1] == 'P')
|
||||
|| (*iter == '[' && iter[1] == ']'))
|
||||
ptrs++;
|
||||
if (ptrs > 1)
|
||||
return ARG_POINTER;
|
||||
iter++;
|
||||
}
|
||||
|
||||
/* 0 or 1 pointer */
|
||||
tab = wide_strings;
|
||||
while (*tab++)
|
||||
if (strstr (string, tab[-1]))
|
||||
{
|
||||
if (!ptrs) return ARG_WIDE_STRING;
|
||||
else return ARG_POINTER;
|
||||
}
|
||||
tab = wide_chars;
|
||||
while (*tab++)
|
||||
if (strstr (string, tab[-1]))
|
||||
{
|
||||
if (!ptrs) return ARG_LONG;
|
||||
else return ARG_WIDE_STRING;
|
||||
}
|
||||
tab = ascii_strings;
|
||||
while (*tab++)
|
||||
if (strstr (string, tab[-1]))
|
||||
{
|
||||
if (!ptrs) return ARG_STRING;
|
||||
else return ARG_POINTER;
|
||||
}
|
||||
tab = ascii_chars;
|
||||
while (*tab++)
|
||||
if (strstr (string, tab[-1]))
|
||||
{
|
||||
if (!ptrs) return ARG_LONG;
|
||||
else {
|
||||
if (!strstr (string, "unsigned")) /* unsigned char * => ptr */
|
||||
return ARG_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
if (ptrs)
|
||||
return ARG_POINTER; /* Pointer to some other type */
|
||||
|
||||
/* No pointers */
|
||||
if (strstr (string, "double"))
|
||||
return ARG_DOUBLE;
|
||||
|
||||
if (strstr (string, "void"))
|
||||
return ARG_VOID;
|
||||
|
||||
if (strstr (string, "struct") || strstr (string, "union"))
|
||||
return ARG_STRUCT; /* Struct by value, ugh */
|
||||
|
||||
if (VERBOSE)
|
||||
{
|
||||
int known = 0;
|
||||
|
||||
tab = known_longs;
|
||||
while (*tab++)
|
||||
if (strstr (string, tab[-1]))
|
||||
{
|
||||
known = 1;
|
||||
break;
|
||||
}
|
||||
/* Unknown types passed by value can be 'grep'ed out for fixup later */
|
||||
if (!known)
|
||||
printf ("/* FIXME: By value type: Assumed 'int' */ typedef int %s;\n",
|
||||
string);
|
||||
}
|
||||
return ARG_LONG;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* symbol_clean_string
|
||||
*
|
||||
* Make a type string more Wine-friendly. Logically const :-)
|
||||
*/
|
||||
void symbol_clean_string (const char *string)
|
||||
{
|
||||
const char **tab = swap_after;
|
||||
char *str = (char *)string;
|
||||
|
||||
#define SWAP(i, p, x, y) do { i = p; while ((i = str_replace (i, x, y))); } while(0)
|
||||
|
||||
while (tab [0])
|
||||
{
|
||||
char *p;
|
||||
SWAP (p, str, tab [0], tab [1]);
|
||||
tab += 2;
|
||||
}
|
||||
if (str [strlen (str) - 1] == ' ')
|
||||
str [strlen (str) - 1] = '\0'; /* no trailing space */
|
||||
|
||||
if (*str == ' ')
|
||||
memmove (str, str + 1, strlen (str)); /* No leading spaces */
|
||||
}
|
Loading…
Reference in New Issue