home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / DLL.ZIP / DLL.TXT
Text File  |  1988-02-03  |  60KB  |  1,275 lines

  1.  
  2.                  
  3.                   OS/2 Dynamic Link Libraries
  4.                      by Ross M. Greenberg
  5.  
  6.  
  7.  
  8. You are in a maze of twisty little passages, all alike.
  9. >USE DYNAMIC LINK LIBRARY
  10. I see no DLL here.
  11. >MAKE DLL
  12. I don't know how to make a DLL.
  13. >INVENTORY
  14. You have:
  15.   an 80286 machine
  16.   sufficient memory
  17.   OS/2
  18.   An OS/2 toolkit. In the toolkit is:
  19.     a text editor
  20.     a C compiler
  21.     an assembler
  22.     a linker
  23. >USE TOOLS IN TOOLKIT TO MAKE DLL.
  24. Huh?
  25.  
  26.  
  27. Like an ADVENTURE game, not knowing the keywords when you're
  28. trying to create a Dynamic Link Library can be very frustrating. 
  29. Once you know the keywords, though, you can explore new areas of
  30. the game, and bring home new prizes and treasures.
  31.  
  32. The objective of this article is to help teach you some of the
  33. new keywords and key techniques necessary to understand and build
  34. a Dynamic Link Library of your own.
  35.  
  36.  
  37. What is a Dynamic Link Library and Why Should I Care?
  38.  
  39. Well, I think the idea of Dynamic Link libraries is one of the
  40. more important concepts OS/2 introduces.  Although the principle 
  41. of DLL's have been around for a while (usually called shareable
  42. libraries in other operating systems), OS/2 is one which makes
  43. such a shareable library an intrinsic part of the operating
  44. system, not just an added-on, "neat" idea.  In fact, the system
  45. library functions themselves are DLL's in OS/2, with a clear
  46. separation by device type, allowing an easy upgrade route.
  47.  
  48. In MS-DOS, after you compile a program, you next link it with
  49. other portions of the program and with portions from a library of
  50. commonly used routines.  The end result, is a stand-alone piece
  51. of code which is loaded into memory, outstanding address references
  52. resolved, and then executed.  The physical file resulting from
  53. the link contains portions of the library it used:  two programs
  54. which use the printf() function will each contain a copy of the
  55. library functions which comprise that ubiquitous function.
  56.  
  57.  
  58. In a single tasking operating system with a sufficiently large
  59. hard disk, this really isn't a problem.  In a multi-tasking
  60. operating system which allows for shared memory usage, loading
  61. multiple copies of the same code seems wasteful.   OS/2 obviates
  62. this need by allowing only one copy of a given function to be
  63. loaded in memory and to have this copy shared by any task which
  64. seeks its functionality.  Since the function itself is not
  65. physically part of the program file, it is possible for the
  66. executable to be rather small, and to only update the library as
  67. required.  The concept of separate overlay files (and complicated
  68. linkers) is no longer needed:  just include the specific DLL's
  69. required and let the operating system do the rest.
  70.  
  71. But the advantages of using DLL's go far beyond the convenience:
  72. there is a functionality to DLL's which can be exploited in  many
  73. different ways.  One way includes the ability for two (or more)
  74. totally separate and distinct programs to be able to share memory
  75. by simply accessing the same run-time routine.  This message
  76. passing, already an intrinsic part of OS/2, can be fine-tuned
  77. with DLLs to fit the exact needs you might have in a complicated
  78. environment.
  79.  
  80. This is not without a price though:  there are some "tricks" to
  81. writing an operable DLL, and some cautions and caveats.  I
  82. discovered some of them the hard way while preparing an
  83. example of DLL usage for this article.
  84.  
  85. Of course, if you wish to avoid the supposed complexity of using
  86. these useful techniques, there is nothing in OS/2 to prevent you
  87. from using the standard, and more familiar, linking techniques of
  88. the past.  Except, perhaps, the knowledge that There Is A Better
  89. Way.
  90.  
  91. A Dynamic Link Library is not simply a new way of linking an old
  92. library.  There are some intrinsic differences between the two
  93. techniques.  A look at how statically linked libraries are linked
  94. into your code will help in understanding how the new DynaLink
  95. approach differs, and why it is a better way.
  96.  
  97.  
  98. Static Linking
  99.  
  100. When you compile a standard C program, the resulting output of
  101. the compiler is an object file.  The object file contains a
  102. number of different types of records.  There are different record
  103. types for procedures and routines, externally accessible
  104. variables, local stack variables and so on.  Each unique type of
  105. item has a unique record type associated with it.
  106.  
  107. There is a also a unique record type which indicates where the
  108. code for a routine starts.  Another unique record type has the
  109. name of the routine and a pointer to that routines code.  Some
  110.  
  111.  
  112. record types indicate that the routine requested in not found,
  113. and hence must be external to the object module.  Other record
  114. types indicate that the object is an externally located data
  115. item. And so on.
  116.  
  117. The important thing is that each call to a routine which can not
  118. be resolved within the particular source module is changed into a
  119. parameter which simply involves an "external item" record. 
  120. Helping out the compiler, you can specify the call type as being
  121. a near or far routine, or a near or far external item of some
  122. other type.
  123.  
  124. After you've finished compiling all of your various source
  125. modules into their object modules, you next link them together,
  126. along with some appropriate libraries,  and end up with an
  127. executable piece of code.  What does the linker actually do,
  128. though?
  129.  
  130. The linker examines each object module it sees (usually in the
  131. order in which they're presented) and keeps a list of all record
  132. types which either indicate a request for an external item or
  133. which define a global item.  Then as it sees record types which
  134. indicate actual code routines, it determines that routine's
  135. placement and resolves all calls for it into an actual address
  136. within the eventual output file.  Far calls, of course, indicate
  137. not just an offset within a 64K segment, but allow additional
  138. segments to be addressed, which in turn allow much larger
  139. programs to be created.  
  140.  
  141. There is nothing intrinsically foreign to the compiler in the 
  142. concept of mixed memory-model code as long as it knows how a routine 
  143. will be called.  The compiler will generate a far return for routines 
  144. defined as far calls, and near returns for near calls.  Addressing of 
  145. "far" data items are resolved in a similar way:  the compiler puts out 
  146. a record type which the linker can understand and resolve into an actual
  147. segment and offset (there's an extra step for the actual loading and
  148. executing of the code, covered below).
  149.  
  150. Whatever items are not resolved within the linking of the various
  151. object modules are next searched for in the libraries.  These
  152. libraries are, basically, object modules with nothing except
  153. local references resolved.  A module in the library starts off as
  154. a simple object file usually, and then is stored and indexed into
  155. a library as an entire unit: it is not stored on a routine by
  156. routine basis, but rather on an object file by object file basis.
  157.  
  158. The appropriate routine or external item is found in the library,
  159. the module is then pulled from the library and inserted into the
  160. executable form.  All references to it are resolved and the
  161. process continues.  An important consideration is that the object
  162. module originally loaded into the library as one unit is pulled
  163. from it as one unit as well, even if only one of the functions
  164. specified in the routine is referenced.
  165.  
  166.  
  167. The end result is a totally self contained image out on disk. 
  168. This image is loaded at some address (called the base address)
  169. when you run it, the base segment address is added to all of the
  170. other segment addresses throughout the code in the mysterious
  171. load routines, and then finally, with a simple call or jump, your
  172. program is executed.
  173.  
  174. That's basically how static linking works, with the more
  175. technical details glossed over.
  176.  
  177. What are the differences with Dynamic Linking, then, with the
  178. idea of statically linking an already existing library?  Well,
  179. the differences in the process are not all that substantial.  The
  180. end result is, though.  And because of that, the conceptual
  181. design of the Dynamic Link Library is different, as I describe
  182. below.
  183.  
  184.  
  185. Dynamic Linking
  186.  
  187. With a "normal" library, you compile all of the object modules
  188. you'll need, then use a librarian program to create a library. 
  189. The library itself is in some strange format, suitable only to
  190. linkers and librarian programs.
  191.  
  192. Things are a little different with DLL's, though.  First, there
  193. are two separate link steps.  You must link the constituent
  194. object file members which form the DLL together, and then link
  195. your own code with the resulting DLL.  Creating the DLL itself,
  196. however, requires a bit of work:
  197.  
  198. After you've created the object files from your source, you use
  199. the normal linker to create the DLL (plus a special file
  200. described below), and its format is really no different than a
  201. normal EXE file (really: it even has the 'MZ' as its first two
  202. bytes!).  It is therefore admirably suited for the standard
  203. system loader to load as if it were actually a program.  Later on
  204. the system will, basically, do just that for the initialization
  205. routine.  Typically, the new library will have an extension of
  206. DLL.
  207.  
  208. The special file, described fully below, is called a module
  209. definition file.  It describes the external interface for each of
  210. the accessible routines:  their public names and their
  211. attributes.  Anything not specifically mentioned in the DEF file
  212. can not be accessed routinely by an outside program.  This
  213. definition file is called the "export module definition file".
  214.  
  215. By running the export DEF file through a program called IMPLIB
  216. (Import Librarian), a special library file can be created.  This
  217. library file is conceptually similar to the "standard" idea of a
  218. library, and hence has the LIB extension. (See Figure x)
  219.  
  220.  
  221. An option to running the special file through the IMPLIB program
  222. is to create what is in essence the *inverse* of the export DEF file.  
  223. Such a file is called an "import module definition" file.  It, too, 
  224. has the extension of DEF. (See Figure x)
  225.  
  226. When you link the DLL with your own code, the linker sees the
  227. special record format of the import library (the LIB file created
  228. by IMPLIB), or reads the import DEF file, and creates special
  229. records which are understood by OS/2's program load facilities.
  230.  
  231. The end result of a link which uses DLL's is a hybrid file. It
  232. can be considered as if a partial EXE and a partial OBJ at the
  233. same time. A compiled object module will resolve local variables
  234. and routines into a segment and an offset, leaving external
  235. references virtually undefined.  The Dynalink program will have
  236. result in an EXE coming from the linker with its external
  237. references to DLL routines effectively unresolved.
  238.  
  239. At this point, I'm going to stop referring to 'segments' as such,
  240. and start calling them 'selectors':  DLL's are applicable only in
  241. protected mode OS/2, after all.
  242.  
  243. Part of OS/2's program loader recognizes that the EXE it's about
  244. to load contains DLL calls.  Finding these records causes a
  245. lookup on an internal table to determine if the DLL's has already
  246. been loaded.  Now, each module in the DLL can be defined as a
  247. "load at runtime" or a "load on demand" module.  Regardless of
  248. this definition, a selector is allocated for each module and all
  249. references to those modules are now resolved into a selector and
  250. offset pair. If the module has been defined as a "load at
  251. runtime" module, then the actual code for the module is read from
  252. the file, loaded into memory and any outstanding linkages
  253. resolved.
  254.  
  255. [Sidebar on alternative "manual" approach to locating and calling
  256. DLL routines.]
  257.  
  258. A brief mention of why protected mode is a handy thing:  consider
  259. what happens if a "load-on-demand" function is called before that
  260. selector points to valid code:  a page fault occurs, and the
  261. memory management module can easily resolve what the problem is,
  262. load the appropriate code, and allow the program to continue
  263. operating as if nothing had happened!  Subsequent calls to
  264. routines within the same selector would operate without a page
  265. fault.  Once the page fault mechanism (an intrinsic part of OS/2
  266. and protected mode applications) has been enabled, it is
  267. virtually transparent whether or not a requested page exists in
  268. "real" memory or in virtual memory.
  269.  
  270.  
  271.  
  272. The 80286 and 80386 chips have a table within them, called the
  273. Local Descriptor Table (See Figure x), which holds selectors, and
  274. the characteristics of these selectors. There is a an LDT for
  275. each of the processes currently running.  If a process attempts
  276. to access memory using a selector not within their LDT then
  277. hardware will cause a fault to occur: effective hardware
  278. protection of memory space.
  279.  
  280. The GDT, or Global Descriptor Table, is similar to the LDT,
  281. except that all tasks may access the selectors (and their
  282. associated memory) contained therein. Although this seems a
  283. simple way in which to make a selector and its data space
  284. accessible to multiple processes, OS/2 does not use the GDT for
  285. shared memory access. Instead it makes an entry into the LDT of each 
  286. process. [Why is this?]
  287.  
  288. When a request is made to OS/2 for memory allocation, the type of
  289. memory (shared or non-shared) is included in the request, and an
  290. entry made in the LDT for all processes allowed to share this
  291. memory.
  292.  
  293. *** the following paragraph is not necessary ***
  294. Only the kernal (Ring 0) code may write to these tables, however.
  295. (device drivers also run at Ring 0, so they'd have write access
  296. to the descriptor tables as well, but we'll save that for another
  297. article).
  298.  
  299.  
  300. So...What's the Big Deal?
  301.  
  302. Well, so far, functionally, a relatively efficient mechanism
  303. exists for linking in routines as required at run time instead of
  304. just once at link.  All automatically and transparently, of
  305. course, but what are the advantages of such an ability? There are
  306. quite a few.  First swapping the DLL routines in and and out of
  307. memory becomes pretty easy:  the LDT has a 'present' bit which
  308. indicates whether the requested segment is in memory or not.  
  309.  
  310. If not in memory, a page fault occurs as described above, and the
  311. swapped out DLL routine can be brought into 'real' memory.  Since
  312. the selector itself is but an index into a table which contains
  313. real address information, the individual DLL modules can end up
  314. anywhere in memory.  Transparent to your own code, of course.
  315.  
  316. Program code without some data space associated with it is a
  317. rarity: pure code can't manipulate items, although often useful
  318. in purely mathematical routines. The 8088/8086 family of
  319. processors used the data segment register to address its data
  320. space.  The 80286/80386 family of chips requires data to be
  321. addressed through a selector as well.  And, the information for
  322. the data selector is also stored in the LDT (See Figure x).  
  323.  
  324.  
  325. By setting of the appropriate bits in the LDT entry for a given
  326. selector, its associated memory can be made private or publicly
  327. accessible, or it can be set so it may be written to or is a
  328. read-only piece of memory.  Data selectors can even require a
  329. certain level of privilege in the code attempting to access it. 
  330. Any "illegal" operation will cause a fault to occur, and the OS/2
  331. is able to deal with the faulting process as required.
  332.  
  333. This means that, with the LDT set properly, memory can be
  334. shareable between tasks, memory can be protected from illegal or
  335. erroneous access, and other interesting memory usage and control
  336. techniques can be enabled.
  337.  
  338. As such, the DLL can be controlled and fine tuned in a variety of
  339. different ways.  This fine tuning is done through the DEF files
  340. mentioned above.
  341.  
  342.  
  343. Defining the DEF File
  344.  
  345. There are two different types of DEF files.  One, the EXPORT
  346. definition file, is used to let the world know what the various
  347. entry points and characteristics of these entry points are.  The
  348. IMPORT definition file indicates what functions from the DLL will
  349. be used and therefore should be linked at run time.  There is
  350. also the IMPORT library created by processing the EXPORT
  351. definition file through IMPLIB.  Let's look at each piece
  352. separately, with a list of the available features and options
  353. handy (See Figure ?).  All these options, by the way, must be
  354. entered in the appropriate DEF file in UPPERCASE. [Why is that?]
  355.  
  356.  
  357. The DEF Files:  Showing Your Face to the Outside World
  358.  
  359. LIBRARY Statement
  360.  
  361. The EXPORT DEF file really only requires a few fields.  The most
  362. important required field is the LIBRARY field.  This defines that
  363. this is a DLL Export definition file, instead of a "normal"
  364. application DEF file.
  365.  
  366.  
  367. LIBRARY [name][init_type]
  368.  
  369. The LIBRARY statement must be the first one in the DEF file,
  370. allowing the linker (and IMPLIB) to have a bit of a head start on
  371. what is about to come.  The first argument [name] to the LIBRARY
  372. statement is the eventual output name for the created DLL.  The
  373. extension of DLL is used unless you specify a different one.
  374.  
  375.  
  376. When the DLL is first loaded, there may well be some things you'd
  377. want to be initialized (setting certain data items, assuring
  378. certain system resources are available, etc.).  Each DLL has the
  379. ability of having an initialization routine which will be called
  380. when the DLL is first loaded, or upon each invocation of the DLL. 
  381. [init_type]allows you to specify if you want the initialization
  382. routine called each time the DLL is invoked (INITINSTANCE) or
  383. only once, when the DLL is first loaded (INITGLOBAL - the
  384. default).
  385.  
  386.  
  387. NAME [appname][apptype]
  388.  
  389. In a like manner, if the linker sees the NAME statement, it
  390. understands that you are creating an application and not a DLL. 
  391. The NAME statement allows you to specify whether the application
  392. is WINDOWS compatible and, if so, whether it is capable of
  393. running in real mode or protected mode.  If you specify WINDOWAPI
  394. as the second argument, then WINDOWS is required by this
  395. application in order to execute.  Specifying WINDOWCOMPAT means
  396. that it is not only WINDOWS compatible, but can also run in its
  397. own screen group under OS/2.  Finally specifying NOTWINDOWCOMPAT
  398. indicates that the application requires its own screen group when
  399. running, and is the default if you specify nothing.
  400.  
  401. NAME allows you to specify (with [apname]) the name the
  402. application shall *** shall ??? *** have after linking.  The default 
  403. extension, naturally enough, is .EXE.
  404.  
  405.  
  406. CODE [load][executeonly][iopl][conforming]
  407.  
  408. All code segments within the DLL will share a similar set of
  409. attributes unless otherwise specified.  The default set of these
  410. attributes is set with the CODE statement.
  411.  
  412. There are several other optional parameters allowed (but ignored)
  413. in the CODE statement for compatibility with WINDOWS.
  414.  
  415. The [load] parameter indicates if you wish the segment to be
  416. automatically loaded upon DLL invocation (PRELOAD) or to wait
  417. until the segment is actually accessed with a call (LOADONCALL -
  418. the default).  In an application which may have large areas of
  419. the code which might never be called, there is no real need any
  420. longer to load those library calls into memory all at once.  If
  421. they're called, then they'll be loaded automatically if the
  422. LOADONCALL option is specified. Once the routine is loaded, it
  423. will stay loaded in memory (except for swapping out to disk, of
  424. course).
  425.  
  426.  
  427. If you use the [executeonly] option to specify that other
  428. processes can not read this segment (by using EXECUTEONLY), then,
  429. even though the LDT marks the selector as accessible on a global
  430. basis, it may not be read (or treated like a data segment
  431. selector) by any process without the appropriate privilege level
  432. required.  The default (EXECUTEREAD) allows the memory allocated
  433. to this selector to be read for purposes other than for
  434. execution.
  435.  
  436. Only code segments with a high enough privilege level may access
  437. the hardware directly.  You may specify that a segment has this
  438. ability with the [iopl] parameter.  The default (NOIOPL) makes
  439. sense:  unless otherwise specified, an attempt to access the
  440. hardware (such as the comm port directly) will cause an immediate
  441. fault to be taken.  In the case where you allow a segment to
  442. access hardware directly, include the IOPL parameter in that your
  443. CODE line (it is probably a better idea to specify IOPL only when
  444. required, see the SEGMENTS statement below)
  445.  
  446. OS/2 still requires that you make a system call in order to
  447. request the privilege of hardware access.  [Why is this?  How
  448. many processes can have hardware access at the same time?]
  449.  
  450. A brief description of how the Privilege Level in OS/2 functions
  451. is important to understanding the implications of using the IOPL
  452. parameter.
  453.  
  454. The 80286/80386 chip prohibit direct transitions between code
  455. segments of a differing level of privilege.  The default
  456. privilege level for application code in OS/2 is Ring 3.  Ring 2
  457. code segments may access hardware directly.  The only way to
  458. transfer from one privilege level to another is through what is
  459. called a call gate.  A call gate has a specific selector type in
  460. the LDT and an actual "destination selector" which is the
  461. selector belonging to the actual code segment of the privileged
  462. call.  Additionally, the gateway has its own attendant privilege
  463. level and may only be called by code segments of the same
  464. privilege level.
  465.  
  466. Conceptually, when a call is made to a privileged routine, it
  467. passes through the call gate before passing to the privileged
  468. routine.  Since the only way through the call gate would be with
  469. either a CALL instruction (going into the routine) and a RET
  470. (coming back from the routine), the call gateway concept provides
  471. for an extra level of code security, but at the cost of some
  472. additional hardware overhead. Each transition via a gateway
  473. causes parameters on the stack to be copied to a new stack,
  474. another interesting security feature of the 80286, since a lower
  475. privileged program could manipulate the return address on the
  476. stack otherwise.  See the EXPORTS statement below for more
  477. information on that requirement.
  478.  
  479.  
  480. An IOPL'ed routine also uses up an additional slot in the LDT
  481. table.  Although the LDT table has 8K possible entries in it
  482. (each LDT entry takes up eight byte, so an entire segment has
  483. been allocated to the LDT in OS/2), 5K of those are reserved for
  484. OS/2 itself.  That leaves you with only about 3K LDT entries. 
  485. Probably enough for the foreseeable future.
  486.  
  487. The final parameter in the CODE statement allows you to specify
  488. whether the segment is a CONFORMING or NONCONFORMING segment. 
  489. This also deals with the IOPL privilege level and can be pretty
  490. confusing at first.  Consider it to be the inverse of the gateway
  491. approach.  
  492.  
  493. Normally, a segment will execute with the privilege level
  494. of the calling segment.  However, there are time when this
  495. might not be appropriate:  consider a Ring 3 communications
  496. protocol checking routine called from a Ring 2 device driver.
  497. In this situation, you might not want to allow the the protocol
  498. checker to operate with the higher privilege of its calling
  499. segment.  The default case, the NONCONFORMING parameter, would
  500. cause the Ring 3 routine to execute at Ring 3.  Set to
  501. CONFORMING, it would execute at the privilege level of the
  502. routine calling it:  the device driver running at Ring 2.
  503.  
  504.  
  505. Data Space Definitions
  506.  
  507. Just as code segments have a method of setting default parameters, 
  508. the DATA segments also allow certain parameters to be set.  This is 
  509. done with the DATA statement.
  510.  
  511. The DATA statement shares some of its parameter list with the
  512. CODE statement.  This makes a great deal of sense since these
  513. parameters describe how to make the default settings for each
  514. data selector in the LDT.  The format of the DATA statement is
  515. therefore very similar to the CODE statement:
  516.  
  517. DATA [load][readonly][instance][iopl][shared]
  518.  
  519. [load], as above, indicates whether the data segment should be
  520. loaded upon first invocation or load should wait until the first
  521. access to the selector's address. The default condition is
  522. LOADONCALL, however you can specify invocation load with PRELOAD.
  523.  
  524. [readonly] allows you to determine if the data segment is allowed
  525. to be written into (with the default parameter of READWRITE) or
  526. whether it should be protected against write access (READONLY). 
  527. Attempts to write to a READONLY segment will cause a hardware
  528. fault.
  529.  
  530.  
  531. [instance] allows you to specify whether or not this data segment
  532. (the DGROUP data segment in most cases) should be automatically
  533. allocated upon invocation, and if so whether there should be one
  534. copy allocated for the entire DLL (SINGLE, which is the default
  535. setting for DLL's), or whether each instance of DLL usage should
  536. have its own automatic data segment allocated (Multiple, and the
  537. default setting for applications).  If no automatic allocation is
  538. required, then the parameter should be set to NONE.
  539.  
  540. Each data segment can also have its own IOPL level.  This allows
  541. you to set the minimum privilege level required in order to
  542. access this data segment:  setting the [iopl] parameter to IOPL
  543. means that only Ring 2 and more privileged levels are allowed
  544. access to the data segment.  The default, NOIOPL, allows Ring 3
  545. code segment routines to have access to the data affiliated with
  546. the data segment.  This allows an interesting interface to be
  547. created between IOPL'ed segments and non-IOPL'ed segments through
  548. common shared memory:  like passing a message through a keyhole.
  549.  
  550. Finally, [shared] allows you to determine whether a data segment
  551. marked as a READWRITE segment may be shared among different
  552. tasks.  If it is marked as shareable, then only one segment is
  553. allocated at load time, and any process with privilege level
  554. sufficient to write to it may do so.  The default, NONSHARED,
  555. does not allow write access to a common data segment and causes a
  556. separate copy to be loaded for each instance.  If a data segment
  557. is marked as READONLY, then it is shareable by definition.
  558.  
  559.  
  560. Segment by Segment Parameters
  561.  
  562. Unless otherwise specified, code and data segments have the
  563. attributes you set in the CODE and DATA statements as described
  564. above (or their pre-defined default values if you don't describe
  565. them).
  566.  
  567. However, using the SEGMENTS statement, you may specify the
  568. individual characteristics for a given named segment.  Using the
  569. fields as specified above, the format for the SEGMENTS statement
  570. is:
  571.  
  572. SEGMENTS
  573. [Tony:the following should be on one line, indented slightly]
  574. <segmentname> [CLASS 'classname'][load][readonlyexecuteonly]
  575.      [iopl][conforming][shared]
  576.  
  577. The [CLASS 'classname'] is an option which allows you to specify
  578. that the <segmentname> parameter (which is required) be assigned
  579. to the class specified. If you don't specify a classname, then
  580. the 'CODE' classname will be assigned.  [What happens to the DATA
  581. segments which aren't named?]
  582.  
  583.  
  584.  
  585. Other arguments to the SEGMENT members are as outlined above.
  586.  
  587.  
  588. EXPORTS Statement
  589.  
  590. The EXPORTS statement is the only method of letting the outside
  591. world know about the routines of the DLL (the EXPORT statement is
  592. only applicable to DLL's.  See the IMPORTS statement below for
  593. application requirements).
  594.  
  595. Unless specified by inclusion in the EXPORTS section of the DEF
  596. file, a DLL routine is invisible to applications.  The full
  597. format of each line within the EXPORT section is:
  598.  
  599. <name>[=internalname][@ordinal][RESIDENTNAME][pwords]
  600.  
  601. A name used internally within the DLL need not be the name the
  602. outside application world knows the routine by:  you can specify
  603. the outside name as different from the internal name easily. This
  604. allows you to have a class of functions each serving a similar
  605. purpose and then to categorize them if you wish with a meaningful
  606. prefix.
  607.  
  608. If you wish, you can allow access to the function by its ordinal
  609. (or the routines library "slot" number) instead of by its name,
  610. by specifying the desired ordinal (obviously unique for the DLL)
  611. preceded by an '@' sign. If you do, lookups will be faster at
  612. load time, and less space will be required for the in-memory
  613. search list.
  614.  
  615. If you do use the [@ordinal] option, then you may have to
  616. consider using the [RESIDENTNAME] option as well:  normally, if
  617. an ordinal is used, then OS/2 will not keep the specified
  618. external name available.  If you're not using the ordinal
  619. parameter, then OS/2 will keep the name resident in its search
  620. tables.
  621.  
  622. If you've included usage of any privileged functions in your
  623. routine, you'll have to let the linker know how many words to
  624. reserve for parameter copying by using the [pwords] variable. 
  625. Since a calling task will have its own parameters copied as it
  626. passes through the gateway, you have to reserve that space now.
  627.  
  628.  
  629. IMPORTS Statement
  630.  
  631. The imports section allows you to specify which external DLL
  632. routines you require in your application (although a DLL can
  633. import functions from another DLL, ad infinitum).  The format of
  634. a line in the IMPORTS section is:
  635.  
  636. IMPORTS
  637.   [name=]<modulename>.<entryname>
  638.  
  639.  
  640. Again, like the EXPORTS lines, you can specify a name your
  641. routine uses when it is trying to resolve external routines.  You
  642. could, therefore, create a debugging DLL and a "normal" DLL and
  643. be able to link between them only by changing the <modulename> or
  644. the <entryname> associated with the named routine.  <modulename>
  645. is the name of the application or DLL which contains the desired
  646. <entryname> which was specified in the EXPORTS statement for the
  647. DLL.  The <entryname> can also be an ordinal number.
  648.  
  649. If the optional [name=] parameter is not specified, then the
  650. default name the routine will be "known" as will be the same as
  651. <entryname>.  You must specify an internal name, however, if
  652. you've specified an ordinal number instead of an <entryname>.
  653.  
  654.  
  655. Other Statements
  656.  
  657. There are a variety of other statements which can be included in
  658. the DEF file(s).  They are described in Figure xDEF.
  659.  
  660.  
  661. Using the DEF files
  662.  
  663. There are two specific ways in which the DEF files can be used: 
  664. first, just including them on the command line to the linker, and
  665. second, passing them onto IMPLIB.
  666.  
  667. IMPLIB is the Import Library Manager utility, a standard part of
  668. the developers toolkit in OS/2.   If you're creating the DLL and
  669. the application to use the DLL, you don't have an absolute need
  670. for IMPLIB, since you can create the EXPORT and IMPORT library
  671. definition files as you desire.  However, if you're creating a
  672. DLL for other applications to use (perhaps a commercial functions
  673. library, or perhaps a replacement for an already available
  674. product you produce), then IMPLIB should be part of your
  675. development cycle.
  676.  
  677. IMPLIB takes a definition file for input and produces what
  678. appears to be a simple LIB file for output.  This then allows you
  679. to include the LIB file in the link step.  And allows you to
  680. include multiple DEF files into one LIB file, too.  Assuming you
  681. had two DLL's, called COM_INP.DLL and COM_OUT.DLL each with their
  682. associated DEF files.  You could specify:
  683.  
  684. IMPLIB COM_STUF.LIB COM_INP.DEF COM_OUT_DEF
  685.  
  686. and then simply distribute the COM_STUF.LIB and the two DLL's,
  687. keeping the internal details of the DLL's to yourself.
  688.  
  689.  
  690.  
  691.  
  692. A DLL Example
  693.  
  694. In attempting to create a DLL for this article, I ran into a
  695. number of difficulties. Some can not, by the very nature of the
  696. DLL and multi-tasking software, be resolved:  deadlock can occur
  697. in DLL's just as they can in other types of software.
  698.  
  699. Think of the required aspects of a simple multi-session process
  700. such as a "chat" facility: multiple copies of the same process
  701. running, each of which occasionally generates a message, which is
  702. added to some internal queue. Each message generated must be
  703. collected by all other processes before it can be erased from the
  704. queue of outstanding messages, and each such message must be
  705. displayed, eventually, by each process. Finally, each of the
  706. processes must be able to "login" or "logout" from the chat
  707. session, and each must have some type of unique identifier.
  708.  
  709. I've designed such a facility as a method of demonstrating some
  710. of the unique abilities and problem spots of using a DLL as the
  711. "glue" which holds a multi-process concept like this together. 
  712. Is it useful? Well, perhaps not on a single-screen machine, but
  713. if the output were to a number of communications ports, it might
  714. be. 
  715.  
  716. Er... one aspect of this code should be brought to your attention
  717. before you start reading.  All of the problems inherent with this
  718. code design can be readily and easily solved using an approach
  719. which includes the OS/2 system resource of queues.  Why wasn't
  720. that approach used for this article, then?
  721.  
  722. Primarily because it wouldn't have required the concept of using
  723. DLL's!
  724.  
  725.  
  726. StepWise Design
  727.  
  728. One of the underlaying advantage of DLL's which makes them useful
  729. in this application is the ability to not only have private and
  730. shared memory, but the ability of separately compiled and
  731. executed tasks to utilize the same code at the same time.  In
  732. essence, there is nothing to prevent one of "users" of this chat
  733. code from following the coding conventions I've created and
  734. creating their own user-friendly interface (the bane of spiffy-
  735. concept-designers everywhere).  In fact, there is no reason why
  736. differently designed front ends couldn't be used for each
  737. session.
  738.  
  739. Starting with a concept like that, I designed this code using a
  740. majority of the capabilities in the DLL.
  741.  
  742. One of the abilities of the DLL is to provide for initialization
  743. code which will be executed either upon just the first invocation
  744.  
  745.  
  746. of the DLL, or upon each invocation.  This initialization routine
  747. is called before the process itself starts to run.  This DLL only
  748. calls its initialization routine the first time, so the EXPORT
  749. file for it contains the INITGLOBAL parameter.  Since this is the
  750. default condition, if could be excluded, if you wished.  The
  751. routine I use in this DLL is a simple one, merely setting certain
  752. default conditions and allocating some required queue space.
  753.  
  754. First, there is a login procedure.  The login procedure must advise
  755. the library code that another consumer and provider of messages has
  756. suddenly appeared.  To make things easier, the login procedure
  757. returns some user identifier to the process: it becomes useful to
  758. include an ID when generating new messages, when consuming old
  759. ones and, of course, when logging out.
  760.  
  761. When the DLL sees the login, it also allocates and assigns
  762. whatever global and local objects and structures are required for
  763. the new process.  A choice had to be made in the design as to
  764. where the actual allocations of memory would be made, since the
  765. memory could be allocated either in the DLL (becoming, in
  766. essence, a hidden object from the "client" code) or in the per-
  767. process code itself.  There are advantages to having a DLL
  768. routine allocate memory which is globally accessible to all
  769. processes but which only the DLL routines know about.
  770.  
  771. Additionally, a login causes each message already in the queue to
  772. appear unread to the newly logged in task.  Later, when requests
  773. are made for an outstanding and unread message, these messages
  774. will be returned.
  775.  
  776. The general design of the DLL causes a sharing of the "cleanup"
  777. task on each call to the "get a message" routine.  When a message
  778. is passed to the DLL, it is added to a queue - a structure which
  779. includes a flag word with one bit for each session.  A mask word
  780. with a set bit for each empty task slot is used for the initial
  781. value of this flag word.  The current task ID is then or'ed in,
  782. allowing the sender of the message to indicate it has already
  783. received the message.
  784.  
  785. When a process fetches a new message, it sets the bit in the flag
  786. word to indicate that it has fetched this message. Then, when
  787. that flag indicates all processes have gotten a copy of the
  788. message, the message can be removed from the global queue.  Each
  789. process therefore has to have the ability to manipulate that
  790. queue directly or must call a routine which has that ability. 
  791.  
  792. I've opted for a more modular design:  using a routine to
  793. specifically remove the message from the queue (or to add a
  794. message to the queue) allows me to isolate the queue itself. 
  795. Although the queue resides in global memory at this point,
  796. perhaps in the future it might reside on some node on a network,
  797. or some memory device which might require a higher privilege
  798. level?  Therefore, isolating the routine which physically
  799. modifies the queues is a good idea.
  800.  
  801.  
  802.  
  803. Since there isn't a human attached to each of the sessions, I
  804. have each session send a message only after a random amount of
  805. time has passed.  And, just to keep things interesting, there is
  806. a suitable sleep period whilst the imaginary typist is "entering"
  807. his or her message.  This allows messages to build up in the
  808. queue.  Whenever the sender is not "typing" or "sending" a
  809. message, it is executing loop which constantly seeks the
  810. outstanding message count.  Blocking on a null message count
  811. would prohibit the sender from sending a message.  Of course,
  812. OS/2 provides the ability of having two different threads, one of
  813. which could block on a null message count within the DLL, but
  814. that is not within the scope of this article.
  815.  
  816. Displaying of messages received takes place on a per-process
  817. basis.  This can cause problems when the session does not
  818. currently have screen access.  Eventually, when the internal
  819. queue for the process fills up, not having access to the screen
  820. will cause it to block. When a process blocks, it stops fetching
  821. messages from the DLL queue.  Eventually that queue will fill up. 
  822. When it does, another session will block when it attempts to add
  823. a message to the queue.  This condition can cascade until all
  824. sessions are blocked.  
  825.  
  826. Therefore, before any session sends a message, it checks to determine 
  827. if room exists in the queue. However, OS/2 is a multi-tasking operating 
  828. system.  Therefore, a routine must not be interrupted between the time 
  829. it determines there is room on the queue and the process of actually 
  830. adding the message to the queue.  Two specific alternatives exist to 
  831. get around this problem:  the first is to call DOSCritSec, which
  832. prohibits the given task from being interrupted by any other
  833. system process - rather drastic, and inherently ugly.
  834.  
  835. The other, and the one I used in DLL_CHAT, was to setup a
  836. globally accessible RAM semaphore and to assign the semaphore
  837. immediately upon entry to the "add a message" routine.  Other
  838. procceses attempting to add a message would programatically block
  839. on this flag and would wait in the loop for it to free up, or for
  840. a certain amount of time to pass.  If the flag didn't change
  841. within the specified time-out period, then an error condition
  842. would be returned to the calling task.
  843.  
  844. I used a little trick here which the optimization of the MSC 5.0
  845. compiler makes easy.  I set the initial value of the RAM
  846. semaphore to 1111111111111110.  With a simple right shift of one
  847. bit position, I can simultaneously read the current status of the
  848. semaphore as well as reserve it for my own usage if it is not in
  849. use.  When I grab the semaphore, I immediately set it to all
  850. 1's (since the right shift causes the topmost bit to be set to
  851. a zero in 80286 architecture), forcing subsequent right shifts to
  852. not only see the semaphore is in use, but to do so without having
  853. to "turn off interrupts" or indicate it is a critical section. 
  854.  
  855.  
  856.  
  857. This will only work when the word is right shifted in place: if
  858. your C compiler does not generate this as an in-place shift, then
  859. this will not be a safe way for you to manipulate the semaphore. 
  860. It's an easy operation to do with an assembler routine, though,
  861. in any case.  [Reed - Which optimization switches cause the SHR
  862. directly to memory?]
  863.  
  864. Finally, the logout routine.  When the session gets a quit
  865. command from the keyboard, it immediately passes control to the
  866. DLL logout routine.  This sets the above mentioned RAM semaphore,
  867. then proceeds to loop through the outstanding message list.  For
  868. each outstanding message, it sets the flag as if the process had
  869. already received the message. After each flag word has been so
  870. set, it is examined to determine if it has been read by all
  871. processes.  If so, it is removed from the queue.
  872.  
  873. Each message on the queue is a member of a linked list, and it's
  874. memory is allocated from the global memory pool.  When removing a
  875. message from the queue, the pointers of the other messages it
  876. points to are modified to point to each other, then the memory is
  877. deallocated.
  878.  
  879. Well, that is the basic design of the DLL_CHAT program.
  880. Now for the bad news.
  881.  
  882.  
  883. Caveats and Warnings
  884.  
  885. It's not really as bad as all that, but there are a few things
  886. you have to be aware about when you're designing your DLL's.
  887.  
  888. Above I mentioned some extraordinary lengths I went to in the
  889. original design to assure that certain areas of the code are
  890. protected against two "competing" tasks attempting to access it
  891. at once.
  892.  
  893. This is a problem inherent in any multi-processing system. 
  894. Typically, it's called a "re-entrancy" problem, that is, a piece
  895. of code being entered by a calling process before another process
  896. has finished with its call. Using semaphores, as I did, is effective 
  897. in most circumstances.  But, the method I chose was not the optimal
  898. method.  
  899.  
  900. Consider what happens if the session currently executing
  901. the semaphores routine happens to be interrupted by some high
  902. priority event (perhaps a keystroke, or (if attached to a comm
  903. port) the modem losing carrier).  There is no guarantee it will
  904. return to where it left off.  Yet, if it doesn't return and
  905. finish the routine, then the semaphore will forever be marked as
  906. in use.
  907.  
  908.  
  909. OS/2 does, however, provide an alternative if you use one of the
  910. system semaphores.  The semaphore is created with a DosCreateSem() 
  911. call, which returns a semaphore handle (similar to a file handle).  
  912. By using other semaphore calls, a process can effectively keep the 
  913. re-entrancy problem from occurring.  In the event that a process who 
  914. "owns" the semaphore at that time (and therefore is blocking others 
  915. waiting on it) gets killed for some reason, even unintentionally, 
  916. the system will effectively call DosCloseSem(), which will clear the 
  917. semaphore if set and restore it as a system resource if there are no 
  918. other references to it. 
  919.  
  920. In this application, I chose not to use system semaphores,
  921. since there would be frequent system calls with heavy overhead,
  922. and the likelihood that a process would be killed unintentionally
  923. was pretty small.  However, this also meant that I had to insure
  924. that a client program "dying" would die only after relinquishing
  925. control of the semaphore.  
  926.  
  927. Therefore, I use the OS/2 system call to add my own specific routine 
  928. to my exit list, that is, the list of routines which OS/2 will execute 
  929. on my behalf between the time the client program dies, and the time it 
  930. is buried. This routine simply calls the logout procedure, which in turn 
  931. will reset the system-wide flagword, the bits in each message, and finally
  932. cleanup the message base  and any outstanding semaphores.
  933.  
  934. When designing a DLL, you should always keep in mind worst case 
  935. scenarios: what would happen if this line of code were running while 
  936. ten other processes were running *those* ten different lines of code.  
  937. Since you can not really control what the other processes might be doing 
  938. as they start to execute common areas of code, it is better to design 
  939. the code as modularly as possible, and be sure to semaphore around areas
  940. sensitive to multi-tasking happening at just the wrong time. Chances are 
  941. that it will!  
  942.  
  943. Remember that, not only must you program defensively against other 
  944. processes using the DLL routines and their attendant data, but if you 
  945. opt to use OS/2 threads, you'll have to protect against their re-entrant 
  946. usage of the DLL routines (in fact, most of the considerations I'm
  947. advising you of regarding DLL's can also be of importance when
  948. designing a threaded program).
  949.  
  950. When speaking about unanticipated or asynchronous interruptions,
  951. you should be thinking about signal catching. And about not doing
  952. it in a DLL!
  953.  
  954. If you're going to use the system to set a routine to catch a
  955. particular asynchronous event (such as program termination, or
  956. control-C trapping done with the DosSetSigHandler system call),
  957. doing it in the DLL can be dangerous.  The concept of "resource"
  958. is the one which plays a critical role here.  The question is,
  959.  
  960.  
  961. who owns the "resource" of a signal catcher in a DLL?  Remember
  962. that the code is re-entrant, and that trying to determine the
  963. death of the last client member for the DLL can be tricky:
  964. especially if the signal catcher for client process termination
  965. is within the DLL itself. [Why? What portion of OS/2 doesn't allow it?]
  966.  
  967. On a similar basis, it is probably a good idea to stay away from
  968. the DosError (which allows a process to suspend hardware error
  969. processing), DosSetVect (which, lets your exception handler be
  970. called when certain conditions, such as attempts to execute an
  971. illegal opcode, occur).
  972.  
  973. If you must include such calls in your code, be sure to thoroughly 
  974. isolate those portions of the code from other client members of the 
  975. DLL's, and to preserve all aspects of your process state.  Be sure 
  976. to terminate "normally", too, not in some unique way, since DLL's have 
  977. some special characteristics which are taken care of properly in 
  978. automatic exit list processing upon client death.
  979.  
  980. Ramifications of what happens if you "signal out" of a DLL
  981. instead of "normal" termination include the possibility that the
  982. "active" count of the selectors which the DLL has used will not
  983. be updated properly.  The DLL may still be considered by OS/2 to
  984. have some client members accessing it, since Process Termination
  985. was handled by a signal handler of your own design which doesn't
  986. know how to update the DLL active client count. [What Signals Apply 
  987. on a global basis versus local?  What happens to a Signal Catcher 
  988. when it's owner dies?]
  989.  
  990.  
  991. Design Considerations
  992.  
  993. When designing your program to use DLL's, there are a few things
  994. you'll have to be careful of in your initial program design. 
  995. First, access to all DLL routines is through a far call.  So,
  996. although you can use the small memory models if you wish in the
  997. client section of your code, and in the DLL itself, the external
  998. definition of the DLL routines must indicate it is a far routine. 
  999. As such, the routine itself must also indicate in its prototype
  1000. that it is a far routine: otherwise the CALL and RET statement
  1001. types won't match.
  1002.  
  1003. What about the data allocated in the DLL?  That, too, must be
  1004. addressed as far data from the client routines.  Locally, within
  1005. the DLL, it may be addressed as near or far as required.
  1006.  
  1007. Before your client code ever executes, the initialization routine
  1008. for the DLL will have already executed.  Expecting any
  1009. initialization by the main() routine in your client code would be
  1010. premature.  Therefore, your DLL initialization code should only
  1011. access data within the DLL itself, since the startup code may not
  1012. have even allocated memory as of yet! [Exactly where does the DLL
  1013. init routine get called from?]
  1014.  
  1015.  
  1016. What of the differences between global and instance data items?
  1017. Well, obviously, they can be confusing concepts, since each DLL
  1018. module has no easily method of determining whether the data space
  1019. it is using is private or common to all tasks.  This can be
  1020. tricky, since many programmers routinely use temporary pointers
  1021. to objects which they place in "global" data space instead of
  1022. allocating it on the stack for local usage.
  1023.  
  1024. It is important to recognize the differences here between global
  1025. data (such as items defined and allocated outside the scope of
  1026. any routine in 'C') and globally accessible data.  In the first
  1027. case it really is "local" data, that is, data local to the client
  1028. process itself and not accessible to other clients of the DLL. 
  1029. In the second case it is accessible to all clients of the DLL. 
  1030. And that can fool you if you're not careful: you must be sure
  1031. that globally accessible data items don't change value when
  1032. you're not looking!  Keeping items to a local stack frame is
  1033. probably the safest bet.  Items which are kept around without
  1034. changing value are best kept in private client data space.
  1035.  
  1036. You can easily indicate through the DATA statement in the EXPORT
  1037. file which data segments you wish to be allocated on a private
  1038. per-client basis and which ones you wish globally accessible.  If
  1039. a segment is marked as READONLY, then it is globally accessible. 
  1040. The MSC data group named CONST should always be marked as
  1041. READONLY:  that allows for only one copy of literal strings to be
  1042. loaded for the entire DLL.
  1043.  
  1044. This brings up another interesting topic:  using a C compiler to
  1045. create DLL's.  There were some rumors floating about for a while
  1046. that this was impossible since the stack segment (SS) did not
  1047. equal the data segment (DS) upon entry into a DLL.  Since the
  1048. library has many routines which expects them to be equal, it at
  1049. first appeared that creating DLL's in C was blatantly forbidden.
  1050. Not so!
  1051.  
  1052.  
  1053. Using Microsoft C to Create and Use DLL's
  1054.  
  1055. There are several specific enhancements available in the MSC
  1056. 5.1 C compiler which make writing C DLL's very easy.
  1057.  
  1058. A new pragma, #pragma data_seg, allows you to specify for any
  1059. function that later loads its own data segment, exactly which
  1060. data segment to use.  By specifying the data segment as:
  1061.  
  1062. #pragma data_seg (segment_name)
  1063.  
  1064. you not only make things easier for using DLL's, but you have
  1065. more control over which data segment all initialized static and
  1066. global data will reside in.  The default data segment name if you
  1067. don't specify one is the one used by DGROUP, which depends upon
  1068. the memory model you use.
  1069.  
  1070.  
  1071. This is half of the solution of which data segment to use in the
  1072. DLL.  The other half is to specify the called function as one
  1073. which uses the previously saved data segment with the _loadds
  1074. keyword.  Upon entry into a _loadds function, the current DS
  1075. register is saved, the last one specified in the #pragma data_seg
  1076. is written into it, the function executed, and the saved DS
  1077. restored upon exit.  This is not such a new concept, since you've
  1078. had the ability to use /Au as a compiler option for quite some
  1079. time now, but this allows you to specify some capability on a
  1080. function by function basis.
  1081.  
  1082. In order for the compiler to know, in advance, that the routine
  1083. is going to be part of a dynamic link library, the new keyword
  1084. _export has been added.  In particular, if the function is one
  1085. with an IO privilege level associated with it, then the number of
  1086. words to reserve for the privilege level transition stack copy
  1087. operation can be easily calculated at compile time if the _export
  1088. keyword is used.  In fact, if you use the _export keyword as part
  1089. of your function definition, the number of words to reserve as
  1090. indicated in the DEF file is ignored.  [Is this TRUE?  I haven't
  1091. figured out how to verify it yet....]
  1092.  
  1093. When setting up the various data segments into their constituent
  1094. types (SHARED, READONLY, etc), you should also take a look at the
  1095. map file produced from the link: some additional segments might
  1096. be created which you hadn't thought about.  In particular, some
  1097. NULL segments are created for each group as _CONST, and _BSS.  In
  1098. order not to confuse the linker, each member of the group should
  1099. be specified within the SEGMENTS section of the EXPORT DEF file,
  1100. and you need only mention the "special" segments: those with
  1101. attributes different from the default setting of the DATA
  1102. statement. [Is this true?]
  1103.  
  1104.  
  1105. Creating the Initialization Routine for Your C DLL
  1106.  
  1107. Remember that the DLL, once passed through the linker, looks much
  1108. like an EXE file.  In fact, the same load routine used for your
  1109. own client module is used to load the DLL itself.  And, if you've
  1110. defined an initialization routine within the DLL, it will be
  1111. executed almost as if a stand-alone routine:  called immediately
  1112. after the DLL is loaded, it is only called (if you specify so)
  1113. upon subsequent loads of the DLL.
  1114.  
  1115. You can easily tell the loader where the initialization routine
  1116. is located by including a small assembly language routine as part
  1117. of your DLL, and linking it into the DLL when you do its link. 
  1118. In fact, it probably is not a bad idea to have a module similar
  1119. to the one in Figure xINIT, and to always name your DLL initialization 
  1120. routine the same. The secret of the initialization routine?  
  1121. Simply the fact that the only "program" the loader will find is 
  1122. the one which is addressed by the 'END START' directive!
  1123.  
  1124.  
  1125. The MS C compiler throws a small monkey wrench in your path, as
  1126. well.  Meaning to be helpful, the compiler throws a usage of the
  1127. _acrtused variable into each object module.  This forces the
  1128. linker to be sure to include some of the startup routines from
  1129. the C run-time library into the eventual output of the linker
  1130. (which the compiler thought was going to be a normal EXE file). 
  1131. To prevent this code from being loaded into your DLL, you should
  1132. define the variable yourself, as external data in a 'solo'
  1133. segment:
  1134.  
  1135. int  _acrtused = 0x1234;
  1136.  
  1137. or some number of particular meaning to you.
  1138.  
  1139. Additionally, in order to have global data items show up in the
  1140. named segment for the particular object module you're linking, it
  1141. should either be initialized, or declared as static.  Or both.
  1142. [What about using const?]
  1143.  
  1144. When writing your own DLL, you'll also want to use the -Gs switch
  1145. on the MSC compiler to disable stack checking.  Aside from the
  1146. slight added efficiency you'll gain (slightly smaller code and
  1147. one less function call per function), this is a requirement for
  1148. the DLL since the stack segment is different for each client
  1149. process and the size of the stack may vary on a per client basis
  1150. as well.  In the few places where you really need to add stack
  1151. checking, MSC provides you with an abundant set of #pragma's and
  1152. routines.
  1153.  
  1154. MSC 5.1 also includes some very welcome additions to the run time
  1155. libraries package.  Three new libraries exist for working with
  1156. programs requiring support for multi-thread, and for DLL's with
  1157. single thread and multi thread.  A couple of changes which will
  1158. affect you are the subtle differences such as errno now being a
  1159. macro which translates into a function call: a table must now be
  1160. used somehow in the functions of the run-time to enable a single
  1161. run-time package to handle errors from multiple sources. [Assuming 
  1162. there is a table, how is the offset into the table created? PID?]
  1163.  
  1164. Additionally, the new DLL run-time libraries allows you to use
  1165. any of the functions you've grown accustomed to.  Although I have
  1166. not tried each and every function, I trust that MS would have
  1167. specifically mentioned any there might be a problem with. [Er...
  1168. hopefully this is so?]
  1169.  
  1170. By the look of things and how they operate, it is probably safe
  1171. to assume semaphoring was used throughout the library --- this to
  1172. keep a call using an globally accessible variable from being
  1173. clobbered from two client processes trying to use it simultaneously.  
  1174. This forces a heavy overhead in system calls to frequently called 
  1175. routines, but one which there is little choice about. Remember that 
  1176. the libraries had to be written under a worst case scenario, and you 
  1177. pay a penalty in speed and efficiency for the safety inherent in 
  1178. putting semaphores around the "dangerous" routines.
  1179.  
  1180.  
  1181. [SideBar/Figure of printf() being interrupted with and without
  1182. semaphores...]
  1183.  
  1184.  
  1185. Conclusion
  1186.  
  1187. With the introduction of DLL's in OS/2, another programming
  1188. environment was created.  Much like WINDOWS programming, it has
  1189. it's own strict rules.  These rules, however, make a great deal
  1190. of sense once the underlying design concept and limitations of
  1191. both the chipset and of the appropriate portions of OS/2 are
  1192. better understood.
  1193.  
  1194. You can avoid a lot of these sticky problems by piece-at-a-time
  1195. programming: get as much of your program to work using routines
  1196. in a more "normal" library (using the library utilities), then
  1197. move the routines out into a DLL.  Then by adding the additional
  1198. functionality and safeguards required for shared memory access
  1199. between sessions and re-entrancy problems, you'll be able to
  1200. easily create a program which uses up less disk space, less
  1201. memory space, and allows for inter-process communication in
  1202. whatever manner *you* wish to design. Not a bad feature at all
  1203. for a new operating system to be written around.
  1204.  
  1205. And, once you've been through the maze of twisty little passages
  1206. once, the next time it isn't so hard to get through it rapidly
  1207. and collect that treasure.  The secret is just knowing a couple
  1208. of key phrases.  And thinking ahead before you enter the maze.
  1209.  
  1210.  
  1211.  
  1212. ==============================================================================
  1213.  
  1214. Figure xDEF
  1215. ===========
  1216.  
  1217.  
  1218. STUB 'filename'         which allows you to specify the name of a
  1219.                         DOS 3.x file to be run if this file is
  1220.                         run under DOS instead of under OS/2.
  1221.  
  1222. PROTMODE                Indicates that this file can only be run
  1223.                         in Protected Mode. An aid to the linker.
  1224.  
  1225. OLD                     This statement allows you to preserve the
  1226.                         names associated with ordinal numbers in
  1227.                         a multi DLL environment.  I haven't
  1228.                         really figured out a use for it yet,
  1229.                         either.
  1230.  
  1231. REALMODE                The opposite of PROTMODE, this indicates
  1232.                         the program can only be run in real mode.
  1233.                         An aid to the linker.
  1234.  
  1235. EXETYPE                 Insures that the specified operating
  1236.                         system is the current one for the
  1237.                         program.  You can specify OS2, WINDOWS. 
  1238.                         Or DOS4. DOS4?!?!? Yep. More on this in a
  1239.                         later article.
  1240.  
  1241. HEAPSIZE                Determines how much local heap must be
  1242.                         allocated within the automatic data
  1243.                         segment.
  1244.  
  1245. STACKSIZE               Allows you to specify how much space
  1246.                         should be reserved in the stack segment
  1247.                         when the program is run.
  1248.  
  1249. ==============================================================================
  1250.  
  1251.  
  1252.  
  1253. ==============================================================================
  1254.  
  1255. Figure xINIT
  1256. ============
  1257.  
  1258.  
  1259.  
  1260. EXTRN   INITROUTINE:FAR
  1261.  
  1262.         ASSUME  CS: _TEXT
  1263. _TEXT   SEGMENT  BYTE PUBLIC 'CODE'
  1264. START   PROC FAR
  1265.  
  1266.         call    INITROUTINE     ; the real initialization routine
  1267.         ret     
  1268.  
  1269. START   ENDP
  1270. _TEXT   ENDS
  1271. END     START   ; defines auto-init entry point
  1272.  
  1273.  
  1274. ==============================================================================
  1275.