Page 1 of 1

WM_COPYDATA: Sending complex data between apps

Posted: Sat Nov 24, 2007 8:27 pm
by netmaestro
There are a few threads dealing with sending strings between apps, and some of them allude to WM_COPYDATA as an alternative, but I couldn't find a good example that shows how to use it successfully. (could be I missed it) Anyhow, I thought I'd post one.

WM_COPYDATA is a message you can send from your application or dll which allows any kind of data to be sent to the application owning the target of the message. You don't even need to have a window open in the sender if you want. In this case, just set wParam to #Null in the message. If the receiver doesn't have a window, you'll have to open a hidden one to receive the message. It isn't a good idea to send this message to HWND_BROADCAST as several mainstream apps process it (I found that out the hard way.) One important feature is that in the receiver you have to copy the data to local variables when you process the message, because the pointer isn't valid later. Here's a pair of sample programs, you can compile them or run them from the IDE, it doesn't matter:

Receiver (Run this first and let it wait for the sender)

Code: Select all

;==================================================================
; Program:         WM_COPYDATA demo - Receiver program
;==================================================================

Enumeration
  #Double_Message ; user-defined identifier that a double is being sent
  #String_Message ;     "             "       "    string      "    "
  #Dog_Message    ;     "             "       "     dog        "    "
EndEnumeration

Structure DOG 
  name.s{10}
  age.l
  weight.l
  breed.s{20}
EndStructure

Procedure.l Callback(hwnd, msg, wparam, lparam)
  result = #PB_ProcessPureBasicEvents
  Select msg
    Case #WM_COPYDATA
      *pp.COPYDATASTRUCT = lparam
      Select *pp\dwData ; determine the type of data being sent and process accordingly
        Case #String_Message
          AddGadgetItem(0,-1, "Got a string: "+PeekS(*pp\lpData) )
        Case #Double_Message
          double.d = PeekD(*pp\lpData)  
          AddGadgetItem(0,-1, "Got a double: "+StrD(double) )
        Case #Dog_Message
          *dog.DOG = *pp\lpData
          With *dog
            name$  = \name 
            age    = \age  
            weight = \weight 
            breed$ = \breed
            s$ = "Got a dog: "+name$+", "+breed$+", "+Str(age)+" years old, "+Str(weight)+" kg."
            AddGadgetItem(0,-1, s$ )
          EndWith
      EndSelect
      result = #True ; You should return #True if processing #WM_COPYDATA
  EndSelect
  ProcedureReturn result
EndProcedure

