Jump to content

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

"EasyCOM.dll" development


  • Please log in to reply
33 replies to this topic
erictheturtle
  • Members
  • 101 posts
  • Last active: Sep 04 2011 02:07 PM
  • Joined: 27 Jun 2007
Update 3 Aug 2007: The "EasyCOM.dll" has been discontinued in favor of the alternative "Embedded Windows Scripting (VBScript & JScript) and COM" which is more flexible, and does not require an extra DLL.


[From the original post ]

I owe [Sean] much for [his] posts because it helped me understand enough of the raw workings of COM to finally use it myself.

For the longest time I've wanted a super easy method to work with COM in my scripting languages. I assumed it was complicated, so I thought it would be nice if someone could at least make a small DLL to handle the dirty work. It has surprised me that no one ever has (or have they?).

Now that I know enough to do so, I'm putting together a DLL to do that very thing. It meets my needs well since I can use it in any language that loads DLLs. Plus, writing COM in C++ (or even C) is ridiculously easier than in a scripting language.

Since I have wanted this for so long, I'm curious if there's any other interest in a DLL like this.

Since there was a little bit of interest in it, here is an update on the so-called "EASYCOM".

This DLL attempts to simplify the use of COM/OLE automation. Sean's much appreciated scripts are good when you don't want an extra DLL lingering around. But this DLL has the advantages of being cleaner to work with, faster, but you still need a little knowledge of COM to use it (although at the rate Sean is going, this DLL may just become obsolete :).

Development Status
So far the code is up to around 2000 lines of C++.

Currently the DLL API looks like this...

I have narrowed down the many VARIANT types to just 8, with the additional flag COM_BYREF:
COM_SKIP = COM_VOID //skip an optional argument, or no return data
COM_INT
COM_FLT
COM_DBL
COM_STR
COM_OBJ
COM_OBJ_UNKN
COM_ARRAY

Well designed COM objects should be converting all data to the types it needs (so you could just be passing in strings if you wanted). If there are any poorly designed COM objects out there, I may add an additional function to allow including raw VARIANTs as well.

DLL function list:
int CreateObject(str Program_id);
int CreateObjectCLSID(str Class_id);
int GetObject(str Program_id);
int GetObjectCLSID(str Class_id);
void ReleaseObject(int object_pointer);

int BeginMemberCall(int #_of_arguments); // passing -1 means it will 
                                // just count as they're added

int AddArgument(int type, data);

Invoke(int object_pointer, str member_name, int member_type);

void * GetArgument(argument_#); // for BYREF values
int GetArgumentType(argument_#); // necessary?
void * GetReturn();

int GetException_Code();
str GetException_Source();
str GetException_Description();
str GetException_HelpFile();
int GetException_HelpContext();

int EndMemberCall();

str GetErrorDescription();
int GetComErrorCode();

Whenever a function fails, it will return 0 (or NULL). Calling GetErrorDescription() will retrieve a nice string describing what's wrong. If it was a COM related error, GetComErrorCode() will have the last error.

Most of this code is most certainly not thread-safe, so it will need to be surrounded by "Critical" statments (but that can be handled by wrapper functions).

This DLL API is definitely not set in stone. If you have any thoughts or requests, I would be interested in hearing them.

When it's fairly stable, I have no problem releasing the source code and the DLL binary. Not sure where to publish it however...

Boo
  • Guests
  • Last active:
  • Joined: --
May be I don't understand nothing (and it's more than possible !!!), but two questions :
1) Where is the DLL ?
2) Where is the example of use ?
Thanks for your answer...

ahklerner
  • Members
  • 1386 posts
  • Last active: Oct 08 2014 10:29 AM
  • Joined: 26 Jun 2006

When it's fairly stable, I have no problem releasing the source code and the DLL binary. Not sure where to publish it however...



Boo
  • Guests
  • Last active:
  • Joined: --
