Advanced

Compiling a DLL

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;

}

The Definitions File (.def)

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.

 

 

 

Using the Intrinsics Utility

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.

 

Inlining the strlen Function

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++);

}

Another Example: Inlining the strchr Function

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;

}

The Dynamic Loader

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.

Binding an object file

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.

 

Loading an Object File

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.

Getting a Function Pointer

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.

Unloading the Object File

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.

 

A Complete Example

#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 within lcc-win32

 

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.

Frequently Asked Questions (FAQs)

1) I get an "undefined reference to "_foo@12". What do I do ?

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 '@').

2) How do I add a library to the project?

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.

3) I still get an undefined to _xxx even if I do include the library.

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@12

foo

 

_linelength

linelength

data

_alternatefoo@12

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

Compiling with the lcc-cd.

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.

Accessing COM Objects Using C

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)

 Interface Method Names and Syntax

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.