Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[module, ahk & dotNet] IPC 2.6 


  • Please log in to reply
59 replies to this topic
majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
I don't think thats all that important, but it should return true.
Posted Image

Mystiq
  • Members
  • 83 posts
  • Last active: Nov 06 2011 07:07 PM
  • Joined: 08 Jan 2007

I don't think thats all that important, but it should return true.


Yea it is really not that important but i thought i'd mention it.

I've been tinkering with the two example scripts and trying to run these in 64-bit but i'm having some pretty weird results.

Here are the changes i've made.

IPC.ahk
	if (DataSize = "")
		 DataSize := (StrLen(Data)+1) * (!!A_IsUnicode + 1), pData := &Data, Port := -Port			;use negative port for textual messages
	else pData := Data         
	
	VarSetCapacity(COPYDATA, 8+A_PtrSize)
	 , NumPut(Port,		COPYDATA, 0, "Int")
	 , NumPut(DataSize, COPYDATA, 4, "UInt")
	 , NumPut(pData,	COPYDATA, 8, "Ptr")

Script1.ahk / Script2.ahk
	port := NumGet(Lparam+0, 0, "Int"), data := NumGet(Lparam+8, 0, "Ptr")
	if port < 0
		 data := DllCall("MulDiv", "Int", data, "Int",1, "Int", 1, "str"), port := -port
	else size := NumGet(LParam+4, 0, "UInt")

In 32-bit builds these changes work without problem but in 64-bit the "IPC_onCopyData" appears to lock, OnMessage handler function no longer "fires", randomly. Usually this happens after second sent message or right from the start, either in both scripts or only in one etc. Also it's sluggish in processing the message.

Two things that seem to fix this. One is to use the original COPYDATA structure or remove/comment any NumGet/StrGet from the OnMessage called function (IPC_onCopyData). Neither seems particularly good solution. Note that once the script hits the NumGet the first time, nothing after that gets processed and from there on it "locks" i.e. OnMessage handler no longer is working.

Also i've only tested the 64-bit version in compiled form only. Using Autohotkey 1.0.91.05, Windows 7 (x64).

Not sure if my COPYDATA structure is wrong or something, i'm assuming it has to be at least bigger in 64-bit than in 32-bit at least for the data pointer?

Any help/comments/etc. are appreciated!

gamax92
  • Members
  • 411 posts
  • Last active: Aug 06 2013 05:00 AM
  • Joined: 05 Dec 2010
This is awesome for multimonitored computers. i know a school that has all there computers linked up to make multi screens. they just select a monitor and send the window to it.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Mistiq, I can't reproduce your problem, altho I tried only briefly. When I find time I will check out more.

Since Lexikos fixed the module, I still didn't use it in 64x environment apart from basic working tests.
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Note that the dwData field is ULONG_PTR, i.e. 64-bit. Since the struct contains 64-bit fields, it is aligned to 64-bit boundaries by default; i.e. there are 32 bits of padding between cbData and lpData.
VarSetCapacity(COPYDATA, [color=darkred]3*A_PtrSize[/color])
    , NumPut(Port,      COPYDATA, 0, "Int")
    , NumPut(DataSize, COPYDATA, [color=darkred]A_PtrSize[/color], "UInt")
    , NumPut(pData,   COPYDATA, [color=darkred]2*A_PtrSize[/color], "Ptr")
port := NumGet(Lparam+0, 0, "Int"), data := NumGet(Lparam+[color=darkred]2*A_PtrSize[/color], 0, "Ptr")
   if port < 0
       data := DllCall("MulDiv", "Int", data, "Int",1, "Int", 1, "str"), port := -port
   else size := NumGet(LParam+[color=darkred]A_PtrSize[/color], 0, "UInt")
Untested.

Mystiq
  • Members
  • 83 posts
  • Last active: Nov 06 2011 07:07 PM
  • Joined: 08 Jan 2007

Note that the dwData field is ULONG_PTR, i.e. 64-bit. Since the struct contains 64-bit fields, it is aligned to 64-bit boundaries by default; i.e. there are 32 bits of padding between cbData and lpData.


Aaah, well that explains it. I got the impression that the size might be 64-bit for the two and 32-bit for the cbData one, which is actually wrong in the example i gave. Just goes to show how much i really know about these things, which is not much! :)

I tested the code and it works perfectly, no problems at all. Thanks to both of you!

Mystiq
  • Members
  • 83 posts
  • Last active: Nov 06 2011 07:07 PM
  • Joined: 08 Jan 2007
Now that this works in 64-bit i'd like to share my changes, which also includes the various fixes from previous posters.

Most notable thing i've changed is that it no longer uses window handles, instead i'm using process name or id. Not sure how good idea this is but it appears to work just fine and no longer deals with GUI(s) or window handles. Also this one does handle the SendMessage more strictly, as i think it might be better to know whether the target process actually processed the message or not. For example if you are accidentally sending to a wrong process/window, one that is not handling the WM_COPYDATA message, then there was no feedback if the message was really received.

Another one is that instead of passing the window handle as the SendMessage wParam, i'm using the process id. According to WM_COPYDATA it is suppose to be the window handle, but i've not seen any side effects of using the process id instead. Not sure if this such a good idea(?)

Also IPC_Send returns empty/false on success and error message string on failure.

I've tested mostly between two AHK programs and with the DotNet examples. However the DotNet ones do not work perfectly as it does not appear to use Unicode(??), at least the message is cut off after the first letter. Also since this version handles the return value of the SendMessage more strictly, AHK script will say that the send failed, when in fact the DotNet program did receive and display the message (well, part of it...).

I've update both the example script(s) and IPC.ahk.

IPC.ahk
/*
 Function:	 Send
			 Send the message to another process (receiver).

 Parameters:
			 PidOrName	- Process name or ID
			 Data		- Data to be sent, by default empty. Optional.
			 Port		- Port, by default 100. Positive integer. Optional.
			 DataSize 	- If this parameter is used, Data contains pointer to the buffer holding binary data.
						Omit this parameter to send textual messages to the receiver.					 

 Remarks:
			The data being passed must not contain pointers or other references to objects not accessible to the script receiving the data. 
			While this message is being sent, the referenced data must not be changed by another thread of the sending process. 
			The receiving script should consider the data read-only. The receiving script should not free the memory referenced by Data parameter.
			If the receiving script must access the data after function returns, it must copy the data into a local buffer.

 Returns:
			Returns EMPTY / FALSE on success. Error message on failure.
 */
IPC_Send(PidOrName, Data="", Port=100, DataSize="") {
	static WM_COPYDATA = 74, INT_MAX=2147483647
	if Port not between 0 AND %INT_MAX%
		return A_ThisFunc "> Port number is not in a positive integer range: " Port
	
	Process, Exist, %PidOrName%
	if (!PidOrName || !(PID := ErrorLevel))
		return A_ThisFunc "> Process not found: " PidOrName 
	
	if (DataSize = "")
	   DataSize := (StrLen(Data)+1) * (!!A_IsUnicode + 1), pData := &Data, Port := -Port         ;use negative port for textual messages
	else pData := Data

   VarSetCapacity(COPYDATA, 3*A_PtrSize)
    , NumPut(Port,      COPYDATA, 0, "Int")
    , NumPut(DataSize, COPYDATA, A_PtrSize, "UInt")
    , NumPut(pData,   COPYDATA, 2*A_PtrSize, "Ptr")
		
	PrevDetectHiddenWindows := A_DetectHiddenWindows
	DetectHiddenWindows, On
	SendMessage, WM_COPYDATA, DllCall("GetCurrentProcessId"), ©DATA,, ahk_pid %PID%
	DetectHiddenWindows, %PrevDetectHiddenWindows%
	
	return ErrorLevel="FAIL" || !ErrorLevel ? A_ThisFunc "> SendMessage failed, ErrorLevel=" ErrorLevel : false
}

/*
  Function:	 SetHandler
 			 Set the data handler.
 
  Parameters:
 			 Handler - Function that will be called when data is received. 					 
 
  Handler:
  >			 Handler(PID, Data, Port, DataSize)

			 PID	- Process ID of the process passing data.
			 Data	- Data that is received.
			 Port	- Data port.
			 DataSize - If DataSize is not empty, Data is pointer to the actuall data. Otherwise Data is textual message.
 */
IPC_SetHandler( Handler ){
	static WM_COPYDATA = 74

	if !IsFunc( Handler )
		return A_ThisFunc "> Invalid handler: " Handler
	
	OnMessage(WM_COPYDATA, "IPC_onCopyData")
	IPC_onCopyData(Handler, "")
}


IPC_onCopyData(WParam, LParam) {
	static Handler
	if Lparam =
		return  Handler := WParam
		
	port := NumGet(Lparam+0, 0, "Int"), data := NumGet(Lparam+2*A_PtrSize, 0, "Ptr")
	if port < 0
	   data := DllCall("MulDiv", "Int", data, "Int",1, "Int", 1, "str"), port := -port
	else size := NumGet(LParam+A_PtrSize, 0, "UInt")

	%handler%(WParam, data, port, size)
	return 1
}

Script1.ahk / Script2.ahk

Remember to change the "target" variable!