2ahklerner
I red it, and it's why I asked the first question...

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I would say either it isn't "fairly stable" yet, or erictheturtle hasn't found somewhere to publish it...

I suggest <!-- m -->https://ahknet.autohotkey.com/<!-- m --> and possibly the Utilities & Resources forum. I've seen other scripts accompanied by .dll files in the Scripts & Functions forum, so if you provide an .ahk wrapper for your .dll, you could post them both there.

erictheturtle
  • Members
  • 101 posts
  • Last active: Sep 04 2011 02:07 PM
  • Joined: 27 Jun 2007
I made some changes to the DLL API. I decided against having separate functions for Methods, Property Get, and Property Put, and combined them into one Invoke function with an added parameter. I updated the original post to reflect this, and added some additional details.

I realize however that people are probably more interested in the AHK wrapper functions API. So here it is (thrown together somewhat quickly, so there are probably errors):

Update: Removed InvokeN() and InvokeSN()

LoadEasyCOM / UnloadEasyCOM

LoadEasyCOM()
UnloadEasyCOM()

Remarks
Loads/unloads the EasyCOM.dll.
On error, a Msgbox will display any error description, and the application is terminated.

Example

LoadEasyCOM()

;; do some stuff...

UnloadEasyCOM()

CreateObject

CreateObject(ProgramId_or_ClassId)

Parameters
ProgramId_or_ClassId - A program identifier (e.g. "Excel.Application") or class identifier. (e.g. "{6D5140C1-7436-11CE-8034-00AA006009FA}")

Remarks
Use this function to create an instance of a COM object when the COM object doesn't already exist, or a new instance is desired.
On success, returns a handle (pointer) to the newly created COM object.
On error, a Msgbox will display any error description, and the thread is terminated.

Examples

objExcel := CreateObject("Excel.Application")
objSpeechAPI := CreateObject("{96749377-3391-11D2-9EE3-00C04F797396}")

GetObject

GetObject(ProgramId_or_ClassId)

Parameters
ProgramId_or_ClassId - A program identifier (e.g. "Excel.Application") or class identifier. (e.g. "{6D5140C1-7436-11CE-8034-00AA006009FA}")

Remarks
Use this function to get a handle (pointer) of a COM object that already exists.
On success, returns a handle (pointer) to the existing COM object.
On error, a Msgbox will display any error description, and the thread is terminated.

Example

objExcel := GetObject("Excel.Application")

ReleaseObject

ReleaseObject(ObjectHandle)

Parameters
ObjectHandle - Handle (pointer) to a COM object.

Remarks
Frees the COM object. All handles/pointers to this object become invalid.
On error, a Msgbox will display any error description, and the thread is terminated.
Note: Passing an invalid COM handle (pointer) to this function will crash your program!

Example

objExcel := CreateObject("Excel.Application")

;; do some stuff...

ReleaseObject(objExcel)

Invoke / InvokeEx

Invoke(ObjectHandle, MemberName [, arg1 [, arg2 [, arg3 [, ...]]]])
InvokeEx(ObjectHandle, MemberName [, type1, arg1 [, type2, arg2 [, ...]]])

Parameters
ObjectHandle - Handle (pointer) to a COM object.
MemberName - The name of a member of the COM object.[*:186okwox]If the member is a method, add "()" to the end of the name (e.g. "SomeMethod()").
[*:186okwox]If the member is a property whose value is being set, add "=" to the end of the name (e.g. "SomeProperty=").
[*:186okwox]If the member is a property whose value is being retrieved, just supply the property name without any suffix (e.g. "AnotherProperty").type1, type2, ... - One of the EasyCOM types (COM_SKIP, COM_INT, COM_FLT, COM_DBL, COM_STR, COM_OBJ, COM_OBJ_UNKN, COM_ARRAY) which may also have the COM_BYREF bit set.
arg1, arg2, ... - Data to be passed to the COM function.

