7. Using OBJ files
As long as you use this system to write Modula-2 programs for the DOS environment only, there is virtually no reason to use OBJ files. But, in case you have to...
You condition the compiler to generate OBJ files by setting the environment variable M2OUPUT to OBJ.
To link OBJ files you must exit the MC environment (or invoke the DOS shell) and use some OBJ file linker.
Also, with OBJ files, there is no module version checking done for you. Of course, if you keep your ".MAK" files up to date, you should not encounter any problems.
To help you figure out which files need to be linked, we provide a utility (GenLink) that generates a LINK answer file. The LINK answer file created by GenLink lists all the Modula-2 OBJ files that are required to link your program.
Since your program, most likely, requires modules written in another language (or you would not be using OBJ files, right?!), you will have to edit the file created by GenLink to make it suitable for the job at hand.
Realizing that you will probably want to customize it, the source code to GenLink is included in this software package.
7.2 Foreign Modules
To let the Modula-2 compiler know about modules written in other languages, you must write FOREIGN DEFINITION modules. A foreign definition modules is a regular definition module, with the exception of the module's header, which goes like this:
FOREIGN [C|PASCAL] DEFINITION MODULE modName;
where the 'C' or 'PASCAL' qualifier is optional.
7.2.1 External names
Names of public variables and procedures in foreign modules are encoded in one of 3 ways:
- In a regular FOREIGN (neither C nor PASCAL) module, the identifiers are encoded in the object files as you enter them.
- In a FOREIGN C module, identifiers are written out preceded by a '_' character.
- In a FOREIGN PASCAL module, identifiers are converted to all upper case.
WARNING: As currently implemented, SET and STRING constants defined in a FOREIGN DEFINITION module, cause the compiler to generate external references to these. These "constants" should, therefore, be allocated space and properly initialized in the foreign module.
FOREIGN modules are not expected to have an initialization procedure, and the compiler will not generate these initialization calls, as in the case of regular Modula-2 modules.
220.127.116.11 FOREIGN C modules
When invoking a routine defined in a FOREIGN C module, the arguments are pushed onto the stack in reverse order, as per C's custom. Also, the caller will remove the arguments off the stack upon return from the subroutine.
Since C, unlike Modula-2, supports (?!) the passing of a variable number of arguments to a function, the symbol '...' may be used at the end of a PROCEDURE parameter list definition to indicate that an indeterminate number of arguments may be passed. Example:
PROCEDURE sum( n:INTEGER; ... ) :INTEGER;
defines a function that takes n integers and returns their sum. It could, actually, be implemented in Modula-2 thus:
PROCEDURE sum( n:INTEGER; ... ) :INTEGER;
VAR p :POINTER TO INTEGER;
res := 0;
p := ADR(n) + 2;
WHILE n > 0 DO
res := res + p^;
p := ADDRESS(p) + 2;
DEC( n );
In addition to being useful to define functions that take a variable number of parameters, the use of '...' is also a handy (?!) way of improving the odds that the arguments are passed in a form that C will like.
18.104.22.168 Parameter passing
The form in which the individual parameters are passed is always the same, regardless of whether the procedure is in a foreign module or not. The exception is when you use '...'.
When passing parameters that correspond to the '...' in the procedure heading, the compiler follows the default C rules: Everything is passed by value, except for arrays, which are passed by reference (their address, instead of their value, is pushed onto the stack).
Some C compilers return structured values by actually returning a pointer to those values in the DX:AX register pair, which is just the way that Fitted Modula-2 returns pointers! With these, you could define
struct someStruct cfunct()
TYPE someStructPtr = POINTER TO someStruct;
PROCEDURE cfunct() :someStructPointer;
7.2.3 In the real world...
Knowing the parameter passing conventions used by the compiler you should have no trouble writing assembly language modules to be invoked by Modula-2.
With the help provided (FOREIGN C and '...'), it should be easy enough to interface Modula-2 to C. But is it, really? Not quite!
There are two main problems that you will have to overcome. One, is the choice of a suitable memory model. Our LARGE memory model is probably a better choice than HUGE, as some compilers require DS to always point to DGROUP.
The other problem, is the set of requirements imposed by each language's runtime system. Since we provide all the source code for our runtime, your best bet will probably be to modify our system to suit theirs.
Modules that are obvious candidates for "adaptation" are System and Storage; In their current state, they are virtually guaranteed to not work with another vendor's runtime system, and they are a base on which many other library modules depend.
7.3 `C' Runtime Option
The compiler environment variable M2RUNTIME can be used to chose a `C' runtime environment (you should also set M2OUTPUT = OBJ, of course).
When the default Modula-2 runtime environment is chosen, the compiler sets the main module's initialization part to be the program's entry point. When the runtime environment is set to `C', the main module's initialization part is named `M2MAIN' and it is supposed to be called by your (main) C program.
If you use the System.MOD example included in the C example sub-directory, `M2MAIN' must be called in a manner compatible with:
extern int _far _pascal M2MAIN(int argc,char **argv,char **envp);
int main( int argc, char **argv, char **envp )
return M2MAIN( argc, argv, envp );