• Welcome to Powerbasic Museum 2020-B.
 

News:

Forum in repository mode. No new members allowed.

Main Menu

Implementing IBindStatusCallback

Started by Jon Eskdale, November 24, 2013, 04:53:44 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Jon Eskdale

I have a program that I've written using FireFly and is all working except now I need to improve the error handling

It uses the URLDownloadToFileA call, but I currently have the Callback defined as Nothing.

Doing a search of the Forums I found Edwins code from 2003

I can use Edwins code from the Powerbasic Forum post 20073  http://www.powerbasic.com/support/pbforums/showthread.php?t=20073&highlight=IBindStatusCallback&page=2 With some minor mods to get it to compile with the PB10.  However to use it requires modifying Joses Declare.  Joses Declare is the correct one so would like to do it right.  Edwins code was written before the Compiler supported COM.

I can understand Edwins code OK, but I need someone to give me a pointer as to where to learn how to implement IBindStatusCallback using the PB COM support.   I've spent the last 5 hrs reading and searching the forums so if anyone knows of any specific reading I should follow or ideally any samples of similar code in Powerbasic that I could work through I would be very grateful.  Unless of course this has already been done.

Thanks Jon

José Roca

This is how you can do it using PB low-level COM:


#COMPILE EXE
#DIM ALL
#INCLUDE ONCE "windows.inc"
#INCLUDE ONCE "urlmon.inc"

FUNCTION PBMAIN

   ' // Create an instance of our implementation of the IBindStatusCallback interface
   LOCAL pfnCB AS IBindStatusCallbackImpl
   pfnCB = CLASS "CBindStatusCallBackImpl"

   ' // Do the download
   UrlDownloadToFile(NOTHING, "http://www.powerbasic.com/support/pbforums/", _
      EXE.PATH$ & "test.html", 0, pfnCB)

END FUNCTION

' ========================================================================================
' Custom implementation of the IBindStatusCallback interface
' ========================================================================================

$CLSID_CBindStatusCallBackImpl = GUID$("{8FFDCF89-1C26-454A-94AC-6214E21513E0}")   ' --> change me if needed

CLASS CBindStatusCallBackImpl $CLSID_CBindStatusCallBackImpl AS COM   ' MUST be AS COM to avoid dead code removal

INTERFACE IBindStatusCallbackImpl $IID_IBindStatusCallback

   INHERIT IUnknown

   ' =====================================================================================
   METHOD OnStartBinding ( _                            ' VTable offset = 12
     BYVAL dwReserved AS DWORD _                        ' __in DWORD dwReserved
   , BYVAL pib AS IBinding _                            ' __in IBinding *pib
   ) AS LONG                                            ' HRESULT
   
     ' Put your code here
   
   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD GetPriority ( _                               ' VTable offset = 16
     BYREF pnPriority AS LONG _                         ' __out LONG *pnPriority
   ) AS LONG                                            ' HRESULT

     ' Put your code here

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnLowResource ( _                             ' VTable offset = 20
     BYVAL reserved AS DWORD _                          ' __in DWORD reserved
   ) AS LONG                                            ' HRESULT

     ' Put your code here

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnProgress ( _                                ' VTable offset = 24
     BYVAL ulProgress AS DWORD _                        ' __in ULONG ulProgress
   , BYVAL ulProgressMax AS DWORD _                     ' __in ULONG ulProgressMax
   , BYVAL ulStatusCode AS DWORD _                      ' __in ULONG ulStatusCode
   , BYREF szStatusText AS WSTRINGZ _                   ' __in LPCWSTR szStatusText
   ) AS LONG                                            ' HRESULT

     ' Put your code here

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnStopBinding ( _                             ' VTable offset = 28
     BYVAL hresult AS LONG _                            ' __in HRESULT hresult
   , BYREF szError AS WSTRINGZ _                        ' __in LPCWSTR szError
   ) AS LONG                                            ' HRESULT

     ' Put your code here

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD GetBindInfo ( _                               ' VTable offset = 32
     BYREF grfBINDF AS DWORD _                          ' __out DWORD *grfBINDF
   , BYREF pbindinfo AS BINDINFO _                      ' __in_out BINDINFO *pbindinfo
   ) AS LONG                                            ' HRESULT

     ' Put your code here

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnDataAvailable ( _                           ' VTable offset = 36
     BYVAL grfBSCF AS DWORD _                           ' __in DWORD grfBSCF
   , BYVAL dwSize AS DWORD _                            ' __in DWORD dwSize
   , BYREF pformatetc AS FORMATETC _                    ' __in FORMATETC *pformatetc
   , BYREF pstgmed AS STGMEDIUM _                       ' __in STGMEDIUM *pstgmed
   ) AS LONG                                            ' HRESULT

     ' Put your code here

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnObjectAvailable ( _                         ' VTable offset = 40
     BYREF riid AS GUID _                               ' __in REFIID riid
   , BYVAL punk AS IUnknown _                           ' __in IUnknown *punk
   ) AS LONG                                            ' HRESULT

     ' Put your code here

   END METHOD
   ' =====================================================================================

