TheSwamp

Code Red => AutoLISP (Vanilla / Visual) => Topic started by: SIDESHOWBOB on March 16, 2012, 10:08:34 AM

Title: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 16, 2012, 10:08:34 AM
I tried passing a function to my COM object as an argument, with the hope that the COM object might be handed an IDispatch* which I could call ...
Code - Auto/Visual Lisp: [Select]
  1. (defun hi () (princ "hi\n"))
  2. (vlax-invoke-method calculation 'callbacktest 'hi)
  3. ; error: lisp value has no coercion to VARIANT with this type:  HI
but vlax-invoke-method seems to expect a VARIANT rather than a function for its argument. 

Is there a way to do this does anyone know?
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: Daniel Eiszele on March 17, 2012, 06:35:52 AM
Maybe pass your autocad application object as well as a string representing the lisp function you wish to create a callback to and then use late binding (or early binding if you have the autocad type library linked), to call application.document.utility.sendcommand("function name"). You could also pass the utility object directly.  This is untested but I am sure it will pass an Idispatch interface you can cast to the relevant interface.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: BlackBox on March 17, 2012, 07:11:38 AM
AFAIK- it's not vlax-invoke-method that is expecting a variant, but the Calculation Method in your example above.

Perhaps this is only loosly related to the topic, but at least when calling a Method, one may get different returns depending on the syntax of the calling statement, I.e., vlax-invoke, etc. Not sure if this has any role in calling any given Method, per-se.

HTH
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 19, 2012, 07:59:59 AM
I don't think my callbacktest is expecting a variant, here's the declaration:

Code - C++: [Select]
  1. interface ICalculation : IDispatch{
  2.         [id(7), helpstring("method callbacktest")] HRESULT callbacktest([in] IDispatch* callback);
  3. };
  4.  
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 19, 2012, 08:04:28 AM
Calling vlax-invoke instead is a nice idea though I get
Code - Auto/Visual Lisp: [Select]
  1. ; error: argument type mismatch: HI
... will have a go with Daniel's suggestion
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 19, 2012, 10:39:09 AM
Daniel - I have nearly got your suggestion working, thank you!!  (The only difference being that SendCommand is a member of Document, not Utility). 

Is there a way to prevent the command being echoed in autocad though?  One use for a callback I have is setting a progress bar, so it won't look great to see
(acet-ui-progress-safe n) appear 100 times in the command window.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 19, 2012, 01:26:04 PM
Another issue I have just found is that the commands sent this way seem to be queued until my call to (vlax-invoke-method) is over.  Is there any way to have them executed immediately?
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: Daniel Eiszele on March 19, 2012, 04:08:15 PM
Daniel - I have nearly got your suggestion working, thank you!!  (The only difference being that SendCommand is a member of Document, not Utility). 

Is there a way to prevent the command being echoed in autocad though?  One use for a callback I have is setting a progress bar, so it won't look great to see
(acet-ui-progress-safe n) appear 100 times in the command window.

Yes, I remembered that after I replied.  You can use Document.EvaluateLisp() to send your commands without echoing to the command line. (It may also remedy your second problem also?)
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 20, 2012, 07:55:48 AM
There doesn't seem to be a method on Application or Document called EvaluateLisp.

Where are these all documented?  I found this link http://www.kxcad.net/autodesk/autocad/AutoCAD_ActiveX_and_VBA_Reference/idh_application_object.htm which mentioned a method Eval() but that's for VBA not Lisp.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: Daniel Eiszele on March 20, 2012, 09:19:04 PM
Ahh... I do most of my hacking for Bricscad and found the function by accident by interrogating my IDE's intellisense for the document object.  It seems that the function isn't even documented in the Bricscad Help Files.  I have however found reference to something called the "Visual Lisp ActiveX Module" which looks promising for Autocad use.  The only thing is it seems to be undocumented also, which means a bit of googling and probably a lot of trial and error!  There are probably arx functions that you could reference if you are confident doing this but nothing that is pure COM so far as I understand.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: BlackBox on March 21, 2012, 01:49:59 AM
For Visual LISP documentation, consult the VLIDE's Apropos, and VBAIDE's developer documentation. Both Visual LISP and VBA can access the ActiveX API.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 21, 2012, 06:32:17 AM
Thanks RenderMan but I'm looking for the reverse really - wanting to call some visual lisp functions from ActiveX/COM, not vice versa.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 22, 2012, 05:18:00 AM
Looks like if I can't callback synchronously over COM I'll have to pop up my own progress bar window.

