A DLL is a Dynamic Link Library. This acronym means that the library is loaded when the program starts by the loader, and not linked statically with the program code like with a normal library.
To build such a
library:
1) Define your project as a DLL project. If you are using lcclnk directly in the command line, you should add the –DLL option to the linker.
This is done in
the project definition panel by checking the corresponding radio button.
2) In your code, mark the functions of the DLL you want to export as such.
An “exported”
function is a function that is visible outside the DLL, and can be called
from other places in the program. There are two ways of doing this.
The
first is by declaring it explicitly in the program text:
int __declspec(dllexport) MyFunction(int f) {
… }
This
instructs the compiler to add a special instruction for the linker to add this
name (in this case MyFunction ) to
the export table of the DLL.
The
second method is to build a definition file (.def) where you write the list of
exported functions from the DLL. This is just an ASCII text file whose
extension must be .def, with the list of the exported functions of the
DLL. An example of such a file is:
EXPORTS
MyFunction
The
first line must be the keyword EXPORTS, followed by the names of the exported
functions, one in each line.
The formal syntax is:
IDENTIFIER [DATA]
IDENTIFIER
is a placeholder for the name of the exported symbol. It can be followed by the
keyword DATA, which means that the exported symbol is not a function, but a
data item. This is important to specify, since the entries generated in the
import library are not the same.
3) You should build an import library.
This is done automatically by lcclnk. If
you want to do it manually, you should type the following command:
buildlib mydll.exp mydll.lib
The ‘buildlib’ utility takes
an ASCII description of the DLL exported entries in a similar format as the
.def described above, and will output a library that can be used with the
linker to build the finished product.[1]
4)
You
should declare the imported symbols in the executable that uses the DLL.
This means, add an “extern” declaration in your code or header
file.
5)
You
should link your executable with the import library.
You
are then finished.
To
debug your DLL with Wedit’s debugger, change the name of the executable to
start in the project configuration tab ‘Debugger’ and write the name of the
executable that uses your DLL. Then, follow the executable until a DLL function
call appears. Next, press F8 to enter the function in the DLL. Remember that
both the executable and the DLL should have been compiled with debug
information turned on!
DLLs
should have a DllMain or LibMain entry point function,
that is called up by the system when the DLL is loaded or unloaded, and allows
you to initialize/cleanup elements. This function MUST be declared as follows:
#include <windows.h>
/*----------------------------------------------------------------------
Procedure: LibMain
Purpose: DLL entry point called up when a DLL is loaded or
unloaded by a process, and when new threads are
created or destroyed.
Input: hDllInst: Instance
handle of the DLL
fdwReason: event: attach/detach
lpvReserved: not used
Output: The return
value is used only when the fdwReason is
DLL_PROCESS_ATTACH. True means
that the DLL has
successfully loaded. False means that the DLL is unable
to initialize and should be unloaded immediately.
Errors:
----------------------------------------------------------------------*/
BOOL WINAPI LibMain(HINSTANCE hDLL,DWORD Reason,LPVOID Reserved)
{
switch (Reason) // Code indicating the reason for this function being called up.
{
case DLL_PROCESS_ATTACH:
// The DLL is being loaded for the first time by a given
process.
// Perform per-process initialization here. If the initialization
// is successful, return TRUE; if unsuccessful, return FALSE.
break;
case DLL_PROCESS_DETACH:
// The DLL is being unloaded by a given process. Do any
// pre-process clean up here, such as undoing what was done
in
// DLL_PROCESS_ATTACH. The return value is ignored.
break;
case DLL_THREAD_ATTACH:
// A thread is being created in a process that has already
loaded
// this DLL.
Perform any per-thread initialization here. The
// return value is ignored.
break;
case DLL_THREAD_DETACH:
// A thread is exiting cleanly in a process that has already
// loaded this DLL.
Perform any per-thread clean up here.
The
// return
value is ignored.
break;
}
return TRUE;
}
This file is needed to define the exports of a DLL or mark the data sections of an executable as shared, i.e., usable by other programs.
The different directives in this file are:
· EXPORTS. This is followed by the exports list. The exports list should have an entrgy in each line, for instance: MyFunction or _MyFunction=Function, to change the name of the exported symbol.
· SECTION. This should be followed by one single line containing DATA SHARED, if you want to make the data sections (.data AND .bss) shared.
· STACKSIZE. This is ignored and is here for compatibility with older versions of the .def files.
· DESCRIPTION. This is ignored and is here for compatibility with older versions of the .def files.
An example of this is the following file:
Library Mydll
EXPORTS
Myfunction
_MyOtherFunction=Other
SECTION
DATA SHARED
The first line must contain the name of the DLL or file that you are building, but this is not required. The Exports section contains two exports: A function called “MyFunction”, and a function called “Other”, which is associated to the “MyOtherFunction” procedure, and which will not be visible with that name, since its exported name will be “Other”.
The SECTION part defines the data sections (.data and .bss sections) as shared, i.e., accessible by other programs.
Lcc-win32 understands special macro definitions called ‘intrinsics’. These constructs will be seen as normal function calls by the front end of the compiler, but will be inline expanded by the back-end. You can add your own intrinsic macros to the system, allowing you to use the power and speed of assembly language within the context of a more powerful and safer high level language.
Assume that the strlen function of the C library is too slow for your needs. Instead of generating:
pushl Arg
call _strlen
addl $4,%esp
you would like to generate inline the following code:
; Inlined strlen. The input
argument is in ECX and points to the
; character string
orl $-1,%eax
loop:
inc %eax
cmpb $0,(%ecx,%eax)
jnz loop
This function should then be inlined by the compiler. The C interface would be:
_strlen(str);
The prototype must be:
extern _stdcall
_strlen(char *);
The compiler recognizes intrinsic macros because they have an underscore as the first character of their names, they are declared _stdcall, and they appear in the intrinsics table. Functions that begin with an underscore are few, thereby avoiding the need to look up the intrinsics table for each function call, which would slow down compilation speed.
Take the file intrin.c in the sources of lcc-win32 and modify the intrinsics table. Its declaration is in the middle of the file and looks like this:
static INTRINSICS
intrinsicTable[] = {
{"_fsincos",2, 0, fsincos, NULL },
{"_bswap", 1, 0, bswap, bswapArgs },
many declarations omitted…
{"_reduceLtb",3, 0, redCmpLtb, paddArgs },
{"_mmxDotProduct",3,0, mmxDotProd, paddArgs },
{"_emms",0, 0, emms, NULL },
{NULL, 0, 0, 0, 0 }
};
Before the last line, add the following line:
{"_strlen",1, 0, strlenGen, strlenArgs },
telling the system that you want an intrinsic called “_strlen”, which takes one argument, whose code will be generated by the function strlenGen(), and the arguments assigned to their respective registers in the function strlenArgs(). These functions should assign the registers in which you want the arguments to the inline macro and generate the code for the body of the macro. Basically, this macros are seen as special calls by the compiler, which — instead of generating a push instruction — will call your <arguments> function, which should set the right fields in each node passed to it, to make the code generator later generate a move to the registers specified.
Note that all intrinsics should start with an underscore to avoid conflicting with user space names.
When a call to this function is detected by the compiler, you will first be called when “pushing” the arguments. Here is the function strlenArgs() then:
static Symbol
strlenArgs(Node p)
{
Symbol r=NULL;
//The global ArgumentsIndex is zero before each call. The
compiler
//takes care of that.
switch (ArgumentsIndex) {
case 0: // First argument pushed, from right to left!
if (p->x.nestedCall == 0) {
Symbol w;
r = SetRegister(p,intreg[ECX]);
}
break;
}
// We have seen another argument.
ArgumentsIndex++;
// Assign the register to this expression.
if (p->x.nestedCall == 0 && r)
p->syms[2] = r;
// Should never be more than one argument
if (ArgumentsIndex == 1)
ArgumentsIndex = 0;
return r;
}
You see that in
several places, there is a test:
if (p->x.nestedCall == 0)
This means that
there should be check for a nested call
sequence within the arguments, i.e., the following C expression:
strlen( SomeFunction() );
In the case of
strlen, this does not change anything important, the result of the function
will be in EAX anyway. But suppose that you defined a macro with two arguments,
for example, some special form of addition sadd(a,b).
In this case, you
would assign the second argument (from left to right) to ECX, and the first to
EAX. Consider then the case of:
sadd( SomeFunction(),5);
If you would
assign 5 to ECX, then the call SomeFunction(), would destroy the contents of
ECX during the call! This means that when the compiler detects a call within
argument passing, all arguments WILL BE in the stack, and your code generating
function should take care of placing them into the right registers before
proceeding. In the case of strlen, this could seldom occur, but its important
to see how this would work in general.
Note too that the
argument function should increase the global argument counter for each
argument, and reset it to zero when its done. Again, this is not necessary for
strlen, but for macros that use more arguments, this should be done each time.
The SetRegister function
assigns a register. Following is its short body:
Symbol SetRegister(Node
p,Symbol r)
{
Symbol w;
w = p->kids[0]->syms[2];
if (w->x.regnode == NULL || w->x.regnode->vbl == NULL)
p->kids[0]->syms[2] = r;
return r;
}
This function
tests that in the given node, the left child is not already assigned to a register. It will assign the register only if
this is not the case.
You then go to the center of the routine: Generating code for the strlen utility.
static Symbol
strlenGen(Node p)
{
static int
labelCount;
First, check if you should pop your arguments. If yes, pop them into the right registers.
if (p->x.nestedCall) {
print("\tpopl\t%%ecx\n");
}
Then generate the
code for the strlen routine. Note that the % sign is used by the assembler to
mark a register, but your print() function also uses it to mark (as printf) the
beginning of an argument. You must double them to circumvent this collision.
1) Set the
counter to minus one.
print("\torl\t$-1,%%eax\n");
2) Generate the
label for this instance. All labels must be unique. The easiest way to ensure
that a new label is generated is to number them consecutively using a counter.
To avoid colliding with other labels, use a unique prefix too.
print("_$strlen%d:\n",labelCount);
3) Then generate
the code for the body of the loop searching for the character zero.
print("\tinc\t%%eax\n");
4) Note the dollar before the immediate constant.
print("\tcmpb\t$0,(%%ecx,%%eax)\n");
5) Generate the jump,
incrementing your loop counter afterwards.
print("\tjnz\t_$strlen%d\n",labelCount++);
You are done; the
result is in eax, which is correct. Finish your function. Note that no pops are
needed, since the ones completed at the beginning (possibly) are only to
compensate for the pushes that the compiler generated.
}
Compile the compiler and obtain a new compiler that will recognize the macro just created.
In general, you can use ECX, EDX, and EAX as desired. The contents of EBX, ESI, EBP and EDI should always be saved. If you destroy them “unpredictable results” will surely occur.
Next, write a test function for your new compiler:
#include <stdio.h>
#ifdef MACRO
int _stdcall
_strlen(char *);
#define strlen _strlen
#else
int strlen(char *);
#endif
int main(int argc, char
*argv[])
{
if (argc > 1)
printf("Length of
\"%s\" is %d\n", argv[1],
strlen(argv[1]));
return 0;
}
In the C source, we use the conditional MACRO to signify if we should use our macro, or just generate a call to the normal strlen procedure. We compile this with our new compiler, and add the –S parameter to see what is being generated.
lcc –S –DMACRO tstrlen.c
The assembly is then:
_main:
pushl %ebp
movl %esp,%ebp
pushl %edi
.line 9
.line 10
cmpl $1,8(%ebp)
jle _$2
.line 11
movl
12(%ebp),%edi
; Your argument gets
assigned to ECX, as your strlenArgs function
; defined
movl 4(%edi),%ecx
; This is the beginning of
your macro body
orl $-1,%eax
; This is your generated
label
_$strlen0:
inc %eax
cmpb
$0,(%ecx,%eax)
jnz _$strlen0
; Your macro ends here, leaving
its results in EAX
pushl %eax
movl
12(%ebp),%edi
pushl 4(%edi)
pushl $_$4
call _printf
addl $12,%esp
_$2:
.line 12
xor %eax,%eax
.line 13
popl %edi
popl %ebp
ret
You can see that there is absolutely no call overhead. The arguments are assigned to the right registers in your function strlenArgs, and the body is expanded inline by strlenGen.
Then, link your executable:
D:\lcc\src74\test>lcclnk
tstrlen.obj
D:\lcc\src74\test>tstrlen
abcde
The length of
"abcde" is 5
D:\lcc\src74\test>
Here is the strlenGen() function again for clarity.
static void strlenGen(Node
p)
{
static int labelCount;
if (p->x.nestedCall) {
print("\tpopl\t%%ecx\n");
}
print("\torl\t$-1,%%eax\n");
print("_$strlen%d:\n",labelCount);
print("\tinc\t%%eax\n");
print("\tcmpb\t$0,(%%ecx,%%eax)\n");
print("\tjnz\t_$strlen%d\n",labelCount++);
}
To demonstrate a function with two arguments,
inline the strchr function. This function should return a pointer to the first
occurrence of the given character in a string, or NULL, if the character does
not appear in the string. The implementation could be like this:
_strchr:
movb (%eax),%dl // read a character
cmpb %cl,%dl // compare it to searched for char
je _strchrexit // exit if found with pointer to char
as result
incl %eax // move pointer to next char
orb %dl,%dl // test for end of string
jne strchr // if not zero continue loop
xorl %eax,%eax // Not found. Zero result
strchrexit :
Scan the characters looking for either zero
(end of the string) or the given char. The pointer to the string will be in
EAX, and the character to be searched for will be in ECX. Use EDX as a scratch
register. Next, write the strchr function for assigning the arguments. It is:
static Symbol
strchrArgs(Node p)
{
Symbol r=NULL;
switch (ArgumentsIndex) {
case 0: // First argument (from right to left) char to be
searched. Put it in ECX
if (p->x.nestedCall == 0) {
r = SetRegister(p,intreg[ECX]);
}
break;
case 1: // Second argument: pointer to the string. Put it
in EAX
if (p->x.nestedCall == 0) {
r = SetRegister(p,intreg[EAX]);
}
break;
}
ArgumentsIndex++;
if (p->x.nestedCall == 0)
p->syms[2] = r;
if (ArgumentsIndex == 2)
ArgumentsIndex = 0;
return r;
}
Next, write the generating function. Note that
two labels are needed. The function is:
static void
strchrGen(Node p)
{
static int labelCount;
if (p->x.nestedCall) {
print("\tpopl\t%%ecx\n");
}
print("_$strchr%d:\n",labelCount);
print("\tmovb\t(%%eax),%%dl\n");
print("\tcmpb\t%%cl,%%dl\n");
print("\tje\t_$strchr%d\n",labelCount+1);
print("\tinc\t%%eax\n");
print("\torb\t%%dl,%%dl\n");
print("\tjne\t_$strchr%d\n",labelCount);
print("\txorl\t%%eax,%%eax\n");
print("_$strchr%d:\n",labelCount+1);
labelCount += 2;
}
This utility
allows you to load an object file from disk and link it to a running executable.
This facility consists of the following parts:
·
The dynloader.dll file, which should be in your PATH variable
·
The bind utility
The dynloader.dll
exports three functions that are of interest here:
1. LoadObjectFile
2. GetProcedureAddress
3. UnloadObjectFile
The interface is
very similar to that of LoadLibrary, since the functionality is quite similar.
The dynamic
loader operates by opening the given object, allocating space in RAM for the
data and the code stored in there, copying the contents of the data and code
sections, then fixing up everything.
To resolve
external references, the loader relies first on the symbol table of the running
executable. If that symbol table exists, it will be used to resolve external
references from the object file.
If the symbol
table is absent in the running executable, or a symbol is found that does not
match any executable symbols, the dynamic loader will rely on the imports table
generated by the bind utility. This table contains the DLLs used by the object
file, and all the imported functions that are needed.
The bind utility
will construct this table from the “apilist.txt” file, which is present in the
lcc-win32 distribution in the “lib” directory. This file contains a list of all
known DLLs and their exported functions. Using this list, the bind utility
appends a list of needed DLLs to the object file, that will be used by the
dynloader.dll library.
Note that if you
wish to add a DLL of your own to the list, you can do so by modifying
apilist.txt.
Before loading an
object file it must be "bound" using the "bind.exe"
utility. The syntax of the "bind" command is very simple:
bind <object file>
This will build a
list of the DLLs needed by the object file so that it can be quickly loaded
later.
An object file is loaded using the
LoadObjectFile function. The result can be either NULL, meaning the object file
could not be found, or a pointer to an OBJECT_FILE structure, containing
information about the loaded object. Please note that the members of the
structure will probably change in future releases, so it is not a very good
idea to use them in your code. You can still look at them and they are provided
for debugging purposes.
This is accomplished with the GetProcedureAddress
function. The pointer returned (that can be NULL if the name is not found)
should be cast to match the function prototype. Note that the name of the
procedure to be found should match the name as exported from the object file,
i.e., you should add the leading underscore and the _stdcall decoration if any.
For instance for the function
void hello(void)
you should pass _hello to the loader. For a
_stdcall function, you should add ‘@’ and the size of the function stack.
This is accomplished using the UnloadObjectFile
function. All memory used by the object file and its descriptors is reclaimed.
You should ensure that no further uses of the object file occur.
#include
<dynloader.h>
int main(void)
{
OBJECT_FILE *obj =
LoadObjectFile("hello.obj");
void
(*fn)(void);
fn =
GetProcedureAddress(obj,"_hello");
(*fn)();
UnloadObjectFile(obj);
return 0;
}
Eiffel support is
still in a very early stage within the lcc-win32 system. Still, there are many
things that are working.
Wedit uses as
its Eiffel compiler, the implementation of Dominique Colnet and Olivier Zendra,
the “SmallEiffel” compiler. It is free of charge and can be downloaded from:
http://smalleiffel.loria.fr.
To build an
Eiffel project, you should add to your new project files with a .e extension. This will tell Wedit that
the compiler is ‘compile’, the name of the SmallEiffel compiler.
The
configuration is set up under the following tab:
|
You should enter
the name of the root class, and the name of the creation procedure for that
root class on which defaults to ‘make’. The name of each compiler option is the
same as the one used by the Eiffel compiler. Please look at the documentation
that comes with the compiler for more information.
The debugger
works with the generated executable, and the commands are the same as for the C
language: F2 to set a breakpoint, F4 to skip calls and advance to the next line,
and F8 to follow calls.
This means that the linker is missing the
definitions of a library or that you have forgotten to add a source file in the
list of sources. You should add the library where the definition of foo is
(ignore the leading underscore, and anything after the '@').
Go to the main menu in the 'options' menu bar.
There choose 'Configuration'. The Configuration tab opens. In this tab, choose
the 'Linker' tab. There you will find an entry field with 'Additional
Libraries'. Write in the name of your library. For lcc libraries, you do not
need to indicate the path. For other libraries you should indicate it.
This can happen when you use a _stdcall
function, but forget to include the corresponding header file. For instance, if
you call CreateWindow without include <windows.h> you will get an
undefined reference. Please include the corresponding header file.
4)
How do I call an external program from the IDE?
In the 'Options' menu there is an option 'Add utility'. Use this.
5)
How can I determine in which library the function 'xxx' is located?
Go to the 'Search' menu bar and start GREP.
Type the name of the function as the pattern to search, and give the
\lcc\include\*.exp file pattern. This will find which .exp has this function
defined in it (if at all). Then add that library to the link command line.
6)
How do I generate an import library for my DLL?
Build a text file containing:
MYDLL.dll |
|
|
foo |
|
|
_linelength |
linelength |
data |
foo |
|
This
file should contain the name of the DLL in the first line, then a series of
exports. In the first column you should write the name of the function, as
decorated by the compiler: A leading underscore, and a '@nn' for all _stdcall
functions. The number following the @ means the number of arguments * 4. In the
second column, you should write the name as it was exported from the DLL. This
is optional. If absent, the buildlib utility will use the name of the function
minus the decorations. The third column (optional) is only necessary if you
want to export a data item and not a function.
You can build aliases (alternatefoo and foo are
different names for the same DLL entry point).
7)
Why is my executable so big?
Do not forget to instruct the linker to
eliminate the debug information.
8)
How do I pass command line parameters to my program when I use the debugger?
Use the ‘Debugger’ tab in the ‘Configuration’
options.
9)
How do I start the debugger with a given executable without building a project?
At the command line enter:
Wedit myprogram.exe <optional arguments>
or use the ‘File->Open menu’ option, and
enter the name of the executable you want to debug.
10) Why
does my program crash when I change the 'Optimize' option?
This can be a problem in the compiler, but
before blaming the compiler, remember the following:
·When
optimizing, the compiler does not initialize local variables to known 'bad'
values, as it does when compiling in a normal setting with debugging info. If
you use an uninitialized variable then, in a normal compilation the program
will crash.
·Tighter
memory layout and register usage when optimizing can produce different behavior
when uninitialized variables are used.
·The
ASSERT macro can contain code with side effects. If those side effects are not
being compiled because the ASSERT macro is not active in an optimized version,
those side effects are not there in the optimized build, which can provoke
different program behavior.
11)
How do I obtain a stack trace when my program crashes?
Compile all modules with the -g4 option or set
the corresponding option in Wedit.
12)
How do I use an MSVC import library with lcc?
Do the following:
1.Use the pedump utility to build an ASCII .exp
file from the library.
2.Use the buildlib utility to build a library
from the generated exp file.
Example:
d:\lcc\test>
pedump /EXP qprowinm.lib >qprowinm.exp
d:\lcc\test>
buildlib qprowinm.exp qprowinl.lib
The first line instructs pedump to generate a
file suitable for the buildlib utility. In the first line, the generated file
should contain the name of the DLL to which this import library is referring.
If that name does not appear, it is highly probable that this is NOT an import
library, but a normal library that should work without any problems with lcc.
The second line instructs the buildlib utility
to use the generated qprowinm.exp to build an import library using the lcc
format. If the name of the DLL was not found, this step will fail.
13) How
do I list the exported symbols from an object file/executable file/DLL?
Use pedump with the following arguments:
pedump
/exp <filename>
14) What
is the difference between lcc 4.1 and lcc-win32?
Lcc 4.1 is a continuation by Dave Hanson and
Chris Fraser of their work with the original LCC compiler. Lcc-win32 is derived
from lcc 3.6 and has diverged considerably since. The purposes of the two
systems are quite different. Lcc 4.1
needs MSVC to run and is not very well adapted to the Windows environment. It
is conceived as an environment to learn how a cross compiler works and can
generate code for a wide spectrum of machines. Lcc-win32 is a stand alone
compiler system, requiring nothing else. It is limited, however, to the
Windows/x86 environment and is not a cross compiler.
15) The
wizard stops with 'Code generation fails' in my Windows 95 system. What should
I do?
In some older Windows 95 systems, the wizard
fails mysteriously. I have spent a lot of time tracking this and never was able
to determine why. When I call the PropertySheet primitive, in some systems the
answer will be minus one, and nothing will be shown. This problem disappears
with a newer version of the system. I am sorry about this, but there is nothing
that can be done. You must acquire the latest service release and upgrade your
system.
16) How
do I build a DLL?
There are several ways of doing this.
·
You can use the
integrated development environment, specifying explicitly the ‘DLL’ option.
This is the easiest.
·
You can call up
the ‘wizard’ and generate the DLL skeleton of your DLL with it. You specify to
the wizard in its dialog boxes where on your disk you want the code to be
written.
·
You can link your
program using the –DLL option, without forgetting to specify the .def if necessary.
This assumes you work from the command line.
14) What
does it mean “compiler error in kids” ?
This means that the compiler has found a
combination of operations that it does not know how to generate. This error can
occasionally appear when you are using the long long integers. This data type
has been introduced relatively recently, and there may be some combinations of
operations that are still not possible. In the event that you get this message,
please send me a bug report.
15) How
can I use the debugger of lcc with Microsoft’s compiler?
You should compile with the debug information
set to the standard NB09 and avoid the program database. This can be
accomplished with the following command line to the Microsoft compiler ‘cl’:
cl /Z7 foo.c /link /pdb:none
16) When
I start my program, a ‘DOS box’ (console) appears. How can I get rid of it?
You have not defined your program as a Windows
(GUI) program. To do this, go to the ‘Linker configuration’ tab, and check the
‘Windows program’ radio button. This will add the option ‘Subsystem Windows’ to
the linker command line.
When Windows starts a
program, it looks at its header for the ‘Windows’ bit. If the program is not
marked as a GUI program, it will open a console window before starting it. The
linker defaults to a ‘console’ application, and not to a Windows application.
17) The
linker indicates that _LibMain@12 is not
defined when I try to link my DLL. What does this mean and how do I get rid of
it?
This
means you did not define the entry point of your DLL. Generate a DLL skeleton
with the wizard. Or write the following function:
BOOL WINAPI __declspec(dllexport)
LibMain(HINSTANCE hDLLInst,DWORD fdwReason,LPVOID lpvReserved)
{
return
1;
}
18) How
do I print a message to be shown in the debugger?
With the OutputDebugString() function. If the application has no debugger, the system debugger displays the string. If the application has no debugger and the system debugger is not active, OutputDebugString does nothing.
19) How do
I debug a DLL?
The debugger
cannot start a DLL like a normal executable. You have to define an executable
that will be started by the debugger. This executable should be linked with the
import library of your DLL, and should contain debug information. When this is
done, Wedit will trace calls into the DLL in exactly the same manner as it will
trace calls into any other function. This will work too if the DLL is loaded
with LoadLibrary, instead of being linked in the executable. The drawback of
the LoadLibrary method is that breakpoints cannot be re-established, since
Wedit has no way to know which DLLs the program will dynamically load.
20) My
dialog box does not show up in the screen!
This can happen if you have a control defined in ComCtl32.dll, and you did not call InitCommonControlsEx. When you use one of the controls in that DLL, you have to call that function before you make the call to DialogBox. If you fail to do that, Windows will not find the class of the list view, slider, etc., and it will refuse to load the entire dialog box. A sample code for how to do that is:
INITCOMMONCONTROLSEX
icex;
memset(&icex,0,sizeof(icex));
icex.dwSize
= sizeof(icex);
icex.dwICC
= ICC_DATE_CLASSES; // The month
calendar control will be added.
InitCommonControlsEx(&icex);
21) How
do I send a bug report?
The purpose of a bug report is to allow the lcc
developers to reproduce the bug so that they can understand it and then correct
it. In this context, it is of paramount importance that the error report states
clearly the symptoms of the error and the code that produces this error. If you
write things such as: ‘It does not work’ or ‘It writes an error message’, there
will be NO WAY to help you, since we will NOT be able to reproduce the error or
find out what is really not working !
You should specify what the actions were that
produced a crash, if you were working within the IDE. Then you should specify
the symptoms you see (error message, etc.). If the error is produced by a
sequence of C code, try to find out what the smallest piece of code is that
reproduces the bug. It is impossible to find out what is not working if we
receive a bunch of C files with the comment: This does not work.
Send your bug reports to:
Jacob@jacob.remcomp.fr
If you bought the source code CD, remember: If something fails, please do not panic and fix it.
For instance:
·
When I use
'make' on the \lcc\src\lccsrc the lcclnk.exe fails. It says, 'Fatal: Command
arguments too long' — What have I forgotten to do?
Answer:
There are many make utilities. That prompt does not appear in the make from the lcc CD, and the user has forgotten that another Make utility with a smaller command line limit apparently is being used. Maybe a DOS Make utility.
These type of bugs are difficult to prevent, and in general, I cannot prevent a problem from surfacing somewhere. To be sure that the lcc Make utility is being used, type:
\lcc\bin\make
instead of typing just
make
This will pick
up any Make utility in the PATH.
There are other
problems that can arise, the possibilities are infinite. Obviously, if a file
is missing, contact me immediately and it will be shipped to you.
Any COM interface method can be called from a C program. There are two things to remember when calling an interface method from C:
* The first parameter of the method always refers to the object that has been created and that invokes the method (the ‘this’ argument).
* Each method in the interface is referenced through a pointer to the object's vtable.
The following example creates a surface associated with a DirectDraw object by calling the IDirectDraw2::CreateSurface method with the C programming language:
ret =
lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS, NULL);
The lpDD parameter references the DirectDraw object associated with the new surface. Incidentally, this method fills a surface-description structure (&ddsd) and returns a pointer to the new surface (&lpDDS).
To call the IDirectDraw2::CreateSurface method, first dereference the DirectDraw object's vtable, and then dereference the method from the vtable. The first parameter supplied in the method is a reference to the DirectDraw object that has been created and which invokes the method.
To illustrate the difference between calling a COM object method in C and C++, the same method in C++ is shown below (C++ implicitly dereferences the lpVtbl parameter and passes the ‘this’ pointer):
ret = lpDD->CreateSurface(&ddsd, &lpDDS,
NULL)
All COM interface methods described in the Microsoft documentation are shown using C++ class names. This naming convention is used for consistency and to differentiate between methods used for different DirectX objects that use the same name, such as QueryInterface, AddRef, and Release. This does not imply that you can use these methods only with C++.
In addition, the syntax provided for the methods uses C++ conventions for consistency. It does not include the ‘this’ pointer to the interface. When programming in C, the pointer to the interface must be included in each method. The following example shows the C++ syntax for the IDirectDraw2::GetCaps method:
HRESULT GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps );
The same example using C syntax looks like this:
HRESULT GetCaps(LPDIRECTDRAW
lpDD, LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps );
The lpDD parameter is a pointer to the DirectDraw structure that represents the DirectDraw object.
[1] The format of the .exp file is as
follows :
The first line should
contain the name of the DLL you are using, i.e., Mydll.dll or whatever. Since
it is a file name, case is NOT significant. No path information should be
included. Just the name of the DLL.
The following lines
should contain three columns, separated by tabs or spaces.
The first one is
the name of the symbol as published by the compiler, i.e., if your symbol is
MyFunction, the column should contain _MyFunction. In the case of a _stdcall
function it would be _MyFunction@12 .
The second one
should contain the name of the symbol as exported from the DLL. Normally this
should be the same as the column 1, but sometimes you want to export the
functions without the leading underscore, for instance for using them with
another language processor like Visual Basic.
The third column
is optional. If present it should contain the column DATA, meaning that this is
an exported DATA symbol.
The .exp file is
generated automatically by lcclnk.