END INTERFACE

END CLASS
' ========================================================================================


Jon Eskdale

Thank you so much - Really appreciated - I've still been trying - So I've so far learnt a lot about what doesn't work.

I'll try yours now - I have been trying to convert a C sample

Thanks again
Jon

Jon Eskdale

All working in my program - Thanks again -Jon

Jon Eskdale

Is there any reason why this would not run in another thread?

Situation is it works perfectly in the main thread and it reports all the progress and downloads data

If I create a new thread so I can monitor the progress in the main thread, and then abort if something goes wrong.
I run the URLDownloadFileA in the new thread but with the last parameter set to Nothing the download works - all data is downloaded but as expected there is no Callback functions but this proves the thread is running and passing the parameters correctly.

If I put the last parameter of the call, back to pfnCB so I get the Callbacks I get a
Callback for GetBindingInfo
Callback OnStartBinding
Callback Progress with statuscode of 32 (DetectingProxy)  - this is normal it does this when running in the main thread
but there it stays the thread just stays running no more Callbacks
The Main thread reports the new thread as still running, but its not making any callbacks or returning from the URLDownloadToFileA

I'm out of ideas

Thanks - if you have any ideas
Jon

José Roca

#5
Are you calling pfnCB = CLASS "CBindStatusCallBackImpl" in the new thread or in the main thread? By default, PB initializes the COM library as single threaded apartment. So if you initialize the class in the main thread and then try to use it in another thread, there will be problems. Is there any reason to put that code in another thread?

Alternatively, you can try putting the following calls as the two very first lines of WINMAIN


CoUninitialize
CoInitializeEx BYVAL %NULL, COINIT_MULTITHREADED


and


CoUninitialize


as the last line of your application.

However, the multi-threaded apartment is intended for use by non-GUI threads. Threads in multi-threaded apartments should not perform UI actions. This is because UI threads require a message pump, and COM does not pump messages for threads in a multi-threaded apartment.

Jon Eskdale

Hi Jose

No I am calling the pfnCB = CLASS "CBindStatusCallBackImpl" in the new thread

The reason I put it in another thread is so I could put up a message box in the the GUI thread if there was a long delay in retrieving the data from the web server.  I was trying to put up a box that says "Data retrieval is taking longer than expected - Do you wish to wait or abort the transfer" If they say abort I was going to set the return of the progress to E_ABORT.  otherwise I would give it another 30 seconds to transfer before repeating the message box.

I tried putting the

CoUninitialize
CoInitializeEx BYVAL %NULL, COINIT_MULTITHREADED

as the first lines in FF_WinMain but I get the Compile Error

Error 519: MISSING DECLARATION: COINIT_MULTITHREADED

Thanks Jon

José Roca


Jon Eskdale

Thanks Jose

I did try that although that also gives an error as its undefined - I also then defined %COINIT_MULTITHREADED = 0 which is what I think it is.
But still the same issue it starts but then hangs.  I did some reading on the COINITIALIZEEX and mentioned about the thread that was calling it so also tried putting as the first instructions in the Thread but still the same.

The COM works in the main thread and the URLDownloadtoFileA works in the second thread but COM doesn't work at least correctly in the second thread

Below is the Debug output

[19512] About to call URLDownloadToFileA C:\Users\JON~1.ESK\AppData\Local\Temp\TempISAF.xml
[19512] GetBindInfo
[19512] OnStartBinding
[19512] ulStatusCode =  32
[19512] Thread status = 103
[19512] Thread status = 103
[19512] Thread status = 103
[19512] Thread status = 103
[19512] Thread status = 103
[19512] Thread status = 103

Jon

Jon Eskdale

