Supposing I have made an app, or several apps, in VB.
I want send a message to one app from the other one, or from one instance to another. What are the methods I can use, apart from DDE?
Printable View
Supposing I have made an app, or several apps, in VB.
I want send a message to one app from the other one, or from one instance to another. What are the methods I can use, apart from DDE?
I found this application on VBForums, it uses winsock connection
Play around with it
OK thanks, that works. So thats one method. But Winsock is a pretty inefficient method unless you are sending messages to a different computer, which I'm not.
I'm looking for something more like a API call or similar. Any ideas?
Why not use a shared registry area.
Because then I would need notifications sent to all apps when the registry was updated. Which brings me back to my original problem.Quote:
Originally Posted by David.Poundall
What type of message are you looking to send?
So how fast do you want it to be ? Game speed or real time ?
A message from one instance/app telling the other to execute a command.
For example, "Open a New Window", or "Close Now" or similar. It needs to be immediate, like the SendMessage API. I was wondering if there was a way to use that, but not sure where to start.
Manipulating another window is fairly simple, using FindWindow and PostMessage and/or SendMessage. If you could give me some specific examples of what you want, I'll whip something together that should, at the very least, get you started.
Here is an scenario where I need it:
User opens my app (web browser).
User then manages to open it again, possibly by opening a .url link file or somehow doing some form of ShellExecute. I then want the 2nd instance to send the 1st instance a message, telling it to open a new window and passing the URL to open as a string, before closing itself (leaving the user with only 1 instance still and none the wiser).
I didn't think this was possible using PostMessage etc. (thought you could only use the specific constants as messages)
For this, it seems the easiest thing to do is have your app check to see if it is already running, and it so, then not allow a second instance to be executed.Quote:
Originally Posted by penagate
Yes, you are right about that and now that I know the specifics of your request, those APIs I mentioned would not do you any good.Quote:
Originally Posted by penagate
Yeah, I already do that, but the problem is I need the 1st instance to open the URL passed in the 2nd instance's command arguments. Otherwise the user will be getting frustrated when their URLs don't open :DQuote:
Originally Posted by Hack
Thanks, that looks interesting. But my problem is, I want to send a string message... not a constant representing a specific string.Quote:
Originally Posted by Merrion
As it is a URL there is no way I can register it beforehand, it needs to be sent as is.
Actually you don't need to register a message. You don't even have to send the string. Since what you want to do is to notify an earlier instance of your program of a file to open (or in this case an URL). All you then have to do is to send a unique user defined message to the previous instance window. Of course you could use RegisterWindowMessage to get such a unique message but you could just as well pick any value above WM_USER that will be unique for your program.
You also need to know the hWnd of the main window of the first started application so you can send a message to that. You could use FindWindow if you want but an easier way is that when the application starts, check if a previous instance exists. If not save the hWnd of your main window to the registry or anyother repository you want to use. If a previous instance of your application exists read the hWnd of that instance main window and send it your unique message using SendMessage.
Now how will the program get the URL to open when it gets the message? Well. again you could use the Registry to write the URL to use before sending the message. Another approach could be to create a Global Atom using the GlobalAddAtom message. The return value for that call should be sent to your previous instance, in for example the wParam argument. You can then use this value with the GlobalGetAtomName to get the URL, just make sure you call GlobalDeleteAtom when you have the URL.
Now for all of this to work you must of course subclass your main window and listen for your own unique message.
Thanks for that mate. I'll have a look at making atoms.
and, more subclassing... :sick:
Can you show me, or point me to a link where I can find out, how to listen for the message? I already have some subclassing in my app but the technique hasn't really stuck.... it was a copy & paste job :D
Add the above in a Module. You can start the subclassing by calling the HookForm Sub and pass the hWnd of the Form. You stop subclassing by calling the UnhookForm. You check for the messages in the WinProc function, I've already put in code to check for the WM_DESTROY message which your form gets before it is unloaded. I then stop the subclassing. This is for safety reasons in the case you would forget to call the UnhookForm before your window is closed.VB Code:
Private Declare Function SetWindowLong _ Lib "user32" Alias "SetWindowLongA" ( _ ByVal hwnd As Long, _ ByVal nIndex As Long, _ ByVal dwNewLong As Long) As Long Private Declare Function CallWindowProc _ Lib "user32" Alias "CallWindowProcA" ( _ ByVal lpPrevWndFunc As Long, _ ByVal hwnd As Long, _ ByVal msg As Long, _ ByVal wParam As Long, _ ByVal lparam As Long) As Long Private Const GWL_WNDPROC = (-4) Private Const WM_DESTROY = &H2& Private lngHwnd As Long Private lngPrevWndProc As Long Public Sub HookForm(frmHandle As Long) lngHwnd = frmHandle lngPrevWndProc = SetWindowLong(lngHwnd, GWL_WNDPROC, AddressOf WinProc) End Sub Public Sub UnhookForm() SetWindowLong lngHwnd, GWL_WNDPROC, lngPrevWndProc End Sub Public Function WinProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lparam As Long) As Long Select Case uMsg Case WM_DESTROY 'the window has been closed UnhookForm End Select WinProc = CallWindowProc(lngPrevWndProc, hw, uMsg, wParam, lparam) End Function
Remember when you subclass a Form not to use the End statement or clicking the End button in VB since it will then crash and burn.
I would use SendMessage to send a WM_COPYDATA message betwen apps. This message uses the COPYDATASTRUCT structure.
whereVB Code:
Type COPYDATASTRUCT dwData as long cbData as long lpData as long End Type
dwData is a long you may want to pass
ldData is a pointer to your string
cdData is the length or your string
That's all fine moeur... Have you tried that between two VB processes? I'm most intrested to learn how you've created a VB string and passed the address of it in the ldData member.Quote:
Originally Posted by moeur
Don't think that I'm trying to ridicule you or your answer because I don't! I just want to know how you passed on the string because I've tried that before but never succeeded.
Create two Apps
App 1 Sender
FormVB Code:
Option Explicit Private Type COPYDATASTRUCT dwData As Long cbData As Long lpData As Long End Type Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _ ByVal hwnd As Long, _ ByVal wMsg As Long, _ ByVal wParam As Long, _ lParam As Any _ ) As Long Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _ ByVal lpClassName As String, _ ByVal lpWindowName As String _ ) As Long Private Const WM_COPYDATA = &H4A Private Sub cmdSend_Click() Dim hWndREC As Long Dim MyData As COPYDATASTRUCT Dim strToSend As String 'get handle of receiver hWndREC = FindWindow(vbNullString, "Receiver") While hWndREC = 0 If MsgBox("Start Receiver Program", vbOKCancel) = vbCancel Then Exit Sub hWndREC = FindWindow(vbNullString, "Receiver") Wend 'string we want to pass strToSend = "Aussies love ABBA" MyData.dwData = 100 'any number you want MyData.lpData = StrPtr(strToSend) MyData.cbData = Len(strToSend) * 2 'wide data SendMessage hWndREC, WM_COPYDATA, Me.hwnd, MyData End Sub
App 2 Receiver
Form
ModuleVB Code:
Option Explicit Private Sub Form_Load() EnableSubclass Me.hwnd Label1.Caption = "Message goes here." Me.Caption = "Receiver" End Sub Private Sub Form_Unload(Cancel As Integer) DisableSubclass Me.hwnd End SubVB Code:
Option Explicit Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" ( _ ByVal lpPrevWndFunc As Long, _ ByVal hwnd As Long, _ ByVal Msg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long _ ) As Long Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _ Destination As Any, _ Source As Any, _ ByVal Length As Long _ ) Private Declare Function SetWindowLongPtr Lib "user32" Alias "SetWindowLongA" ( _ ByVal hwnd As Long, _ ByVal nIndex As Long, _ ByVal dwNewLong As Long _ ) As Long Private Const GWLP_WNDPROC = (-4) Private Const WM_COPYDATA = &H4A Dim lOrigWndProc As Long Public Function NewWndProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Dim cbData As Long Dim dwData As Long Dim lpData As Long Dim bTemp As Byte Dim i As Long Dim x As String If uMsg = WM_COPYDATA Then 'get dwData CopyMemory dwData, ByVal lParam, 4 'get len of string CopyMemory cbData, ByVal (lParam + 4), 4 'get pointer to string CopyMemory lpData, ByVal (lParam + 8), 4 'get string itself x = "" For i = 0& To cbData - 1 Step 2 CopyMemory bTemp, ByVal lpData + i, 1 x = x & Chr(bTemp) Next i Form1.Label1 = x End If NewWndProc = CallWindowProc(lOrigWndProc, hwnd, uMsg, wParam, lParam) End Function Public Function EnableSubclass(lhWnd As Long) As Boolean If lOrigWndProc <> 0 Then MsgBox "Error: Already subclassed" Else lOrigWndProc = SetWindowLongPtr(lhWnd, GWLP_WNDPROC, AddressOf NewWndProc) End If If lOrigWndProc <> 0 Then EnableSubclass = True Else EnableSubclass = False End If End Function Public Function DisableSubclass(lhWnd As Long) As Boolean If lOrigWndProc = 0 Then DisableSubclass = False Else SetWindowLongPtr lhWnd, GWLP_WNDPROC, lOrigWndProc lOrigWndProc = 0 DisableSubclass = True End If End Function
There is a limitation with WM_COPYDATA, the data pointer can point to anything including a data structure, but the data structure cannot contain any pointers itself.
Cool thanks!
Thanks, nice looking code moeur, I'll try that!
I'm really sorry for replying this 4 years old thread. I was just reading around forum and got interested on this one..
In my beginner's mind, why this task is so hard to do? why must use subclassing (which is difficult for me)?
with SendMessageByString i can send string between my 2 apps:
App 1 Sender
App 2 Receiver:Code:Option Explicit
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Private Const WM_SETTEXT = &HC
Private Sub cmdSend_Click()
Dim thunderrtformdc As Long, thunderrttextbox As Long
thunderrtformdc = FindWindow("thunderrt6formdc", "Receiver")
If thunderrtformdc <> 0 Then
thunderrttextbox = FindWindowEx(thunderrtformdc, thunderrttextbox, "thunderrt6textbox", vbNullString)
Call SendMessageByString(thunderrttextbox, WM_SETTEXT, 0&, "Text sent by sender")
End If
End Sub
only 1 textbox
thank you for explanation..
and sorry for bringing up this old thread..
What you are doing here is sending a Set Text message to another window (form). This will change the caption of the form to your new message and you can then retrieve the message by reading the caption property.
Let's say that you don't want your string showing up on your form, then you can't use the Set Text windows message.
In addition, suppose you want to send something more complicated than a string such as a user defined type. Now the job becomes more complicated.