OpenWindow(0,0,0,320,240,"WM_COPYDATA Receiver", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
CreateGadgetList(WindowID(0))
ListViewGadget(0,0,0,320,240)

SetWindowCallback(@Callback(),0)
StickyWindow(0,1)

Repeat
  ev = WaitWindowEvent()
Until ev = #WM_CLOSE
Sender: Run once the receiver is up and waiting

Code: Select all

;==================================================================
; Program:             WM_COPYDATA demo - Sender program
; Author:              Lloyd Gallant (netmaestro)
; Date:                November 24, 2007
; Target Compiler:     PureBasic 4.xx and later
; Target OS:           Microsoft Windows All
; License:             Free, unrestricted, credit appreciated 
;                      but not required
;==================================================================

Enumeration 
  #Double_Message
  #String_Message
  #Dog_Message
EndEnumeration
 
window = FindWindow_(0, "WM_COPYDATA Receiver")

Structure DOG ; complex structure for data to be sent
  name.s{10}
  age.l
  weight.l
  breed.s{20}
EndStructure

; ****************** Prepare a string to be sent ********************

string2send.s = "Testing 123"

*stringdata.COPYDATASTRUCT = AllocateMemory(SizeOf(COPYDATASTRUCT))
With *stringdata
  \dwData = #String_Message
  \cbData = Len(string2send)+SizeOf(CHARACTER)
  \lpData = @string2send
EndWith    

; ******************* Prepare a double to be sent *******************

double2send.d = #PI

*doubledata.COPYDATASTRUCT = AllocateMemory(SizeOf(COPYDATASTRUCT))
With *doubledata
  \dwData = #Double_Message
  \cbData = SizeOf(DOUBLE)
  \lpData = @double2send
EndWith    

; ************* Prepare a complex structure to be sent **************

dog2send.DOG
With dog2send
  \name   = "Bowser"
  \age    = 3
  \weight = 20
  \breed  = "German Shepherd"
EndWith

*dogdata.COPYDATASTRUCT = AllocateMemory(SizeOf(COPYDATASTRUCT))
With *dogdata
  \dwData = #Dog_Message
  \cbData = SizeOf(DOG) 
  \lpData = dog2send
EndWith    

; *******************************************************************

SendMessage_(window, #WM_COPYDATA, #Null, *stringdata)
SendMessage_(window, #WM_COPYDATA, #Null, *doubledata)
SendMessage_(window, #WM_COPYDATA, #Null, *dogdata)

Posted: Sat Nov 24, 2007 8:41 pm
by Kwai chang caine
Cool, it works fine :D
I have three results return : :D

Code: Select all

Got a string : Testing 123
Got a double : 3.1415926536
Got a dog: Bowser, German Shepherd, 3 years old, 20 kg.
Xp Sp2, Pb v4.10 beta

Posted: Sat Nov 24, 2007 9:22 pm
by srod
Very nice. Haven't come across that message before! :)

Thanks.

Posted: Sat Nov 24, 2007 10:11 pm
by netmaestro
There seems to be no practical limit to the size of data you can send this way, I just tested with a string 10 million chars in length and it successfully transferred the whole thing.

Posted: Sat Nov 24, 2007 10:21 pm
by srod
Looking at the api docs for this message, it is probably worth noting that in a multi-threaded application, steps must be taken to ensure that secondary threads do not attempt to modify the data being sent. A mutex would of course suffice.

I wonder if the data is physically copied or the underlying memory is temporarily mapped into the receiving processes virtual memory? Interesting.

Posted: Sat Nov 24, 2007 10:26 pm
by netmaestro
I wonder if the data is physically copied or the underlying memory is temporarily mapped into the receiving processes virtual memory?
Given the fact that you have to copy the data to local variables upon processing the message, because the pointer in lParam becomes no longer valid, I'd guess the latter. Also MSDN cautions against trying to free the memory pointed to, which would imply the same thing.

Posted: Sat Nov 24, 2007 10:49 pm
by srod
Yes, my thoughts exactly. :)

A very nice way of sharing data that's for sure.

Re: WM_COPYDATA: Sending complex data between apps

Posted: Fri Aug 05, 2011 2:14 am
by IdeasVacuum
If sender and receiver are compiled as unicode, the received data includes garbage.

Re: WM_COPYDATA: Sending complex data between apps

Posted: Fri Aug 05, 2011 11:21 pm
by X
Nice piece of code!

Good point about the unicode.

The other question is, have anyone tested the speed of the transfer between programs?

Re: WM_COPYDATA: Sending complex data between apps

Posted: Fri Aug 05, 2011 11:31 pm
by netmaestro
Afaik it's 100km/h until it gets close then the speed limit drops to 60 :mrgreen:

Seriously, it's typically not going to be more than will fit in free ram and so it's a straight memory copy. Blazing fast.

Re: WM_COPYDATA: Sending complex data between apps

Posted: Fri Aug 05, 2011 11:57 pm
by X
This could be useful for shards and AI servers that live on the same machine.

Hmm. I'm thinking about eve technology, but that is a whole new interconnect problem ;)

Thanks for the code! I'll h have to poke and prod for that unicode issue.

Re: WM_COPYDATA: Sending complex data between apps

Posted: Sat Aug 06, 2011 12:12 am
by netmaestro
I ran this test today on my win7 x86 machine with PB 4.6b3 and I'm not getting any problems with unicode. Are you possibly using x64?

Re: WM_COPYDATA: Sending complex data between apps

Posted: Thu Aug 11, 2011 10:03 am
by happer66
Replace Len() with StringByteLength() in the sender code and it should be good to go.

Code: Select all

\cbData = StringByteLength(string2send)+SizeOf(CHARACTER)
Nice old gem btw netmaestro!