Is there a call that tells autocad to at least handle window events so it doesn't appear to freeze up while my dll is calculating?
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: Daniel Eiszele on March 22, 2012, 05:57:18 AM
Looks like if I can't callback synchronously over COM I'll have to pop up my own progress bar window.

Is there a call that tells autocad to at least handle window events so it doesn't appear to freeze up while my dll is calculating?

The synchronous call may be possible using the visual lisp activex module but if that's too much effort (and it probably is), have you considered converting your activex dll to a Com addin and do away with the lisp completely?

As to the second issue, surely if you are displaying a progress bar it shouldn't matter if autocad appears to "freeze" as the bar informs the user it is still working?
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 22, 2012, 06:05:00 AM
Daniel thanks again for the pointers - but I have to ask

The Visual lisp activex module - what is that and where to find docs?

What is a COM addin (from an autocad perspective) and what extra functionality would that buy me given I can already make COM calls to autocad from my c++?
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: Daniel Eiszele on March 22, 2012, 06:37:42 AM
I don't have autocad so the visual lisp activex module is only something I found online. It appears to be a dll you can reference in your  com project which gives direct access to the available lisp functions bypassing the command line. You will have to google it if you want to find out more as I don't have access to acad and can't test anything I find anyway.

As far as a Com addin is concerned; it is basically the same activex object but you implement the Iextensibility2 interface.  Then you add entries in the registry so that it loads automatically when autocad starts.  From there on in any functions that you register with autocad are automatically available from the command line. You can also add these functions to toolbars and menu items and react to application and document events.  And because it is in process you should see a big speed difference in computation intensive operations.

Something to think about maybe?
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 22, 2012, 06:46:01 AM
Cheers will have to investigate further.  Don't think I need to make a COM addin - my vlisp handles the interface quite nicely now and the dll when called is fast enough.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on March 30, 2012, 08:11:03 AM
For future reference - over COM, I just found the Update method on the application object which causes it to, er, update.

This can be combined with ActiveDocument.Utility.Prompt to display messages synchronously in the Autocad window over COM.

If you begin each message with "\r" then it will overwrite the "Command:" prompt, though not the previous message sent to Prompt (any ideas on how to do this welcome!)

So, it's not quite a progress bar, but it'll do the job.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: irneb on April 01, 2012, 04:05:55 AM
If it's a progress bar you want ... and you're using the command-line to show a text representation of it, you don't need C++/ActiveX/COM for that. Here's a pure lisp function of mine which does much the same - but in the status bar:
Code - Auto/Visual Lisp: [Select]
  1. ;;; -------------------------------------------------------------------------------------
  2. ;;; Show a progress bar message in the status bar
  3. ;;; -------------------------------------------------------------------------------------
  4. (defun TextProgressBar (msg n / l i s)
  5.   (if n
  6.     (progn
  7.       ;; Get the mode tile's length
  8.       (setq l (- (atoi (menucmd "M=$(linelen)")) (strlen msg)))
  9.       ;; Get the numnber of characters simulating the number n (0.0= none, 1.0=all)
  10.       (setq i (fix (* l n)))
  11.       (repeat l
  12.         (if (> (setq i (1- i)) 0)
  13.           (setq s (cons 33 s))
  14.           (setq s (cons 46 s))
  15.         )
  16.       )
  17.       (grtext -1 (strcat msg (vl-list->string (reverse s))))
  18.     )
  19.     (grtext -1 msg)
  20.   )
  21. )
  22.  
