Sign in to follow this  
Followers 0
dpaynebrown

This is, apparently, a no-no ... Now a yes-yes!

12 posts in this topic

#1 ·  Posted (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 by dpaynebrown

Share this post


Link to post
Share on other sites



#2 ·  Posted (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 by evilertoaster

Share this post


Link to post
Share on other sites

#3 ·  Posted (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 by dpaynebrown

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites

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)..

Share this post


Link to post
Share on other sites

#6 ·  Posted (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 by dpaynebrown

Share this post


Link to post
Share on other sites

#7 ·  Posted (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 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.

Edited 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.

Share this post


Link to post
Share on other sites

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?

If $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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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?

#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

Share this post


Link to post
Share on other sites

...., 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.

#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..<_<

Share this post


Link to post
Share on other sites

#12 ·  Posted (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 by dpaynebrown

Share this post


Link to post
Share on other sites

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 account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0