Dialog into a DLL

Started by Rudolf Furstauer, February 17, 2012, 01:18:05 PM

Previous topic - Next topic

Rudolf Furstauer

Hello to all,

i try to put a dialog into a DLL and call this from my application.
I can call up the dialog in the program, but stopped in the DLL with an error.
This is the code for the DLL
'------------------------------------------------------------------------------
' FRMTOASTMESSAGE Auto-Generated FireFly Functions
'------------------------------------------------------------------------------
Function FRMTOASTMESSAGE_Show( _
                    ByVal hWndParent As Dword, _
                    ByVal ShowModalFlag As Long, _
                    Optional ByVal UserData As Long _
                    ) Export As Long

    Local IsMDIForm as Long
   
    ' // Create an instance of the class
    Local pWindow As IWindow
    pWindow = Class "CWindow"
    If IsNothing(pWindow) Then Function = -1: Exit Function
   
    ' // Set the flag if this is an MDI form we are creating
    IsMDIForm = %FALSE

    ' // Save the optional UserData to be checked in the Form Procedure CREATE message
    App.ReturnValue = UserData
   
    ' // Create the main window
    pWindow.CreateWindow( hWndParent, _
                          "ToastMessage", _
                          0, 0, 500, 310, _
                          %WS_POPUP Or %WS_CAPTION Or %WS_SYSMENU Or %WS_CLIPSIBLINGS _
                                  Or %WS_CLIPCHILDREN , _
                          %WS_EX_STATICEDGE Or %WS_EX_CONTROLPARENT _
                                  Or %WS_EX_TOPMOST Or %WS_EX_LEFT Or %WS_EX_LTRREADING _
                                  Or %WS_EX_RIGHTSCROLLBAR, _
                          CodePtr( FRMTOASTMESSAGE_FORMPROCEDURE ) )
   
   
    '<<<<Here, the error occurs.         
    If IsWindow(pWindow.hWnd) = 0 Then  '<<<<  At this point the processing is terminated with an error.
       Function = -1: Exit Function
    End If

Can someone tell me what am I doing wrong?


Thanks for any help.

Rudolf Fürstauer

Elias Montoya


Im not sure aout this, but,  shouldn't the line:

App.ReturnValue = UserData

be:

IF varptr(Userdata) THEN App.ReturnValue = UserData

?

As i said, im not sure if the byval overrides the fact that the value is optional, probably it is, in the new version of the compiler, otherwise it would be like using PEEK at address 0, leading to unstability and the crash you mention?


Win7, iMac x64 Retina display 5K, i7-5820K 4.4 ghz, 32GB RAM, All updates applied. - Firefly 3.70.

José Roca

#2
You can't use it in a DLL as a popup window. Doing it, you don't have access to the data of the main window and everything will went wrong. Use an include file instead, e.g.

TestX.inc


Function FRMTOASTMESSAGE_Show ( _
                    ByVal hWndParent As Dword _
                    )  As Long

    ' // Create an instance of the class
    Local pWindow As IWindow
    pWindow = Class "CWindow"
    If IsNothing(pWindow) Then Function = -1: Exit Function

    ' // Create the main window
    pWindow.CreateWindow( hWndParent, _
                          "ToastMessage", _
                          0, 0, 500, 310, _
                          %WS_POPUP Or %WS_CAPTION Or %WS_SYSMENU Or %WS_CLIPSIBLINGS _
                                  Or %WS_CLIPCHILDREN , _
                          %WS_EX_STATICEDGE Or %WS_EX_CONTROLPARENT _
                                  Or %WS_EX_TOPMOST Or %WS_EX_LEFT Or %WS_EX_LTRREADING _
                                  Or %WS_EX_RIGHTSCROLLBAR, _
                          CodePtr( FRMTOASTMESSAGE_FORMPROCEDURE ) )

   ' // Default message pump (you can replace it with your own)
   pWindow.DoEvents

END FUNCTION