Remarks
These 2 functions are useful in different situations:

Invoke() - If the member being invoked only takes COM_INT, COM_FLT, COM_DBL, or COM_STR types, then all the arguments can simply be passed as strings. Most COM objects will convert the strings to the data types it needs. This saves from having to explicitly list the parameter types. If the member requires parameters other than those listed, or it is unable to convert the passed strings to the necessary data type, then you should instead use Invoke().

InvokeEx() - This function requires explicit declarations of every argument type.

Up to 9 arguments may be passed (but that number is easily increased).
Invoke() uses "`b" (backspace character) as the default value for testing when the arguments end. If a "`b" string needs to be passed to a COM call, either change this default character in the Invoke() function, or use the InvokeEx() function.
If a COM object is returned, then ReleaseObject() should be called on it when the object is no longer needed.
On error, a Msgbox will display any error description, and the thread is terminated.
Note: Passing an invalid COM handle (pointer) to this function will crash your program!

Examples

objSpeech := CreateObject("SAPI.SpVoice")

;; These functions do the same thing
Invoke(objSpeech, "Speak()", "hello there", 0)  
InvokeEx(objSpeech, "Speak()", COM_STR, "hello there", COM_INT, 0)  

objFSO := CreateObject("Scripting.FileSystemObject")

;; All of these functions do the same thing
Invoke(objFSO, "OpenTextFile()"
       , "file.txt"
	   , 2  ; Open for writing
	   , True  ; Create file
	   , 0) ; non-unicode
Invoke(objFSO, "OpenTextFile()" ; Can skip optional parameters
	   , "file.txt")
InvokeEx(objFSO, "OpenTextFile()" ; Must declare each argument type
       , COM_STR, "file.txt"
	   , COM_INT, 2
	   , COM_INT, True
	   , COM_INT, 0)
InvokeEx(objFSO, "OpenTextFile()" ; Can skip optional parameters
       , COM_STR, "file.txt")

objXL := CreateObject("Excel.Application")

Invoke(objXL, "Visible=", True)

objWrkBks := Invoke(objXL, "Workbooks")
objWB := Invoke(objWrkBks, "Add()")
objWS := Invoke(objWB, "ActiveSheet")

;; Invoke() doesn't work with the 'Cells' property (exception 0x800A03EC)
;; because the second argument can either be column index (integer),
;; or column name (string, such as "A")
;; Therefore InvokeEx() must be used
objCell := InvokeEx(objWS, "Cells"
					, COM_INT, A_Index
					, COM_INT, 1)

Invoke(objCell, "Value=", "Happy!")
ReleaseObject(objCell)

objCell := InvokeEx(objWS, "Cells"
					, COM_INT, A_Index
					, COM_INT, 2)
Invoke(objCell, "Value=", 1000.34567)
ReleaseObject(objCell)

ReleaseObject(objWS)
ReleaseObject(objWB)
ReleaseObject(objWrkBks)
ReleaseObject(objXL)


Some things I still need to add:[*:186okwox]Wrapper functions for DLL GetArgument() for arguments passed by reference.
[*:186okwox]GetObject(), but from a file
[*:186okwox]Simplified SAFEARRAY creation and accessSome questions I have about this API:[*:186okwox]Currently, whenever there is a COM error, the thread is simply terminated. I imagine this won't be the desired behavior in many cases. So how would people want to handle errors?
[*:186okwox]About the MemberName parameter for the Invoke functions: Does the appended "=" and "()" make sense? Or would it just be better to keep such a thing in a separate parameter?
[*:186okwox]Do these different Invoke() functions help at all? Or do they instead just add confusion? Would be better to stick with one function?
[*:186okwox]Does anyone know of a COM object that requires some 'by reference' arguments? I need to test those related functions.



I suggest <!-- m -->https://ahknet.autohotkey.com/..<!-- m -->.

Thanks lexikos, I was considering using autohotkey.net. Seems like the best place.

