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 (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
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
' ========================================================================================
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
All working in my program - Thanks again -Jon
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
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.
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
Must be %COINIT_MULTITHREADED.
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
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
' ========================================================================================
> 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?
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
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
> 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.
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
It is not inside the thread function, but it belonsg to that thread, since you're creating the class in it.
> 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.
You can add your own methods AFTER the last one, and use instance variables, but you can only call them from the thread that has created it, not from the main thread.