' ========================================================================================
' Main callback function.
' ========================================================================================
FUNCTION FRMTOASTMESSAGE_FORMPROCEDURE (BYVAL hwnd AS DWORD, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG

   ' // Process window mesages
   SELECT CASE uMsg

      CASE %WM_CREATE
         ' // Disable the owner of the modal window
         EnableWindow GetWindow(hwnd, %GW_OWNER), %FALSE

      CASE %WM_COMMAND
         SELECT CASE LO(WORD, wParam)
            CASE %IDCANCEL
               ' // If the Escape key has been pressed...
               IF HI(WORD, wParam) = %BN_CLICKED THEN
                  ' // ... close the application by sending a WM_CLOSE message
                  SendMessage hwnd, %WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
         END SELECT

      CASE %WM_CLOSE
         ' // The owner window is enabled in WM_CLOSE rather than WM_DESTROY to
         ' // prevent the application from losing the focus. In WM_DESTROY the
         ' // modal window has already been removed from the screen by the system.
         ' // Because the remaining windows are disabled, the system gives the
         ' // focus to another application.
         EnableWindow GetWindow(hwnd, %GW_OWNER), %TRUE

      CASE %WM_DESTROY
         ' // End the application
         PostQuitMessage 0
         EXIT FUNCTION

   END SELECT

   ' // Pass unprocessed messages to Windows
   FUNCTION = DefWindowProc(hwnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================


Main file:


#COMPILE EXE
#DIM ALL

' // Include files for external files
#INCLUDE ONCE "CWindow.inc"   ' // CWindow class
#INCLUDE "TestX.inc"

%IDTEST = 101


' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS DWORD, BYVAL hPrevInstance AS DWORD, BYVAL lpszCmdLine AS WSTRINGZ PTR, BYVAL nCmdShow AS LONG) AS LONG

   ' // Create an instance of the class
   LOCAL pWindow AS IWindow
   pWindow = CLASS "CWindow"
   IF ISNOTHING(pWindow) THEN EXIT FUNCTION

   ' // Create the main window
   ' // Note: CW_USEDEFAULT is used as the default value When passing 0's as the width and height
   pWindow.CreateWindow(%NULL, "Test", 0, 0, 0, 0, 0, 0, CODEPTR(WindowProc))
   ' // Set the client size
   pWindow.SetClientSize 500, 320
   ' // Center the window
   pWindow.CenterWindow

   ' // Add a button
   pWindow.AddButton(pWindow.hwnd, %IDTEST, "&Test", 350, 250, 75, 23)


   ' // Default message pump (you can replace it with your own)
   pWindow.DoEvents(nCmdShow)

END FUNCTION
' ========================================================================================

' ========================================================================================
' Main callback function.
' ========================================================================================
FUNCTION WindowProc (BYVAL hwnd AS DWORD, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG

   ' // Process window mesages
   SELECT CASE uMsg

      CASE %WM_COMMAND
         SELECT CASE LO(WORD, wParam)
           
            CASE %IDTEST
               IF HI(WORD, wParam) = %BN_CLICKED THEN
                  FRMTOASTMESSAGE_Show(hwnd)
                  EXIT FUNCTION
               END IF
           
            CASE %IDCANCEL
               ' // If the Escape key has been pressed...
               IF HI(WORD, wParam) = %BN_CLICKED THEN
                  ' // ... close the application by sending a WM_CLOSE message
                  SendMessage hwnd, %WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
         END SELECT

      CASE %WM_DESTROY
         ' // End the application
         PostQuitMessage 0
         EXIT FUNCTION

   END SELECT

   ' // Pass unprocessed messages to Windows
   FUNCTION = DefWindowProc(hwnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================


José Roca

Ok. I will make a change to the CWindow class to allow to be used in a DLL.

To work you will need to add


pWindow.ClassName = "MyClassName" ' --> or another name
' Don't use "PBWindowClass" or "PBFrameClass"


After:


    ' // Create an instance of the class
    Local pWindow As IWindow
    pWindow = Class "CWindow"
    If IsNothing(pWindow) Then Function = -1: Exit Function



José Roca

The  problem was that the DLL becomes isolated code. Therefore, CWindows was creating the same class name for the window in the DLL than the one for the main program, and RegisterClassEx failed.

Rudolf Furstauer

Thanks for your great support!
Maybe Paul should also set it in FF, so the use of dialogues in DLL's is possible.

José Roca

#6
Quote
Maybe Paul should also set it in FF, so the use of dialogues in DLL's is possible.
Paul doesn't need to do anything. I already have made the needed changes and it works. You just will need to use pWindow.ClassName = <MyClassName> after pWindow = Class "CWindow" when used in a DLL.

Regarding the complex High DPI problem, the only thing that remains pending is when, in Windows 7, you change the DPI percent but at the same time you check the "Use Windows XP Style DPI Scaling" setting. When this setting is checked, the application must not do scaling, to honor the user preferences. The problem is how to know programatically if this setting is checked or not. In Windows 7 there is a way reading the registry:

HKEY_CURRENT_USER
  |__ Software
    |__ Microsoft
      |__ Windows
         |__DWM
           |__ value of "UseDpiScaling" (0 or 1)

I have read that this key doesn't exist in Windows Vista. Can anyone confirm it?

José Roca

Normally, when someone changes the DPI to increase the size of the UI elements, applications must also scale windows, controls and fonts. In this case, you no longer have to thing in pixels, but in dots per inch. That is, 100 is no longer 100 pixels, but 100 dots per inch, that depending of the DPI percent can be 120 pixels, 130, etc. If the application is not High DPI aware and Aero is activated, Windows 7 virtualizes it, i.e. it does the scaling but leaves you to believe that you are working at 96 DPI. The problem is, that depending of the application, you get artifacts, fuzzy fonts and several other nasty troubles. To complicate matters further, if you choose a theme like the Windows 7 Basic Theme, virtualization is disabled and the program must do scaling, but if you check the "Use Windows XP Style DPI Scaling" setting, then the program must honor it and not scale.

The only way for a program to work properly in all these options of Windows 7 is making the application fully High DPI aware, and we must know how to deal with it because a visual designer is not going to write all the code of your aplication for you, and if you use a GDI function like GetWindowRect, that is not DPI aware, it will return the dimensions in pixels, not in dot per inchs, and you must downscale these values. Otherwise, you can get a width of, e.g. 120 pixels when in fact it should be 100 dots per inch, for example. This is why Microsoft wants to deprecate GDI. We need new functions that work with dots per inch instead of pixels.

That is why I have been saying during more than a year and a half that everybody should read carefully and understand all that it is said in this article: http://msdn.microsoft.com/en-us/library/windows/desktop/dd464660%28v=vs.85%29.aspx

If you don't do it right, an application that looks fine in your computer can look terrible in another with different settings.

Even Microsoft had to deprecate Windows Forms, that was not DPI aware, and replace it with WPF.

DDT does it almost right if you use dialog units instead of pixels, but has some rounding problems because it uses longs instead of singles, and the font statements aren't DPI aware (fonts also have to be scaled according the DPI settings when you create them). And when using GDI functions, you also need to do conversions with DIALOG UNITS TO PIXELS and PIXELS TO DIALOG UNITS.


José Roca

Another article that you should read to understand the different mechanisms that have been used in different versions of Windows to accommodate monitors with a high pixel density.

http://www.kynosarges.de/WindowsDpi.html

Paul Squires

Thanks Jose, the classname was indeed the problem. I have updated FF's code generation to create unique class names and assign them to pWindow.

I will have this new code in the next FF update.

Thanks!
Paul Squires
PlanetSquires Software

Jim Dunn

3.14159265358979323846264338327950
"Ok, yes... I like pie... um, I meant, pi."

Elias Montoya


Jose, in my tests, FF is creating the dialogs and controls just fine, its FF_Control_SetLoc and FF_Control_SetSize the ones that arent doing very well, placing and sizing controls incottectly. Also the Firesplitter is guilty of the same sin.

I was thinking that maybe we could pull the DPI setting from registry and do the conversion as follows:

FF_Control_GetLoc(HControl, X, Y)

FF_Pixel_Edit(X, Y, 10, 15) ' Increase the DPI correspondent to 10 pixels for X and 15 for Y.

or...

FF_Pixel_Set(X, Y, 10, 15) ' Set the DPI correspondent to 10 pixels for X and 15 for Y.


FF_Control_SetLoc(HControl, X, Y) ' Set the correct DPI location for the control.


?

Doing it this way we dont have to worry about modifying older programs, just adding this two new functions.

Oh, but the firesplitter is going to be a little out of our reach.

Win7, iMac x64 Retina display 5K, i7-5820K 4.4 ghz, 32GB RAM, All updates applied. - Firefly 3.70.

José Roca

#12
Quote from: Jim Dunn on February 19, 2012, 02:59:07 PM
Quote from: Jose Roca on February 19, 2012, 03:36:47 AM"UseDpiScaling" I have read that this key doesn't exist in Windows Vista. Can anyone confirm it?

Looks like YES, it is in VISTA:

http://social.msdn.microsoft.com/Forums/en-AU/vcgeneral/thread/5deb0008-23ba-4996-9c96-19afc9c41b33
http://msdn.microsoft.com/en-us/library/windows/desktop/ee417691%28v=vs.85%29.aspx

Maybe I did read old information and it was not in the original version and has been added in a service pack.

In this case we must modify the new AfxGetUseDpiScaling function in Windows.inc.


' ========================================================================================
' Retrieve the value of the UseDpiScaling setting (Windows 7).
' ========================================================================================
FUNCTION AfxGetUseDpiScaling () AS LONG

   LOCAL hr AS LONG
   LOCAL hKey AS DWORD
   LOCAL wszBuff AS WSTRINGZ * 256
   LOCAL dwType AS DWORD
   LOCAL cbData AS DWORD

   IF AfxGetWindowsVersion < 6.01 THEN EXIT FUNCTION   ' // Must be Windows 7 or superior
   IF RegOpenKeyExW(%HKEY_CURRENT_USER, "Software\Microsoft\Windows\DWM", 0, %KEY_QUERY_VALUE, hKey) = %ERROR_SUCCESS THEN
      IF hKey THEN
         cbData = SIZEOF(wszBuff)
         hr = RegQueryValueExW(hKey, "UseDpiScaling", 0, dwType, wszBuff, cbData)
         RegCloseKey hKey
         IF hr = %ERROR_SUCCESS THEN
            wszBuff = EXTRACT$(wszBuff, CHR$(0))
            ' // Extracts DWORD from string
            FUNCTION = CVDWD(wszBuff)
         END IF
      END IF
   END IF

END FUNCTION
' ========================================================================================


It would only be needed to remove this line:


   IF AfxGetWindowsVersion < 6.01 THEN EXIT FUNCTION   ' // Must be Windows 7 or superior


The rest of the code must work in all Windows versions. Since I'm doing error checking, if the node or key doesn't exist it simply would return FALSE.

Paul Squires

Quote from: Elias Montoya on February 19, 2012, 03:45:19 PM

Jose, in my tests, FF is creating the dialogs and controls just fine, its FF_Control_SetLoc and FF_Control_SetSize the ones that arent doing very well, placing and sizing controls incottectly. Also the Firesplitter is guilty of the same sin.

I was thinking that maybe we could pull the DPI setting from registry and do the conversion as follows:

FF_Control_GetLoc(HControl, X, Y)

FF_Pixel_Edit(X, Y, 10, 15) ' Increase the DPI correspondent to 10 pixels for X and 15 for Y.

or...

FF_Pixel_Set(X, Y, 10, 15) ' Set the DPI correspondent to 10 pixels for X and 15 for Y.


FF_Control_SetLoc(HControl, X, Y) ' Set the correct DPI location for the control.


?

Doing it this way we dont have to worry about modifying older programs, just adding this two new functions.

Oh, but the firesplitter is going to be a little out of our reach.


Jose's cWindow class already does the conversions for us. The class has methods that calculate positioning, client size, window size, etc, and will adjust the values based on the scaling ratios. FF needs to update much of its internals and FireFly Functions to call these methods. Fair amount of work but will be worth it in the long run.
Paul Squires
PlanetSquires Software

José Roca

To understand the importance of this setting, look at the attached picture. The program has been compiled using the new headers, that takes into account the UseDpiScaling setting.

The image on the left shows the output in my computer with "Use Windows XP Style DPI Scaling" unchecked, and the second with that option checked; a resolution of 1366x768 and a DPI of 140%. The output will vary with different resolution and DPI.