#SingleInstance, off	;allow multiple instances

	target := "Script2"
	;target := "Script1"
	stress := 1000,   x:=300
	;========================

	Gui, +LastFound	  +AlwaysOnTop
	CPID := DllCall("GetCurrentProcessId")
	
	Gui, Font, s10
	Gui, Add, Edit,		 vMyMsg  w200 , Message to %target%
	Gui, Add, Edit,  x+0 vMyPort w50, 100

	Gui, Font, s8
	Gui, Add, Button, x+5	gOnSend		  , Send
	Gui, Add, Button, x+5	gOnSendBinary , Send Binary
	Gui, Add, Button, x+5	gOnStress	  , Stress

	Gui, Add, ListBox,xm w440 h300 vMyLB,

	Gui, Show, x%x%	AutoSize
	GuiControl, , MyLB, This script PID: %CPID%
	IPC_SetHandler("OnData")
return

OnData(PID, Data, Port, Size) {
	global myLB

	if Size =
		 s = %Port%      PID: %PID%      Message: %Data%
	else {		  
		  x := NumGet(Data+0), y := NumGet(Data+4)
		  s = %Port%     PID: %PID%      Binary Data: POINT (%x%, %y%)      DataSize: %Size%
	}	
	GuiControl, , MyLB, %s%
}


OnSend:
	Gui, Submit, NoHide
	WinGet, TPID, PID, %target%
	if IPC_Send(TPID, MyMsg, MyPort)
		MsgBox Sending failed
return

OnStress:
	Gui, Submit, NoHide
	WinGet, TPID, PID, %target%
	if !(TPID)
		MsgBox Host process doesn't exist
	loop, %stress%
	   IPC_Send(TPID, MyMsg " : " A_Index, MyPort)
return

OnSendBinary:
	Gui, Submit, NoHide
	VarSetCapacity(POINT, 8), NumPut(2000, POINT), NumPut(8000, POINT, 4)
	WinGet, TPID, PID, %target%
	if IPC_Send(TPID, &POINT, MyPort, 8)
		MsgBox Sending failed
return

GuiClose:
	ExitApp
return

#include ..\IPC.ahk

Again, feedback is much appreciated!

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006

target := "Script2.exe"

How can this work since there is no "Script2.exe" but Ahk.exe with Script2.ahk as parameter ? (unless u compiled scripts).

About process id, its probably good idea since script doesn't need to have Gui.

About C# include, its not unicode:
ea.Text = Marshal.PtrToStringAnsi(CD.lpData, CD.cbData);

Posted Image

Mystiq
  • Members
  • 83 posts
  • Last active: Nov 06 2011 07:07 PM
  • Joined: 08 Jan 2007

target := "Script2.exe"

How can this work since there is no "Script2.exe" but Ahk.exe with Script2.ahk as parameter ? (unless u compiled scripts).


Yea, it's meant to be compiled. I might have needlessly done it that way since you can just get the process id from window title as it was in the original example scripts.

I updated the example script so that it now works like the original ones. No compiling needed. :)

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
I am thinking to support only Unicode string from now on. What do you think ?
That means that AHK Basic will no longer be supported but user can download old version if they need it. Alternatively, AHK Basic can be recognized and its ANSI strings could be converted to Unicode first. If that is not done, C# app can not know to what kind of AHK it sends data so we need to have another property or two in IPC class and/or methods - those that will send/receive text as Unicode.
Posted Image

Mystiq
  • Members
  • 83 posts
  • Last active: Nov 06 2011 07:07 PM
  • Joined: 08 Jan 2007

I am thinking to support only Unicode string from now on. What do you think ?


I would go for Unicode only simply because that's what I'm going to be using anyways, unless forced otherwise (very unlikely). If you know how to do the ANSI conversion thing properly without much effort then maybe?

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Its not a problem how to convert, but I wouldn't like to impose extra complexity because of outdated format.
Posted Image

Mystiq
  • Members
  • 83 posts
  • Last active: Nov 06 2011 07:07 PM
  • Joined: 08 Jan 2007
If there is no huge demand for the ANSI thing then i wouldn't bother with it.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
This script hasn't been updated to support Unicode yet, has it?

I am thinking to support only Unicode string from now on. What do you think ?

I think it's the best way to go, even for ANSI builds. However, you don't necessarily need to drop AutoHotkey Basic support. You can use StrPut/StrGet (or borrow code from that script).

magusneo
  • Members
  • 51 posts
  • Last active: May 10 2014 03:15 PM
  • Joined: 30 May 2012

SendMessage, WM_COPYDATA, DllCall("GetCurrentProcessId"), ©DATA,, ahk_pid %PID%

 
 
what is the "©DATA"?