1) Where is the DLL ?
2) Where is the example of use ?


1) The DLL is sitting on my computer(s) at the moment, alternating between a working and non-working state (it's still under development).
2) My original post had a pretty good example of use--both using the raw DLL functions, and some nicer wrapper functions. However, there are some more up-to-date examples in the above API.

ABCyourway
  • Guests
  • Last active:
  • Joined: --
Good news!

I guess quite a few people are making good uses of other great COM wrapper scripts (e.g by Sean) but I am not.
I know that's mainly because of my lack of uderstanding the scripts,
but I have to say it's not that easy and intuitive.
I don't know much about programming, but I heard that the nature of COM lies in the easiness of its usage.

I hope yours will be :D

Maybe someone would say "You are asking too much!"
Yes, I am!
'cause they are experts and good enough to make ones,
and those scripts are fabluous enough for more users to play with.

These are the feelings only noobs like me are getting from the recent posts about COM here?

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004
Hi erictheturtle,

The documentation so far looks interesting. Would you please consider releasing a beta version of the dll ?

erictheturtle
  • Members
  • 101 posts
  • Last active: Sep 04 2011 02:07 PM
  • Joined: 27 Jun 2007

I heard that the nature of COM lies in the easiness of its usage.

Well I don't know about COM being easy to use. I find it quite cumbersome. I would like to make EasyCOM as easy I possible. I hope feedback from the community will help with that. Specifically, I really would like some feedback from those questions I posted below the Wrapper API.

Would you please consider releasing a beta version of the dll ?

Even though there's still more to add, the DLL has been well-behaved for awhile now, so it's probably ok to share it.

This is v0.0.1.62 BETA.

EasyCOM.dll

I'm not done with all the wrapper functions, but I'll share what I have of that too.
Note: you need to edit the location of the easycom.dll.

EasyCOM.ahk v0.002

Here is a script I've been using for simple regression testing. Contains some of the examples in the Wrapper API posted above.

testcom.ahk for easycom.ahk v0.002

All this should give people something to play with for awhile.

I hope most everything is clear. Let me know if you have any questions. And again, I'm very interested in feedback about the API. I thought long and hard about the API, but there could be better ways of doing things that I didn't see.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Some questions I have about this API:[*:26mhj1kl]Currently, whenever there is a COM error, the thread is simply terminated. I imagine this won't be the desired behavior in many cases. So how would people want to handle errors?
[*:26mhj1kl]About the MemberName parameter for the Invoke functions: Does the appended "=" and "()" make sense? Or would it just be better to keep such a thing in a separate parameter?
[*:26mhj1kl]Do these different Invoke() functions help at all? Or do they instead just add confusion? Would be better to stick with one or two functions?

[*:26mhj1kl]I suppose set ErrorLevel and return? Users of the API would need to do their own error-checking.
[*:26mhj1kl]"()" makes sense to me, as I'm used to referring to functions as "func()." I think a seperate parameter might be more intuitive to some users, but at the same time less convenient.
[*:26mhj1kl]I don't think the different Invoke() functions add to the confusion, as they all seem to have a purpose. I do wonder, though, if it would be possible to support optional parameters without having to specify ParamCount...

erictheturtle
  • Members
  • 101 posts
  • Last active: Sep 04 2011 02:07 PM
  • Joined: 27 Jun 2007

[*:u2ag230y]I suppose set ErrorLevel and return? Users of the API would need to do their own error-checking.

Thanks lexikos, that got me thinking. How about if another function is added to the Wrapper API, based on the VB approach...
OnErrorResumeNext(true/false)
By default this internal flag is set to false, and on error a msgbox is shown and the thread exits (as is the current behavior). If this flag is set to true, then the code continues, with ErrorLevel being set to 0 on falure and 1 on success. Then have additional Wrapper API functions...
GetErrorDescription()
GetComErrorCode()
GetExceptionDescription()
GetExceptionCode()
...to get details on the error, if desired.