If you want a true progress bar, then you could import a function call from the acad.exe file (as if it's a dll): http://through-the-interface.typepad.com/through_the_interface/2007/05/displaying_a_pr.html (http://through-the-interface.typepad.com/through_the_interface/2007/05/displaying_a_pr.html)

BTW, while you're in C++, why're you making an ActiveX connection? I'd have thought you'd be better off going with C# & DotNet ... see some of the comments on the bottom of that page.

Edit: And if you want to call a lisp function directly, the the P/Invoke method (as explained in Kean's blog) can also work: http://www.theswamp.org/index.php?topic=35714.0
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: BlackBox on April 01, 2012, 10:37:35 AM
If you don't mind the .ARX requirement, there's always the Dos_GetProgress (http://www.en.na.mcneel.com/doslib/interface_functions/dos_getprogress.htm) function too.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on April 02, 2012, 04:42:30 AM
@irneb - cheers - displaying a progress bar from lisp was never the issue.  My app architecture is autocad -> vlisp -> COM -> C++ because I was hoping to gain wide compatibility over different autocad versions, rather than taking the .net approach where I have to recompile the plugin for each autocad version I would like to support.  The progress bar however has to call functions in the reverse direction (C++ to autocad) so whether I'll have actually achieved version independence (given I'm now using undocumented COM calls) is another matter..!
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: irneb on April 02, 2012, 08:03:42 AM
Yes, if you're using P/Invoke you might find there's some issues between versions - seeing as the function entry point may be different in 2 acad.exe files. I think the major one would be at the 2006 split when ADesk changed to using UniCode.

The DotNet stuff doesn't need recompiling all the time. It depends on the versions, some versions work perfectly fine with the same DLL file (e.g. I've got some which work on both 2008 and 2012). The issue is that you need to compile using the lowest common DotNet version - e.g. instead of 3.5 use 2.0. Also try to steer clear of API functions newly introduced into newer versions. Apparently there's some new requirement with 2013, though I've not tested it yet.

The ARX's need recompiling between major versions. Every time the "true" version number's integer portion changes. Thus 2007/8/9 may work fine with the same ARX, but a new one would be required for 2010/11/12. Thus far ADesk's grouped their major versions into 3 consecutive releases.

All that said, I'm unsure why you're going with COM outside of lisp. Is there some reason? AFAIK all acad's com interfaces are also available in VLisp. And the stuff you can't get to in ALisp/VLisp is definitely also impossible to get to through COM.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on April 02, 2012, 08:18:41 AM
All that said, I'm unsure why you're going with COM outside of lisp. Is there some reason?

Yes.  The app backend is CPU heavy, and is already written in native c++ (for another frontend, not autocad).  I wrapped it in COM so I could call it from lisp.  This is all fine until I need a progress bar - the backend has to make 'update progress' calls, but as vlisp doesn't support callbacks over COM, another method was needed so the C++ could communicate to the autocad user how much progress had been made (at intervals chosen by the algorithm running in C++).  This eventually led me to autocad's COM interface which allows me to put messages in the display directly over COM, bypassing the lisp environment (which I can't access from there in any case).
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: irneb on April 02, 2012, 09:51:13 AM
Have you thought of popping up a modal dialog box with a message & windows progress bar? That way you don't need to fiddle with strange ways of getting acad's progress bar to display - just use a normal windows form which you can create direct in C++.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on April 02, 2012, 10:09:32 AM
Yes, though I'm happy with the Prompt and Update COM calls for now.
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: SIDESHOWBOB on June 21, 2012, 09:23:35 AM
Have you thought of popping up a modal dialog box with a message & windows progress bar? That way you don't need to fiddle with strange ways of getting acad's progress bar to display - just use a normal windows form which you can create direct in C++.

Irneb,

I'm coming back around to this idea.  If I want to do this, don't I need to get the HWND of autocad from somewhere?  Do you know how to do that?

Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: Lee Mac on June 21, 2012, 09:26:41 AM
If I want to do this, don't I need to get the HWND of autocad from somewhere?  Do you know how to do that?

In LISP:

Code - Auto/Visual Lisp: [Select]

Or, for the Active Document window:

Code - Auto/Visual Lisp: [Select]
Title: Re: Passing reactors/callbacks to COM object and calling them from the COM object
Post by: BlackBox on June 21, 2012, 09:44:21 AM

I also use this (vla-get-hwnd) with the Shell.Application Object's BrowseForFolder Method.  :wink: