@qvb6: SendMessage *has* to switch threads as its synchronous by definition. PostMessage just appends to hWnd's message queue and returns (no thread switching). Subsequently the OS takes care this message queue to get "emptied" on the "correct" thread by the "correct" message pump -- remember that each thread has to have a separate msg pump, right?
Looks like you have to sub-class form in order to receive Send/PostMessage events. Should work well in combination with shared memory thread / recordset / shared memory address.
Code:
Private Sub Form_Load()
lngOldWindowProc = SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf NewWindowProc)
End Sub
Private Sub Form_Unload(Cancel As Integer)
SetWindowLong Me.hwnd, GWL_WNDPROC, lngOldWindowProc
End Sub
' IN MODULE
Public Function NewWindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case Msg
Case USR_MESSAGE1
'Debug.Print wParam
NewWindowProc = 0
Case Else
NewWindowProc = CallWindowProc(lngOldWindowProc, hwnd, Msg, wParam, lParam)
End Select
End Function
I'm going to try and make a single post to this thread. I've been watching it, and I've also gone down the road of multi-threading-for-VB6 a couple of times in the past. I've come up with what I thought were good approaches to multi-threading, but I've never found anything that I'd feel good enough about to put into production.
Also, along the way, I learned that there's the hierarchy of process, thread, & fiber. I suppose you could even work more into that hierarchy. You could certainly put core above process.
However, because of all the marshaling issues, I decided that if/when I need something like this, I'm just going to write an entirely new VB6 application (a new process). This accomplishes a few things. First, it puts me back into the possibility of being portable. Second, it brings me back to ground on which I feel extremely solid. Third, I can still do inter-process memory copying similar to marshaled-shared memory (but not exactly because memory is copied and not shared).
If I don't want a user to execute this second process, I can just put some command line flag on it. And, I'll just do all my inter-process communications with SendMessageTimeout and WM_COPYDATA, and a subclassed window in the receiving process. I've got it all setup, and there's no problem going both directions. If someone wants, I'll post my subclassing and send message code.
Y'all Take Care,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
That's exactly what I WAS doing. I had a GUI ActiveX application and a Service application that did backend work. It relays information via Referenced COM. But it became a pain to debug because I have to compile the GUI so that the service can interact via COM. Once I found out that you can do multi-thread in VB6, I figured eliminating the other process would tidy up a lot. There's a lot of send/receive code between processes for each function requiring data.
***, EVEN POSTMESSAGE IS BLOCKING MY WORKER THREAD....
What the heck am I doing wrong, ...
You've only relabled a SendMessageA to PostMessage in your Declare:
Declare Function PostMessage Lib "user32" Alias "SendMessageA"
Also - in case you'll correct this later - take note that you should use PostMessage
only in a low frequency from your Threads - because otherwise (e.g. when used as Progress-Callback in a high-frequented inner-loop),
you'll risk MessageQueue-Flooding at the receiving end (the hWnd of the "other thread" which was perhaps busy for a second or two).
I guess I'll never understand, why Timer-based Polling-approaches (as in the example I've posted),
are always regarded that "unfavourably" in the community.
Timerbased Polling is one of the few approaches, which allow you a true (and rock-stable)
decoupling between "independent tasks" (when detecting "changed state" with nearly unmeasurable system-resource-usage).
Looks promising, do you have the dim for WM_ID_ASYNCH_METHOD, cant find it anywhere...
It's your own message like WM_USER + ...
You can see how AsynchDispMethodCall works. Also there is InternetStatusCallback example where you can find the following methods:
Code:
Private Sub PutHandleAsync( _
ByVal hwnd As Long, _
ByVal hUrl As Long)
PostMessage hwnd, WM_PUTHANDLE, hUrl, ByVal 0&
End Sub
Private Sub PutLogAsync( _
ByVal hwnd As Long, _
ByRef sText As String)
PostMessage hwnd, WM_PUTLOG, SysAllocString(ByVal StrPtr(sText)), ByVal 0&
End Sub
Private Sub OnStatusCompleteAsynch( _
ByVal hwnd As Long, _
ByVal lStatus As Long, _
ByVal lError As Long)
PostMessage hwnd, WM_ONCOMPLETE, lStatus, ByVal lError
End Sub
' // Window proc of async window in main thread
Public Function AsyncWndProc( _
ByVal hwnd As Long, _
ByVal uMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Dim cObj As CAsynchDownloader
' // Get object from window bytes
vbaObjSetAddref cObj, ByVal GetWindowLong(hwnd, 0)
Select Case uMsg
Case WM_PUTLOG ' // Show log entry
Dim bstrText As String
' // Extract string
GetMem4 wParam, ByVal VarPtr(bstrText)
cObj.PutLog bstrText
Case WM_PUTHANDLE ' // Put request handle
cObj.RequestHandle = wParam
Case WM_ONCOMPLETE ' // INTERNET_STATUS_REQUEST_COMPLETE event
cObj.OnStatusComplete wParam, lParam
Case Else
AsyncWndProc = DefWindowProc(hwnd, uMsg, wParam, lParam)
End Select
End Function
I guess I'll never understand, why Timer-based Polling-approaches (as in the example I've posted),
are always regarded that "unfavourably" in the community.
I have no objection against Timers, but PostMessage is almost instantaneous compared to Timers, and does the same job, but a Timer is easier to use, while PostMessage requires subclassing.
***, EVEN POSTMESSAGE IS BLOCKING MY WORKER THREAD....
What the heck am I doing wrong, all the documentation says these things run async, I can never get anything to run async between threads.
Besides what others suggested, I don't see "Option Explicit" in any of the source files. Also, if you want a custom message, use one in the range of WM_APP, not WM_USER(VB Runtime and the Common Controls use some of WM_USER messages), or better yet, a custom message ID returned by RegisterWindowMessage.
The timer message has the lowest priority than other messages.
while PostMessage requires subclassing.
Not necessary.
Originally Posted by qvb6
Also, if you want a custom message, use one in the range of WM_APP, not WM_USER(VB Runtime and the Common Controls use some of WM_USER messages), or better yet, a custom message ID returned by RegisterWindowMessage.
In other words what The trick means is that you can post WM_KEYDOWN and receive Form_KeyDown event for instance -- no custom msg registration, no subclassing. There are other such pairs possible too.
I know that one could post a message like WM_LBUTTONDOWN so simulate a click to a hidden control, so no subclassing is needed.
Originally Posted by The trick
It isn't required for your own private windows.
Not sure what you mean, but for windows created from scratch, then WM_USER is okay to use, but anything else could use WM_USER messages, and worse, even if a control doesn't use WM_USER now, it could use one in future updates of the runtime, or Common Controls updates, while WM_APP is save to use in both cases.
I know that one could post a message like WM_LBUTTONDOWN so simulate a click to a hidden control, so no subclassing is needed.
Subclassing is a technique that allows an application to intercept and process messages sent or posted to a particular window before the window has a chance to process them. You can create your own message-only window and process the messages without subclassing. Alternatively, your thread can have no window at all but you can process the messages (PostThreadMessage).
Originally Posted by qvb6
Not sure what you mean, but for windows created from scratch, then WM_USER is okay to use, but anything else could use WM_USER messages, and worse, even if a control doesn't use WM_USER now, it could use one in future updates of the runtime, or Common Controls updates, while WM_APP is save to use in both cases.
Yes, i told about private window classes. Alternatively, you can use APC to call an arbitrary function in the context of the needed thread. The route thread can have only the loop with the SleepEx which switch the thread to the alertable state.
I appreciate everyone chiming in here, thanks guys.
@ Schmidt, I did realize I forgot to change the alias. Nice catch, I didn't think it was going to fix anything. But it did, finally async notifications...
I'm sure everyone will freak out about this statement but I like being event driven and it just seems cleaner to me. Plus I don't have to implement my own queue on top of a timer.
I need it only rarely, when a user requests GUI change via web server.
@ The trick, I looked for info on APC but couldn't find enough to implement.
--------
Using a hidden control may be even better. What are my options, are there any messages that don't take focus and allow me to send a int/long to identify the type of notification? I don't really want a fake form, prefer hidden control.
How safe is passing pointers? Can you handle errors if the memory is moved via change by other thread?
Edit: It doesn't look like WM_CHAR changes focus, may be able to use...
PostMessage Text1.hWnd, WM_CHAR, Asc("A"), 0&
Last edited by PsuFan; May 15th, 2019 at 08:46 PM.
Also for others trying, you cant use Schmidt's Form_Activate example with multiple forms, it executes multiple times. I couldn't get threading at startup, you will have to use one time timer to start threads if app has multiple forms (or store var to check if already loaded).
For multithreading, the ThreadHandler in RC5c that I use is great overall, but there are still the following problems. For example, thread Dll debugging is difficult, and sometimes it crashes for no reason to find out exactly where it is.
In addition, how to actively uninstall loaded threads? (th = regfree. ThreadObjectCreate (cThreadKey, ThreadLibPath, cThreadClass)) is set directly to set th = nothing?
TH. JobQueueCount ‘represents the task currently executed (including the number of functions or processes being executed?)
TH. CancelExecution ‘Is to cancel the task being performed? If multiple tasks are cancelled altogether?
TH. TimeoutSecondsToHardTerminate 'What does this do?
I ended up using shared memory class / PostMessage(Form.hWnd, WM_CHAR, Asc("A"), 0&) / Private Sub Form_KeyPress(KeyAscii As Integer) for Async notifications. Seems to be pretty clean and doesn't have any issues. I cant even fake keypresses to the form via real user input. This specific app won't have a local user anyway.
Compared to an Ax-EXE it has two advantages:
- The logic is in the app (and the code to be executued). So by any changes the app can be modified and the dll remains untouched as it's generic.
- Can be used regfree via side-by-side manifest.