[*:u2ag230y]I don't think the different Invoke() functions add to the confusion, as they all seem to have a purpose....

Alright, I was worried they were too much. I guess it's not too bad.

...I do wonder, though, if it would be possible to support optional parameters without having to specify ParamCount...

Hmm, that's a good point. With my goal to make COM as easy as possible for the script user, that really should be added. Then we could eliminate two of those Invoke() functions.

The only way I know how to find the parameter count is to query for the 'ITypeInfo' interface and dig through the data it returns. If there is a better way, someone please let me know!
In any case, I was planning on getting into ITypeInfo eventually so that a TypeLib browser could be created.
-m35

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

The only way I know how to find the parameter count is to query for the 'ITypeInfo' interface and dig through the data it returns. If there is a better way, someone please let me know!

I think it was actually me who asked about this. After that I tested a few cases, and to my surprise, no need to care about the whole number of parameters! Looks like it's taken care by the COM manager in dispatch interfaces, so, one of the benefits of Dispatch over VTable.

In other words, even though the parameters are added oppositely into rgvarg array (:first parameter should be the last member of rgvarg), need not to specify all the remaining optional parameters: COM manager seems to grant that the last member of rgvarg array should be always the first parameter, not that the first member of rgvarg array should be the last parameter.

erictheturtle
  • Members
  • 101 posts
  • Last active: Sep 04 2011 02:07 PM
  • Joined: 27 Jun 2007
Well check that out!

Ran a quick test:
InvokeS(objFSO, "OpenTextFile()"
      , "file.txt"
      , 1  ; Open for reading
      , 0 ; Don't create file
      , 0) ; non-unicode
The last 3 parameters are optional. So if I remove them...
InvokeS(objFSO, "OpenTextFile()"
      , "file.txt")
It still runs fine!

Not sure where I got the idea that all parameters had to be specified. Maybe I was testing some function once and wasn't specifying enough required parameters so I thought all the optional ones had to be supplied.

That is so wonderfully easy, I can just get rid of InvokeN() and InvokeSN() (which is really simple because I never got around to implementing those functions :) and nothing else needs to change.

Thank you very much Sean!
-m35

tfcahm
  • Members
  • 48 posts
  • Last active: Sep 06 2007 03:07 PM
  • Joined: 20 May 2007

I'm very interested in feedback about the API

Why not combine CreateObject and CreateObjectCLSID into just CreateObject(ProgramId | ClassId)? Let EasyCom recognize what kind of ID it is. It makes one less thing for the poor scripter to remember. Same suggestion for GetObject/GetObjectCLSID.

* Does anyone know of a COM object that requires some 'by reference' arguments? I need to test those related functions.

Sure do, BookCAT has several methods that use ByRef BSTR arguments. There is plenty of discussion about it in the COM Helper thread. I haven't messed with them yet, but FastTrack Scheduler, TopStyle, and SnagIt have ByRef BSTRs and Nero has some ByRef Variants.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
And, I have a suggestion about the name.
I suppose that actually InvokeS will be used the most frequently, so it may better be named to Invoke, and the original Invoke to other.

BTW, I think the invokekind flags can be combined/grouped as:

DISPATCH_METHOD | DISPATCH_PROPERTYGET
DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF
Although I haven't checked completely the last assumption as I couldn't find an example of PropertyPutRef, but it worked fine with PropertyPut, at least. On the contrary, I could check several cases on the first one and indeed it worked flawlessly with both Method and PropertyGet. So, may better alter the InvokeKind to the above for PropertyGet (with no NameParam) and PropertyPut (with NameParam to "="), and then there seems to be no need of separate "Method()".

OTOH, although I'm not aware of any real examples atm, there could be some rare cases where Method and PropertyGet have the same name.
However, I'm not sure if those cases can be handled too without introducing an extra flag or function.