I thought I would create a simple program to demonstrate the problem so that I could post the code on the forum.  I did this and would you believe it worked perfectly.  So next thought perhaps its something that FireFly is doing, so I wrote a similar program in Firefly and you guessed it, it worked perfectly.

I have modified my program code to be identical to the small test programs even to the extent of hardcoding the source URL and the file as in the test program but it still does  not work in the main program. Why I have no idea - If there is a Genius out there that has a suggestion on what to try.

This is the code of the test program which works

For those that are interested it is for an interface between Sailwave (www.sailwave.com) and ISAF
( International Sailing Federation)

Thanks Jon


#COMPILE EXE
#DIM ALL
#INCLUDE ONCE "windows.inc"
#INCLUDE ONCE "urlmon.inc"

FUNCTION PBMAIN
   global hWthread as dword
   global AbortDownload as long
   local tcount, nRet as long

   
   if isfile(EXE.PATH$ & "test.html") THEN
      kill EXE.PATH$ & "test.html"
   END IF

   THREAD CREATE URLDOWNLOAD(0) TO hWthread
   tcount = 0
   DO
      SLEEP 100
      INCR tcount
      THREAD STATUS hWthread TO nRet
      OUTPUTDEBUGSTRING "Thread status = " + HEX$(nRet)
      IF tcount = 80 THEN     ' Abort after 8 seconds
         AbortDownload = 1
      END IF
   LOOP UNTIL nRet <> &h103
   OUTPUTDEBUGSTRING "Thread reported as terminated code = " + HEX$(nRet)


END FUNCTION

THREAD FUNCTION URLDOWNLOAD(BYVAL dwData AS DWORD) AS DWORD
   ' // Create an instance of our implementation of the IBindStatusCallback interface
   ' // Create an instance of our implementation of the IBindStatusCallback interface
   LOCAL pfnCB AS IBindStatusCallbackImpl
   pfnCB = CLASS "CBindStatusCallBackImpl"

   OUTPUTDEBUGSTRING "About to call URLDownloadToFileA "
   ' // Do the download
   UrlDownloadToFile(NOTHING, "http://www.powerbasic.com/support/pbforums/", _
      EXE.PATH$ & "test.html", 0, pfnCB)   

   pfnCB = nothing
end function


' ========================================================================================
' Custom implementation of the IBindStatusCallback interface
' ========================================================================================

$CLSID_CBindStatusCallBackImpl = GUID$("{8FFDCF89-1C26-454A-94AC-6214E21513E0}")   ' --> change me if needed

CLASS CBindStatusCallBackImpl $CLSID_CBindStatusCallBackImpl AS COM   ' MUST be AS COM to avoid dead code removal

