Announcement

Collapse
No announcement yet.

How safe is LIBMAIN?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • How safe is LIBMAIN?

    Hi all!

    I have a DLL with objects, such as fonts and GDI brushes created in the LIBMAIN function, the dll then passes the object handles to the calling application, when the DLL is unloaded the handles are deleted using DeleteObject.

    Are all the handles deleted when the DLL unloads? or is it possible that the LIBMAIN function may not be called, if then would all the handles etc. be automatically deleted when the DLL unloads anyway?

    The DLL could be used by lots of applications at one time.

    I will post some example source when I get back to my DEV pc tommorrow.


    TIA


    -------------
    Kev G Peel
    KGP Software
    Bridgwater, UK.
    mailto:[email protected][email protected]</A>

  • #2
    In "Win32 Programming" by Rector & Newcomer, they detail the behavior of LIBMAIN execution when processes and threads Attach and Detach... it is essential reading for anyone that uses LIBMAIN for anything other than returning non-zero to Windows. This book also covers aspects of DLL creation that makes good (and technical) reading.

    In essence, there are circumstances under which LIBMAIN is not executed - I don't have my copy of the book handy but it relates mostly to multiple threads.

    Generally speaking, all objects are deleted when a process is closed normally, but this is not a behavior to rely upon either, as poorly written code that continuously creates new objects can slowly strangle the system while the app's are still running.

    Personally, I would add a sub to the DLL that can be explicitly executed to destroy all of the objects. That way, you can call this sub from your main app code, and by using a static variable in this sub to track the "deletion state" of the objects, you can also call this sub from LIBMAIN (%process_detach) - this should guarantee that you cleanly destroy everything you have created as the sub will always be executed at least once.

    Do yourself a favor - get a copy of "Win32 Programming" - it is worth every cent.

    ------------------
    Lance
    PowerBASIC Support
    mailto:[email protected][email protected]</A>
    Lance
    mailto:[email protected]

    Comment


    • #3
      It is important to understand that LibMain can actually be called multiple times by the same application in a threaded application.

      I was developing a DLL for use with a program that would load multiple instances of the same DLL in multiple threads and I found it important to "alway" process the fwdReason parameter.

      Never leave out the Select case as defined below. To make sure your DLL is threadsafe, process the fwdReason parameter, so you know what is actually happening to the DLL.

      Code:
      FUNCTION LibMain(BYVAL hInstance   AS LONG, _
                       BYVAL fwdReason   AS LONG, _
                       BYVAL lpvReserved AS LONG) EXPORT AS LONG
      
          SELECT CASE fwdReason
              CASE 1  '  %DLL_PROCESS_ATTACH  
                  '   initialize DLL here
              CASE 2  '  %DLL_THREAD_ATTACH
                  '   This is like %DLL_PROCESS_ATTACH, but for threads
              CASE 3  '  %DLL_THREAD_DETACH
                  '   This is like %DLL_PROCESS_DETACH, but for threads
              CASE 0  '  %DLL_PROCESS_DETACH
                  '   This is where app unloads the DLL
                  '   Global Variables, etc. will be lost at this point
              CASE ELSE
          END SELECT
          
          LibMain=1
      END FUNCTION

      ------------------
      Chris Boss
      Computer Workshop
      Developer of "EZGUI"
      http://cwsof.com
      http://twitter.com/EZGUIProGuy

      Comment


      • #4
        Chris, you should also read the book I mention above... it explains in detail how you cannot guarantee that LIBMAIN will be called in all circumstances, as your message suggests.

        ------------------
        Lance
        PowerBASIC Support
        mailto:[email protected][email protected]</A>
        Lance
        mailto:[email protected]

        Comment


        • #5
          Lance --

          My experience is the same as yours. I've never seen DLL_PROCESS_ATTACH or DLL_THREAD_ATTACH fail to execute, but the other two are not at all reliable. And it doesn't have to be a multi-threaded app to have problems. For example, doing an End Task on a program, or tripping a GPF, will bypass DLL_PROCESS_DETACH.

          To be clear... This is not a PowerBASIC problem, it is a flaw (or "feature", if you prefer) of the Microsoft Windows operating system.

          -- Eric


          ------------------
          Perfect Sync: Perfect Sync Development Tools
          Email: mailto:[email protected][email protected]</A>



          [This message has been edited by Eric Pearson (edited February 29, 2000).]
          "Not my circus, not my monkeys."

          Comment


          • #6
            If your DLL needs to be initialised with globals (Folders-reference,diffrent messages ands strings and
            so forth) read from your applications INI-file or registry,let your application call an
            initialization-function in the DLL before use. In this init_function you creates a mutex.
            In all Exported functions you check if the mutex is created, if not you start with a call to your init_function.
            This way you asure that the DLL is properly initialized

            ------------------
            Fred
            mailto:[email protected][email protected]</A>
            http://www.oxenby.se



            [This message has been edited by Fred Oxenby (edited February 29, 2000).]
            Fred
            mailto:[email protected][email protected]</A>
            http://www.oxenby.se

            Comment


            • #7
              Thanks for all suggestions and replies,

              What I have decided to do is create a global 'tracker' variable in the dll code which is set to 1 when the DLL finishes loading, and set to 0 when all the objects are destroyed, so if in any case the LIBMAIN load function is called again, that variable is checked, and if it is 1 the objects are not created again, thus avoiding a resource leak.


              ------------------
              Kev G Peel
              KGP Software
              Bridgwater, UK.
              mailto:[email protected][email protected]</A>

              Comment


              • #8
                Here my LIBMAIN function, most of my applications use this DLL:

                Code:
                  
                 '---------------------------
                 Global ghHandles     As Long   ' True if brushes were created
                 '---------------------------
                 Global ghLibInst     As Long
                 Global ghStdNrmlFont As Long
                 Global ghStdBoldFont As Long
                 Global ghStdListBack As Long
                 Global ghStdEditBack As Long
                 
                Function LibMain(ByVal hInstance As Long, ByVal fwdReason As Long, ByVal lpvReserved As Long) As Long
                 
                  Select Case fwdReason
                 
                    Case %DLL_PROCESS_ATTACH
                         ghLibInst = hInstance
                         If IsFalse(ghHandles) Then
                          ghStdNrmlFont = MakeFont("Arial", 8, 0, 0, 0)
                          ghStdBoldFont = MakeFont("Arial", 8, 1, 0, 0)
                          ghStdListBack = CreateSolidBrush(%COMDLL_BACKLIST)
                          ghStdEditBack = CreateSolidBrush(%COMDLL_BACKEDIT)
                         End If
                         ghHandles = %True
                         ' Required for applications
                         InitCommonControls
                         LoadLibrary "RichEd32.dll"
                         Function = %True
                 
                    Case %DLL_PROCESS_DETACH
                         ghHandles = %False
                         DeleteObject ghStdNrmlFont
                         DeleteObject ghStdBoldFont
                         DeleteObject ghStdListBack
                         DeleteObject ghStdEditBack
                         Function = %True
                 
                    Case Else
                         Function = %True
                 
                  End Select
                 
                End Function
                ------------------
                Kev G Peel
                KGP Software
                Bridgwater, UK.
                mailto:[email protected][email protected]</A>

                [This message has been edited by K Peel (edited February 29, 2000).]

                Comment


                • #9
                  Kev --

                  I don't think that your code will have the desired effect. Each time a new application (a "process") attaches to your DLL the application will get its own individual set of variables, including all GLOBAL and STATIC variables, plus its own set of fonts, brushes, etc. In Win32 apps, individual processes are 100% isolated from each other, even if they share a DLL, unless you jump through some major hoops. So in this code...

                  Code:
                  Case %DLL_PROCESS_ATTACH
                       ghLibInst = hInstance
                       If ghHandles Then
                           'make fonts, etc.
                  ...the ghHandles value will always be zero, unless somehow the same instance of the same application manages to call LibMain with %DLL_PROCESS_ATTACH more than once, which is very unlikely. Impossible, I'd say, unless you were calling LibMain manually or something, which is certainly not recommended.

                  (Hmmm... by any chance did you mean to write IF ghHandles = 0 THEN...? It looks to me like the resources will never be created.)

                  If you are looking to cover most normal circumstances, then simply using %DLL_PROCESS_ATTACH and _DETACH will work fine for you without the extra checks. But there is really no way to guarantee that %DLL_PROCESS_DETACH will always be called. In the case of an End Task operation or a GPF, forget it. Fortunately, when Windows stops a process in a way that does not call %DLL_PROCESS_DETACH it automatically cleans up resources like the ones you are deleting. In fact, Win32 is really good about cleaning up resources whenever an app terminates. It is considered to be good practice to delete objects in %DLL_PROCESS_DETACH, but IMO that is a holdover from the days when Windows did not clean up for you. It could be considered a waste of code, and it is a possible location for a last-second GPF if you try to delete an object that does not exist.

                  One exception to all of this (i.e. automatic cleanup) is when resources are somehow shared with another process, such as when an app creates an object and then allows another app to "inherit" it. If you have purposely shared an object like a brush or font with another process, things get really messy.

                  -- Eric
                  ------------------
                  Perfect Sync: Perfect Sync Development Tools
                  Email: mailto:[email protected][email protected]</A>



                  [This message has been edited by Eric Pearson (edited February 29, 2000).]
                  "Not my circus, not my monkeys."

                  Comment


                  • #10
                    Yes, sorry Eric, it was IsFalse(ghHandles), but i've corrected it now!

                    I am going to put this code to test for a few weeks, if there are any observed ill effects, then I will stop using it and revert to creating the objects in each application.

                    I think the InitCommonControls and Riched32.dll calls are quite a good implementation, as 99.9% of my apps will use this DLL and the common controls etc, I will probably never have to remember to use the call again in each and every application, as they all use the DLL and automatically load it.


                    ------------------
                    Kev G Peel
                    KGP Software
                    Bridgwater, UK.
                    mailto:[email protected][email protected]</A>

                    [This message has been edited by K Peel (edited February 29, 2000).]

                    Comment


                    • #11
                      > if there are any observed ill effects, then I will
                      > stop using it and revert to creating the objects
                      > in each application.

                      I would not expect any "ill effects" at all. But I can guarantee that you are already creating the objects in each application! (Unless, as I said, you have added code to allow them to be inherited.)

                      -- Eric


                      ------------------
                      Perfect Sync: Perfect Sync Development Tools
                      Email: mailto:[email protected][email protected]</A>

                      "Not my circus, not my monkeys."

                      Comment


                      • #12
                        I have experience in writing a DLL that is loaded my multiple threads by a single program (one process).

                        The first Thread to load the DLL will pass LibMain

                        DLL_PROCESS_ATTACH

                        The next thread to load the DLL will pass LibMain

                        %DLL_THREAD_ATTACH

                        as will all other threads after that.

                        They will never use DLL_PROCESS_ATTACH

                        Then when the different instances of the DLL are unloaded by different threads, they will pass

                        %DLL_THREAD_DETACH

                        The Main thread will pass


                        %DLL_PROCESS_DETACH

                        when the DLL is finally removed from RAM.

                        Now as far as I could tell, the DLL_PROCESS_ATTACH event always was fired for the first instance of the DLL loaded. The Threads always fired the %DLL_THREAD_DETACH event when they loaded the DLL.

                        The detach events were not so consistant.

                        Now this experience is based on a DLL being loaded using the API call LoadLibrary.

                        Now there are other ways for a DLL to be loaded and my experience may NOT apply to those instances. DLLs that are implicitly loaded because they have exported functions may not follow these rules.


                        I think the problems may be associated with "how" the DLL is loaded, since Windows can do it more than one way.

                        Likely the advice from Lance is very valid, since there may be certain instances when things don't happen the way you expect.

                        It would be important to learn all the "ins and outs" of how Windows deals with DLLs.




                        ------------------
                        Chris Boss
                        Computer Workshop
                        Developer of "EZGUI"
                        http://cwsof.com
                        http://twitter.com/EZGUIProGuy

                        Comment


                        • #13
                          I will buy the book Lance mentioned, and I expect it would be of invaluable help as I continue with Win32 API programming,

                          Thanks all for your useful comments!

                          ------------------
                          Kev G Peel
                          KGP Software
                          Bridgwater, UK.
                          mailto:[email protected][email protected]</A>

                          [This message has been edited by K Peel (edited February 29, 2000).]

                          Comment

                          Working...
                          X