Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Progress bar in a DLL, step-by-step

sabalan
New Contributor I
1,256 Views

I am trying to implement a progress bar inside a Fortran DLL (CVF 6.6C3). The DLL is called from a VB6 dialog. I have tried to use Jugoslavs ThreadDlg, have tried to use the sample DLLPRGRS, have read almost all relevant topics in this forum and in on-line help, but I have been more and more confused:
- Jugoslavs example is called from a console;
- DLLPRGRS creates a main dialog and then calls the progress bar. This example uses WinMain function and calls DlgInit;
- On-line help states that one should rather call DlgInitWithResourceHandle and recommends DLLMain function;
- And I cannot find some how to in this forum either.

Could anyone please post a step-by-step how to here?

The structure of my application is as following:

A VB dialog containing text boxes and buttons calls a Fortran MyDLL;
MyDLL looks something like this:

Subroutine MyDLL (,,,,)
Use MyModule

Call Sub1

Call Sub2

Call Sub3(,,,)

END

Inside Sub3 I have time-consuming calculations and what I am trying to do is to show a progress bar from within this subroutine:

Subroutine Sub3(,,,)
Use MyModule


DO

-->Show the progress bar

END DO

End Subroutine Sub3

Any help would be appreciated.

Regards,

Sabalan.

Message Edited by Sabalan on 07-01-2004 05:20 AM

0 Kudos
7 Replies
Jugoslav_Dujic
Valued Contributor II
1,256 Views
Well, don't get confused -- it's not that complicated :-). ThreadDlg just happens to be a console application, and it would as easily work as Win32 (like callcon and calldlg ones from DllPrgrs sample).
Before we proceed, please clarify:
1) Where do you want the progress bar to be displayed:
a) in the VB's main form
b) as part of a separate dialog within dll?
2) You need to keep pumping messages to the GUI, so that the dialog is updated correctly. You can achieve that
a) using a separate thread like in ThreadDlg sample
b) by periodically pumping messages via PeekMesssage loop, like in DllPrgrs sample.
DlgInitWithResourceHandle is required if your dialog resource is stored in dll, as opposed to an exe, but that is really a side issue.
Jugoslav
0 Kudos
sabalan
New Contributor I
1,256 Views

Thanks Jugoslav for your response.

1) I suspect that callback from DLL to VB would make the thing more complicated. I think it would be much easier to open a new dialog from within DLL, show only the progress bar without any buttons, and kill this dialog when the loop is done and before returning to the VB interface;

2) I was more confused with DllPrgrs than with your ThreadDLL and I think your auxiliary thread is perhaps an easier solution.

Sabalan.