INTERFACE IBindStatusCallbackImpl $IID_IBindStatusCallback

   INHERIT IUnknown

   ' =====================================================================================
   METHOD OnStartBinding ( _                            ' VTable offset = 12
     BYVAL dwReserved AS DWORD _                        ' __in DWORD dwReserved
   , BYVAL pib AS IBinding _                            ' __in IBinding *pib
   ) AS LONG                                            ' HRESULT
   
      OUTPUTDEBUGSTRING "OnStartBinding"
      METHOD = %E_NOTIMPL
   
   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD GetPriority ( _                               ' VTable offset = 16
     BYREF pnPriority AS LONG _                         ' __out LONG *pnPriority
   ) AS LONG                                            ' HRESULT

      OUTPUTDEBUGSTRING "GetPriority"
      METHOD = %E_NOTIMPL

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnLowResource ( _                             ' VTable offset = 20
     BYVAL reserved AS DWORD _                          ' __in DWORD reserved
   ) AS LONG                                            ' HRESULT

      OUTPUTDEBUGSTRING "OnLowResource"
      METHOD = %E_NOTIMPL

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnProgress ( _                                ' VTable offset = 24
     BYVAL ulProgress AS DWORD _                        ' __in ULONG ulProgress
   , BYVAL ulProgressMax AS DWORD _                     ' __in ULONG ulProgressMax
   , BYVAL ulStatusCode AS DWORD _                      ' __in ULONG ulStatusCode
   , BYREF szStatusText AS WSTRINGZ _                   ' __in LPCWSTR szStatusText
   ) AS LONG                                            ' HRESULT

      OUTPUTDEBUGSTRING "ulStatusCode = " + STR$(ulStatusCode)
      IF AbortDownload THEN
         METHOD = %E_Abort
      ELSE
         METHOD = %S_OK
      END IF

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnStopBinding ( _                             ' VTable offset = 28
     BYVAL hresult AS LONG _                            ' __in HRESULT hresult
   , BYREF szError AS WSTRINGZ _                        ' __in LPCWSTR szError
   ) AS LONG                                            ' HRESULT

      OUTPUTDEBUGSTRING "OnStopBinding"
      METHOD = %E_NOTIMPL

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD GetBindInfo ( _                               ' VTable offset = 32
     BYREF grfBINDF AS DWORD _                          ' __out DWORD *grfBINDF
   , BYREF pbindinfo AS BINDINFO _                      ' __in_out BINDINFO *pbindinfo
   ) AS LONG                                            ' HRESULT

      OUTPUTDEBUGSTRING "GetBindInfo"
      METHOD = %E_NOTIMPL

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnDataAvailable ( _                           ' VTable offset = 36
     BYVAL grfBSCF AS DWORD _                           ' __in DWORD grfBSCF
   , BYVAL dwSize AS DWORD _                            ' __in DWORD dwSize
   , BYREF pformatetc AS FORMATETC _                    ' __in FORMATETC *pformatetc
   , BYREF pstgmed AS STGMEDIUM _                       ' __in STGMEDIUM *pstgmed
   ) AS LONG                                            ' HRESULT

      OUTPUTDEBUGSTRING "OnDataAvailable"
      METHOD = %E_NOTIMPL

   END METHOD
   ' =====================================================================================

   ' =====================================================================================
   METHOD OnObjectAvailable ( _                         ' VTable offset = 40
     BYREF riid AS GUID _                               ' __in REFIID riid
   , BYVAL punk AS IUnknown _                           ' __in IUnknown *punk
   ) AS LONG                                            ' HRESULT

      OUTPUTDEBUGSTRING "OnObjectAvailable"
      METHOD = %E_NOTIMPL

   END METHOD
   ' =====================================================================================

END INTERFACE

END CLASS
' ========================================================================================

José Roca

> so I wrote a similar program in Firefly and you guessed it, it worked perfectly.

But the FF program that works has a GUI or not?

Jon Eskdale

#11
Hi Jose,

Yes it has a GUI only a single form with one button on it but I thought that would simulate it.

The code for both the FF test and the main program is in the handler for a button - That works in the test program - It's probably something stupid but I'm running out of ideas.

The main program  that contains this code has a couple of forms the main one has a tab control with three tabs.  One of the Tabs has a MLG Grid (MyLittleGrid) using a SLL and other tab has a calendar control and Listviews plus the usual labels text boxes etc.  Nothing really unusual.

What I keep coming back to is that even in my main program it works fine if it is not in a separate thread and it works in the separate thread if I don't use COM

Oh and it uses the XMLLite dll to decode the received XML

I think this is putting me off COM  :(
Jon

Jon Eskdale

Hi Jose,

I found what was causing the problem, it was where I was updating a text box in the Callback with the status - Strange it worked when it was in the main thread but not in the other thread.  But it does leave one question with this COM Callback, as I understand the interface to the class is fixed by Microsoft so what is the best way of getting data in and out of the callback other than via a GLOBAL variable.

I need to signal to the Callback when it needs to return an abort.  I'm currently doing this via a GLOBAL variable and now another GLOBAL variable to report the Status back to the Main Thread.  Is there a better way?

Thanks

Jon

José Roca

> Strange it worked when it was in the main thread but not in the other thread.

No, it's not strange. There's a general rule of thumb that says don't update the UI from any thread other than the UI thread itself.

> Is there a better way?

Don't know. I don't have expertise using threads.

Jon Eskdale

Thanks Jose.

I agree I don't normally update the UI from a thread and it wasn't immediately obvious as it was in the class that it was getting updated so it wasn't in the Thread Function otherwise it would have rung alarm bells to me when I looked at it.  But was thinking that the Class was all working previously - anyway that is another mistake I won't make again (until next time :) )

The question of how to get data in and out of the class arises because the callback is a COM class and is therefore its interface is fixed. Microsoft say to make the class return abort if you want to cancel the download.  I think I will stay with a Global its simple and works Guess you could use semaphores or something but what do you gain other than some complexity.

I'm away for a few days but will post what I use when I get back - In case any one is interested

Jon