dpaynebrown Posted October 30, 2007 Posted October 30, 2007 (edited) This is, apparently, a no-no ... I have two compiled AutoIt executables that work together. The first is a library ("Functions.exe") of functions that loads into memory and processes commands sent to it from the second, caller, executable ("DoFunction.exe"). The job of the library is to sit in memory and wait for something to do, then do it, and return the results to the caller. The job of the caller is to pass a command-line string to the library telling it which function to run and which parameters to use. Then it waits for a return string containing the results from the function. So far so good. Both the library and caller are communicating back-and-forth as expected. Very efficient, very fast, and very reliable. The communications method used is built on the DLL structure / WM_COPYDATA / SendMessage API stuff found in several different posts. And damn! ... the stuff works like magic. My users can happily type string, math, regular expression, and such commands from a program that launches the caller and receives back answers, until the cows come home. But ... here's the no-no: I created a function in the library to simply launch a new instance of Excel. This is it: Func XL_LaunchNewWorkBook() $objXL = ObjCreate( "Excel.Application" ) With $objXL .Visible = 1 .WorkBooks.Add .ActiveWorkbook.Sheets(1).Select() EndWith Return "" EndFunc The command line to run the function is "DoFunction XL_LaunchNewWorkbook". That's it. The workbook never gets created. Using the _ComErrFunc found in another post, I am getting the following error: 8001010D - "An outgoing call cannot be made since the application is dispatching an input-synchronous call" Googling this message results in http://support.microsoft.com/kb/190523 So, I reviewed the error message and the KB article and have determined that they are both speaking in tongues. Is there someone here that would care to interpret, and tell me what I need to do to fix this little no-no? Edited October 31, 2007 by dpaynebrown
evilertoaster Posted October 30, 2007 Posted October 30, 2007 (edited) The function you provided works fine for me. It would be helpfull to have all the code required to reporduce the problem (but no more than required). The MSDN article tries to put a delay on one of the funcitons so doing somthing as simple as Func XL_LaunchNewWorkBook() $objXL = ObjCreate( "Excel.Application" ) Sleep(50) With $objXL .Visible = 1 .WorkBooks.Add .ActiveWorkbook.Sheets(1).Select() EndWith Return "" EndFunc might fix it... but it's hard to tell without an example. Edited October 30, 2007 by evilertoaster
dpaynebrown Posted October 30, 2007 Author Posted October 30, 2007 (edited) The function you provided works fine for me. It would be helpfull to have all the code required to reporduce the problem (but no more than required).Yes, a delay is something I did try, however, it doesn't make any difference because the code never gets past the ObjCreate command line, where the error is thrown.And as you are aware, it's not that the code in-and-of-itself doesn't work, it does ... but not in the environment that it runs: there is an executable passing data to another executable via WM_COPYDATA, that in turn, is attempting to create an Excel object. It is here that the Input-Synchronous error occurs. Edited October 30, 2007 by dpaynebrown
dpaynebrown Posted October 30, 2007 Author Posted October 30, 2007 Here is a little more information from http://support.microsoft.com/kb/131056/EN-US/OLE determines if the caller of the synchronous call is a recipient of an input-synchronized call by using the InSendMessage() API. This broad check prevents a synchronous call from being made if the caller is currently a recipient of any inter-process/inter-thread SendMessage.Emphasis mine.Now, it is true that SendMessage is used to communicate WM_COPYDATA between the two executables, so they are both "recipients" of SendMessage.Because I'm using SendMessage to fire a function within an executable library, that same executable can't be used to fire-off an Excel or Word object? Am I reading and understanding this correctly? Anyone have any experience or solutions to this problem?
Uten Posted October 30, 2007 Posted October 30, 2007 Could you use PostMessage instead? Rewrite your code to do something like this? exe1: PostMessage <DoWork> exe2: Does the work exe2: Postmessage to Exe1 <WorkDone> exe1: SendMessage <GetFinishedWork> Blocking SendMessage with lengthy processing is not nice to do as it can result in a corrupted message queue (if memory serves me right).. Please keep your sig. small! Use the help file. Search the forum. Then ask unresolved questions :) Script plugin demo, Simple Trace udf, TrayMenuEx udf, IOChatter demo, freebasic multithreaded dll sample, PostMessage, Aspell, Code profiling
dpaynebrown Posted October 30, 2007 Author Posted October 30, 2007 (edited) Could you use PostMessage instead?Thanks Uten. I've been looking at the problem, trying to figure it out. No luck so far, which means PostMessage is the next step.Blocking SendMessage with lengthy processing is not nice to do as it can result in a corrupted message queue (if memory serves me right)..Yes, but that brings up a good point: I thought that once SendMessage sends a message, and the receiver receives the message, that the loop is completed. In other words, the receiver automatically signals the sender that it got the message. In my case the receiver is simply parsing the string from the message it gets and then destroys the DLL structure by setting to 0. By then, wouldn't the SendMessage/Acknowledge loop have been completed? Or do I have it all wrong? Edited October 30, 2007 by dpaynebrown
martin Posted October 30, 2007 Posted October 30, 2007 (edited) Thanks Uten. I've been looking at the problem, trying to figure it out. No luck so far, which means PostMessage is the next step.Yes, but that brings up a good point: I thought that once SendMessage sends a message, and the receiver receives the message, that the loop is completed. In other words, the receiver automatically signals the sender that it got the message. In my case the receiver is simply parsing the string from the message it gets and then destroys the DLL structure by setting to 0. By then, wouldn't the SendMessage/Acknowledge loop have been completed? Or do I have it all wrong?It depends on how your function works for receiving the message. Say it's something like thisFunc getdataMessage();lots of stuffEndFuncThen the reply to the messsage isn't sent until you get to Endfunc. If this is the problem then you need to process the message as fast as possible then deal with the request afterwards. Say if the message is to start Excel then rather than start Excel just set a variable $nextjob = "Start Excel". Then deal with the next job in another function, like an ADlib function which checks if there's work to do. The Post message method might also work because the sender doesn't wait for an answer, but equally it doesn't know it's been received. Edited October 30, 2007 by martin Serial port communications UDF Includes functions for binary transmission and reception.printing UDF Useful for graphs, forms, labels, reports etc.Add User Call Tips to SciTE for functions in UDFs not included with AutoIt and for your own scripts.Functions with parameters in OnEvent mode and for Hot Keys One function replaces GuiSetOnEvent, GuiCtrlSetOnEvent and HotKeySet.UDF IsConnected2 for notification of status of connected state of many urls or IPs, without slowing the script.
dpaynebrown Posted October 31, 2007 Author Posted October 31, 2007 It depends on how your function works for receiving the message. Say it's something like this Func getdataMessage() ;lots of stuff EndFunc Then the reply to the messsage isn't sent until you get to Endfunc. If this is the problem then you need to process the message as fast as possible then deal with the request afterwards. Say if the message is to start Excel then rather than start Excel just set a variable $nextjob = "Start Excel". Then deal with the next job in another function, like an ADlib function which checks if there's work to do. The Post message method might also work because the sender doesn't wait for an answer, but equally it doesn't know it's been received. Well how about that? I don't need either app to wait for the other. I just need to get a data string back and forth and let the apps hammer away at them at their leisure. Here's my sender app. It has both a send and receive function, much the same as the receiver app. After reading your post Martin, it is possible that I could simply use PostMessage inside the DllCall command below, rather than SendMessage? Could the answer really be that simple? expandcollapse popupIf $CmdLine[0] = 0 Then Exit Opt( "MustDeclareVars", 1 ) ;- Always a good thing to do Opt( "GUICloseOnESC", 1 ) ;- ESC closes the window Opt( "TrayIconHide", 0 ) ;- Show tray icon Dim $h, $i, $j, $k, $w, $x, $y, $z Const $WM_COPYDATA = 0x4A Const $WM_CLOSE = 0x10 ;==================================================================================================================== ;- Activate the GUI window and then keep it live and loaded. Must be done before sending message ;- Register the message handler. Must be done after creating GUI window and before sending message ;- Send the message to the Library. Must be done before the GUIGetMsg loop ;- Loop until a message is received back from the Library ;-------------------------------------------------------------------------------------------------------------------- GUICreate( "DoFunction", 180, 25, 10, 67 ) GUISetState( @SW_SHOW ) GUIRegisterMsg( $WM_COPYDATA, "ReceiveFromLibrary" ) SendToLibrary( $CmdLineRaw ) While GUIGetMsg() <> $WM_CLOSE WEnd Func SendToLibrary( $pcString ) Local $lnStringLen, $objStringStruct, $ptrStringStruct, $objSendStruct, $ptrSendStruct $lnStringLen = StringLen( $pcString ) + 1 $objStringStruct = DllStructCreate( "char var1[1024]" ) DllStructSetData( $objStringStruct, 1, $pcString ) $ptrStringStruct = DllStructGetPtr( $objStringStruct ) $objSendStruct = DllStructCreate( "dword var1;dword var2;ptr var3" ) DllStructSetData( $objSendStruct, "var1", 0 ) DllStructSetData( $objSendStruct, "var2", $lnStringLen ) DllStructSetData( $objSendStruct, "var3", $ptrStringStruct ) $ptrSendStruct = DllStructGetPtr( $objSendStruct ) DllCall( "user32.dll", "long", "SendMessage", "hwnd", WinGetHandle( "Library" ), "int", $WM_COPYDATA, "int", 0, "int", $ptrSendStruct ) $objStringStruct = 0 ;- Free the structure $objSendStruct = 0 EndFunc Func ReceiveFromLibrary( $hWnd, $MsgID, $wParam, $lParam ) Local $objReceiveStruct, $objStringStruct If $MsgID = $WM_COPYDATA Then $objReceiveStruct = DllStructCreate( "dword var1;dword var2;ptr var3", $lParam ) $objStringStruct = DllStructCreate( "char var1[1024]", DllStructGetData( $objReceiveStruct, 3 ) ) ConsoleWrite( DllStructGetData( $objStringStruct, 1 ) ) $objStringStruct = 0 ;- Free the structure $objReceiveStruct = 0 Exit EndIf EndFunc
Uten Posted October 31, 2007 Posted October 31, 2007 Looks like your setup will be able to handle it. Depends on how ReceiveFromLibrary is used. Take a look in my signature for a postmessage sample. Regarding using Addlib ( or a timer, never understood why they called it Addlib:huh2: ). It is "better" to post a message to on self to get the asynchronous behavior after a SendMessage call. Please keep your sig. small! Use the help file. Search the forum. Then ask unresolved questions :) Script plugin demo, Simple Trace udf, TrayMenuEx udf, IOChatter demo, freebasic multithreaded dll sample, PostMessage, Aspell, Code profiling
dpaynebrown Posted October 31, 2007 Author Posted October 31, 2007 Looks like your setup will be able to handle it. Depends on how ReceiveFromLibrary is used. Take a look in my signature for a postmessage sample. It is "better" to post a message to on self to get the asynchronous behavior after a SendMessage call. Okay, here are the send and receive functions within the receiver app. If I understand you correctly, Uten, you are saying that I need to PostMessage something from this app ... to this app. In other words, PostMessage to itself and let it return to waiting for another message. And by using PostMessage as the last, er, message-sender, then further along I should be able to use ObjCreate. Do I place a call to the PostMessage function (your example, thanks) immeditealy after getting the string from the DLL structure? I'm more-than-likely missing the obvious here, but how do I then process the string I just extracted from SendMessage? expandcollapse popup#include <GUIConstants.au3> #include <Misc.au3> #include <Array.au3> #include <File.au3> #include <String.au3> _Singleton( @ScriptName ) ;- Allow only a single instance to run Opt( "MustDeclareVars", 1 ) ;- Always a good thing to do Opt( "GUICloseOnESC", 1 ) ;- ESC closes the window Opt( "TrayIconHide", 0 ) ;- Show tray icon Global $h, $i, $j, $k, $w, $x, $y, $z Global Const $WM_COPYDATA = 0x4A Global Const $WM_CLOSE = 0x10 Global $objXL ;- Excel object Global $oComError = ObjEvent( "AutoIt.Error", "_ComErrFunc" ) GUICreate( "ExtLibrary", 180, 25, 10, 10 ) GUISetState( @SW_SHOW ) GUIRegisterMsg( $WM_COPYDATA, "ReceiveFromCaller" ) GUIRegisterMsg( $WM_CLOSE, "ReceiveFromCaller" ) While GUIGetMsg() <> $GUI_EVENT_CLOSE WEnd Func ReceiveFromCaller( $hWnd, $MsgID, $wParam, $lParam ) If $MsgID = $WM_CLOSE Then Exit If $MsgID = $WM_COPYDATA Then Local $objReceiveStruct, $objStringStruct, $lcCmdLine, $laCmdLine, $lcReturnValue $objReceiveStruct = DllStructCreate( "dword var1;dword var2;ptr var3", $lParam ) $objStringStruct = DllStructCreate( "char var1[1024]", DllStructGetData( $objReceiveStruct, 3 ) ) $lcCmdLine = DllStructGetData( $objStringStruct, 1 ) $objReceiveStruct = 0 $objStringStruct = 0 $lcReturnValue = ParseCmdLineString( $lcCmdLine ) If IsString( $lcReturnValue ) Then SendBackToCaller( $lcReturnValue ) Else $laCmdLine = $lcReturnValue $lcReturnValue = Call( $laCmdLine[0], $laCmdLine[1] ) SendBackToCaller( $lcReturnValue ) EndIf EndIf EndFunc Func SendBackToCaller( $pcString ) Local $lnStringLen, $objStringStruct, $ptrStringStruct, $objSendStruct, $ptrSendStruct $lnStringLen = StringLen( $pcString ) + 1 $objStringStruct = DllStructCreate( "char var1[1024]" ) DllStructSetData( $objStringStruct, 1, $pcString ) $ptrStringStruct = DllStructGetPtr( $objStringStruct ) $objSendStruct = DllStructCreate( "dword var1;dword var2;ptr var3" ) DllStructSetData( $objSendStruct, "var1", 0 ) DllStructSetData( $objSendStruct, "var2", $lnStringLen ) DllStructSetData( $objSendStruct, "var3", $ptrStringStruct ) $ptrSendStruct = DllStructGetPtr( $objSendStruct ) ;- Create a pointer to this DLL structure _SendMessage( WinGetHandle( "DoFunction" ), $WM_COPYDATA, 0, $ptrSendStruct ) $objStringStruct = 0 $objSendStruct = 0 EndFunc
Uten Posted October 31, 2007 Posted October 31, 2007 ...., PostMessage to itself and let it return to waiting for another message. And by using PostMessage as the last, er, message-sender, then further along I should be able to use ObjCreate.Yes, that sounds about right. Store the data in the $WM_COPYDATA message. Then PostMessage ($ME_PROCESSDATA(You define this) ) to yourself. When your app receives $ME_PROCESSDATA then yuo take the previously stored data and process it. At this point you should be able to use4 ObjCreate(...) Take it a bit further and place the $WM_COPYDATA in a FIFO stack and you will be able to handle messages from multiple applications in an Asynchronous fashion. expandcollapse popup#include <GUIConstants.au3> #include <Misc.au3> #include <Array.au3> #include <File.au3> #include <String.au3> _Singleton( @ScriptName ) ;- Allow only a single instance to run Opt( "MustDeclareVars", 1 ) ;- Always a good thing to do Opt( "GUICloseOnESC", 1 ) ;- ESC closes the window Opt( "TrayIconHide", 0 ) ;- Show tray icon Global $h, $i, $j, $k, $w, $x, $y, $z Global Const $WM_COPYDATA = 0x4A Global Const $WM_CLOSE = 0x10 Global $objXL ;- Excel object Global $oComError = ObjEvent( "AutoIt.Error", "_ComErrFunc" ) GUICreate( "ExtLibrary", 180, 25, 10, 10 ) GUISetState( @SW_SHOW ) GUIRegisterMsg( $WM_COPYDATA, "ReceiveFromCaller" ) GUIRegisterMsg( $WM_CLOSE, "ReceiveFromCaller" ) GLOBAL $ME_PROCESSDATA = 1313 ;just some number, not sure how to avoid clashes with MS defined messages. GUIRegisterMsg($ME_PROCESSDATA, "ProcessData") While GUIGetMsg() <> $GUI_EVENT_CLOSE WEnd Func ReceiveFromCaller( $hWnd, $MsgID, $wParam, $lParam ) ;Do as little data processing as you can in this function. If $MsgID = $WM_CLOSE Then Exit If $MsgID = $WM_COPYDATA Then Local $objReceiveStruct, $objStringStruct, $lcCmdLine, $laCmdLine, $lcReturnValue $objReceiveStruct = DllStructCreate( "dword var1;dword var2;ptr var3", $lParam ) $objStringStruct = DllStructCreate( "char var1[1024]", DllStructGetData( $objReceiveStruct, 3 ) ) ; Just let $lcCmdLine be a global variable or a FIFO stack (array). $lcCmdLine = DllStructGetData( $objStringStruct, 1 ) $objReceiveStruct = 0 $objStringStruct = 0 ; Refactored code to new function ProcessData() ;TODO: PostMessage($WM_PROCESSDATA, $hMyHandle) ; EndIf EndFunc Func SendBackToCaller( $pcString ) ; NOTE: The caller should also implement a ProcessLater strategy to avoid the same kind of ObjCreate problem. Local $lnStringLen, $objStringStruct, $ptrStringStruct, $objSendStruct, $ptrSendStruct $lnStringLen = StringLen( $pcString ) + 1 $objStringStruct = DllStructCreate( "char var1[1024]" ) DllStructSetData( $objStringStruct, 1, $pcString ) $ptrStringStruct = DllStructGetPtr( $objStringStruct ) $objSendStruct = DllStructCreate( "dword var1;dword var2;ptr var3" ) DllStructSetData( $objSendStruct, "var1", 0 ) DllStructSetData( $objSendStruct, "var2", $lnStringLen ) DllStructSetData( $objSendStruct, "var3", $ptrStringStruct ) $ptrSendStruct = DllStructGetPtr( $objSendStruct ) ;- Create a pointer to this DLL structure _SendMessage( WinGetHandle( "DoFunction" ), $WM_COPYDATA, 0, $ptrSendStruct ) $objStringStruct = 0 $objSendStruct = 0 EndFunc Func ProcessData() ; Let this functoin retrive data from the FIFO stack and put it in $lcCmdLine $lcReturnValue = ParseCmdLineString( $lcCmdLine ) If IsString( $lcReturnValue ) Then SendBackToCaller( $lcReturnValue ) Else $laCmdLine = $lcReturnValue $lcReturnValue = Call( $laCmdLine[0], $laCmdLine[1] ) SendBackToCaller( $lcReturnValue ) EndIf EndFunc You have probably figured this out already.. Please keep your sig. small! Use the help file. Search the forum. Then ask unresolved questions :) Script plugin demo, Simple Trace udf, TrayMenuEx udf, IOChatter demo, freebasic multithreaded dll sample, PostMessage, Aspell, Code profiling
dpaynebrown Posted October 31, 2007 Author Posted October 31, 2007 (edited) Yes, that sounds about right. Store the data in the $WM_COPYDATA message. Then PostMessage ($ME_PROCESSDATA(You define this) ) to yourself. When your app receives $ME_PROCESSDATA then yuo take the previously stored data and process it. At this point you should be able to use4 ObjCreate(...)Take it a bit further and place the $WM_COPYDATA in a FIFO stack and you will be able to handle messages from multiple applications in an Asynchronous fashion.You have probably figured this out already.. Uten - Yes. After thinking about all the posted information the solution became clear early this morning. And now it all works just fine!The only difference is that I used AdlibEnable( "ProcessData", 10 ) as the last command in the ReceiveFromCaller function and AdlibDisable() as the first command in the ProcessData function. I think the PostMessage command may be a bit less "weird" and possibly more stable(?) We'll see!Finally, I did indeed implement a FIFO array to hold commands coming in from different apps.Thank you, and the rest, for all the help. Edited October 31, 2007 by dpaynebrown
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now