0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,256 Views
I suspect that callback from DLL to VB would make the thing more complicated.
Not much, actually. I think it's a cleaner design, as you leave the GUI part on GUI-handling side, i.e. VB. If your dll is tied to the VB caller, approach with dialog within the dll is ok as well. I'll spare few lines on how I designed the interface for another application of mine. Its long-lasting routine has an "optional" argument which is address of callback function, called 100 times (or less). The callback is STDCALL, with one argument (nPercent), and return value indicateswhether the calculation should be canceled (.FALSE.). If the calling application doesn't need progress/cancel capability, it will simply provide NULL:
integer function DMS_Execute(..., lpfnProgressProc)
use Globals
integer(INT_PTR_KIND()):: lpfnProgressProc
...
pProgressProc = lpfnProgressProc
since the actual calculation is some 6 calling levels away from this routine, I used a global cray pointer to function (so that I don't have to pass it through 6 argument-lists):
module Globals
interface
integer function DMS_ProgressProc(nPercent)
!DEC$ATTRIBUTES STDCALL:: DMS_ProgressProc
integer:: nPercent
end function
end interface
pointer(pProgressProc, DMS_ProgressProc)

the trickier part was to calculate number of passes nTotalIter in advance (so that it's called 100 times or less). Then, in the main do-loop of calculation:
...
use Globals
...
if (pProgressProc.ne.0) then
nIter = nIter + 1
iPercent = nint(100*real(nIter)/nTotalIter)
if (iPercent.ne.iPrevPercent) then
if (DMS_ProgressProc(iPercent).eq.0) then
iError = DMSERR_CANCELLED
call ErrHandler(1, iError)
return
end if
iPrevPercent = iPercent
end if
end if
In your case, VB callback should update its progress bar and call (if I recall correctly)Application.ProcessMessages, which is equivalent of PeekMessage loop. The callback should be an Integer Function with one ByVal Integer argument. Declare it as ByVal/ByRef integer and pass it using AddressOf operator (my memory is vague on VB though).
Pure Fortran approach, with dialog resource within dll,is just a bit simpler. You can write your own "callback", (actually, a normal subroutine)and call DlgSet(IDC_PROGRESS) from there, along with PeekMessage loop as in DllPrgrs. On startup, you should call DlgInitWithResourceHandle to load the dialog resource. Then, you should apply DlgModeless (it's probably a good idea to pass the VB form's HWND so that you can use that as parent of the dialog so that it is in proper Z-order (on top)).
All above assumes that there is no additional thread. (If there is, Application.ProcessMessag es is not needed).
Jugoslav

Message Edited by JugoslavDujic on 06-29-2004 02:28 PM

0 Kudos
sabalan
New Contributor I
1,256 Views

Thank you very much Jugoslav for your help. I had to find my own way anyway!

What got me most confused was the relationship between WinMain or DllMain described in the sample DllPrgrs or on-line documentation, and my own DLL. I wasnt sure if a WinMain or DllMain was needed except for my own DLL or not, and how I should call WinMain or DllMain. I discovered at last LoadLibrary and solved the problem. Now the progress bar works and I will try to write here a step-by-step instruction for a simplest possible (I think?) progress bar. Iask all experts here to look at this and let me know if I am doing something wrong (loading MyDLL twice, memory leak, etc.?).

1-Create a simple progress bar dialog without any buttons in CVF in a DLL or Win32 application. Save this project as ProgressBar;

2-Add the files ProgressBar.rc, Resource.fd and Resource.h to your project MyDLL if you created the ProgressBar in a separate project other than MyDLL;

3-You need a module MyGlobals for global variables in MyDLL if you plan not to showing the progress bar in the SUBROUTINE MyDLL itself and progress bar is going to appear later on in some other subroutine;

4-The above module is going to contain:
Module MyGlobals
(your other global variables)
Integer ghInst

End Module MyGlobals

5- Your main DLL is going to look like this:
Subroutine MyDLL (,,,,)
!DEC$ ATTRIBUTES DLLEXPORT, STDCALL :: MyDLL
!DEC$ ATTRIBUTES ALIAS : 'MyDLL' :: MyDLL

Use DFWin
Use MyGlobals
Logical lret

ghInst = LoadLibrary("MyDLL.dll"C)
! ghInst is saved in MyGlobalsand will be used later

Call Sub1

Call Sub2

Call Sub3(,,,)
! In this sub. you want to show the progress bar

lret = FreeLibrary(ghInst)! You must run this after LoadLibrary
END

6- The Sub3 where you want to show the progress bar looks like this:

Subroutine Sub3(,,,)

Use DFWin
Use DFLOGM
Use MyGlobals

Include Resource.fd
Type (Dialog):: dlg
Type (T_MSG) :: mseg
Logical lret, lNotQuit
Integer Duration, Iter, iret
...
lret = DlgInitWithResourceHandle (IDD_DIALOG1, ghInst, Dlg) ! ghInst is used here
lret = DlgSet(Dlg, IDC_PROGRESS, 0, DLG_RANGEMIN)

Duration = (a number suitable for the duration of your long-lasting calculation, say, 100, 1000. Test to find!)

lret = DlgSet(Dlg, IDC_PROGRESS, Duration, DLG_RANGEMAX)

lret = DlgSet(Dlg, IDC_PROGRESS, 0)
lret = DlgModeless(Dlg)


Iter = 0
DO (your calculation)

! Show the progress bar here
Iter = Iter + 1
lret = DlgSet(Dlg, IDC_PROGRESS, Iter)
! Dispatch all pending window messages
lNotQuit = .true.
Do while ( lNotQuit .and. (PeekMessage(mesg, 0, 0, 0, PM_NOREMOVE) .ne. 0) )
lNotQuit = GetMessage(mesg, NULL, 0, 0)
if (lNotQuit) then
if ( DlgIsDlgMessage(mesg) .EQV. .FALSE. ) then
lret = TranslateMessage( mesg )
iret = DispatchMessage( mesg )
end if
end if
END DO

END DO

Call DlgExit(Dlg)
CALL DlgUnInit(Dlg) ! with these 2 CALLs you kill the progress bar dialog
End Subroutine Sub3

7- You declare MyDLL in VB as usaual and CALL it as usual!

Thanks again,
Sabalan.
0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,256 Views
I'll take a look on the rest of the code tomorrow. Until then, just a comment on first part:
What got me most confused was the relationship between WinMain or DllMain described in the sample DllPrgrs or on-line documentation, and my own DLL. I wasnt sure if a WinMain or DllMain was needed except for my own DLL or not, and how I should call WinMain or DllMain.
DllMain is irellevant in most contexts. You can mostly forget about it: when you build a dll, the compiler supplies a default one if you didn't define any for whatever purpose. DllMain is usually called just once when the dll is loaded via LoadLibrary -- since dll's are another things than .exes, the most oftheir purpose occurswhen its exported routines arecalled, not in DllMain.WinMain is of course another story, as it's the sole entry point for the program. DllMain is supposed to do a little or nothing, contrary to the WinMain.
I discovered at last LoadLibrary and solved the problem.
You should really use GetModuleHandle instead. There's probably no harm in using LoadLibrary, but it increases "reference count" on how many times is dll loaded into process' address space and . It's not really loaded twice, just has reference count of two, but it may confuse the calling process (I believe VBrelies onreference-counting as it does a LoadLibrary itself behind the scene).Reading&storing hInstance (hModule) from DllMain is just a bit preferred over GetModuleHandle (as it will work even if the dll is renamed) but, as you discovered yourself, it's a bit of overkill.
I'm certain that HMODULE and HINSTANCE confusion in Win32 wasn't helpful either :-) (yes, these two are the very same thing).
Jugoslav
0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,256 Views

I looked it and it's quite OK. Just a comment on message loop:

Do while ( lNotQuit .and. (PeekMessage(mesg, 0, 0, 0, PM_NOREMOVE) .ne. 0) )
lNotQuit = GetMessage(mesg, NULL, 0, 0)
if (lNotQuit) then

PeekMessage with PM_REMOVE will do the same thing simpler. However, for the special case of WM_QUIT (which will occur if the user decides to close the entire application during your calculation), this won't be good enough. It will exit your message loop but not the main one, whichshould cause the application to terminate. Instead, I suggest something along the following lines:
Do while ( lNotQuit .and. (PeekMessage(mesg, 0, 0, 0, PM_REMOVE) .ne. 0) )
if (mesg.message .eq. WM_QUIT) then
l = PostMessage(mesg.hwnd, mesg.message, mesg.wParam, mesg.lparam)
exit MainLoop
end if
if ( DlgIsDlgMessage(mesg) .EQV. .FALSE. ) then
...
end if
end do

The PostMessage(WM_QUIT) will end up in the "parent" message loop, properly terminating the application (provided that you don't call PeekMessage for it anymore).

Jugoslav

0 Kudos
sabalan
New Contributor I
1,256 Views

Thanks Jugoslav for all your comments.

I use now ghInst = GetModuleHandle("MyDLL.dll"C) inside SUB3 and this way the module MyGlobals and saving ghInst in it is not needed. I had to remove FreeLibrary too because I get access violation error for RETURN and/or END for the obvious reason that after FreeLibrary which is called after GetModuleHandle, there is no loaded DLL left to END.

Regarding WM_QUIT, i don't care about it because the user is not able to quit my application while the calcuations are going on! :-)

Sabalan.

0 Kudos
Reply