May 01, 2024, 11:10:24 AM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


CreateObject?

Started by J B Wood (Zumwalt), October 20, 2006, 03:10:59 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

J B Wood (Zumwalt)

What is the equal to this in Aurora?

Ionic Wind Support Team

?  Not much context there to go on dude.

In Aurora you create an object either on the stack or with the NEW statement.  Pretty sure you already knew that though
Ionic Wind Support Team

J B Wood (Zumwalt)

VB example:

dim myExcel as object
set myExcel = New CreateObject("Excel.Application")



Ionic Wind Support Team

It is probably a COM object so you would need the interface definition for it.  Jose Roca could help with his type lib browser.
Ionic Wind Support Team

Parker

That's late binding, which is a lot harder to do when you're actually writing that code. However you can use Aurora's COM, it'll just look different.

J B Wood (Zumwalt)

Late binding has major advantages, especially when the user can have different versions of the application, for instance one could have office 97, another 200, another 2003, each have there own class enstansiation, if I late bind, I don't care about a specific com dll, I just attach to what is registered.

Parker

but it's very complicated.

Mike Stefanik

Omitting all of the defines and structures, here's basically what COM in Aurora looks like if you want to use late-binding. It's example code from a small test project I wrote with a simple COM server that had an "About" method which displayed a message box.

Here's what the Visual Basic code would look like:


Dim o As Object
Set o = New CreateObject("TestControl.TestClass")
o.About


And here's the corresponding Aurora code:


HRESULT hr;

hr = CoInitializeEx(null, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
ÂÃ,  ÂÃ,  // Unable to initialize COM subsystem
ÂÃ,  ÂÃ,  return 0;
}

LPOLESTR pwszProgID = OLESTR("TestControl.TestClass");
GUID CLSID_TestClass;
hr = CLSIDFromProgID(pwszProgID, CLSID_TestClass);
if (FAILED(hr))
{
ÂÃ,  ÂÃ,  // Unable to determine class ID
ÂÃ,  ÂÃ,  return 0;
}

LPOLESTR pwszCLSID = NULL;
StringFromCLSID(CLSID_TestClass, &pwszCLSID);
CoTaskMemFree(pwszCLSID);

IUnknown *pUnknown;
hr = CoCreateInstance(CLSID_TestClass, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, &pUnknown);
if (FAILED(hr))
{
ÂÃ,  ÂÃ,  // Unable to create object instance
ÂÃ,  ÂÃ,  return 0;
}

IDispatch *pIDispatch;
hr = pUnknown->QueryInterface(IID_IDispatch, &pIDispatch);
if (FAILED(hr))
{
ÂÃ,  ÂÃ,  // Unable to obtain interface
ÂÃ,  ÂÃ,  return 0;
}

LPOLESTR pwszMethod = OLESTR("About");
DISPID id;
hr = pIDispatch->GetIDsOfNames(IID_NULL, &pwszMethod, 1, LOCALE_USER_DEFAULT, &id);
delete pwszMethod;
if (FAILED(hr))
{
ÂÃ,  ÂÃ,  // Unable to find method in interface
ÂÃ,  ÂÃ,  return 0;
}

DISPPARAMS params;
ZeroMemory(&params, len(DISPPARAMS));

UINT nArgErr;
hr = pIDispatch->Invoke(id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, &nArgErr);
if (FAILED(hr))
{
ÂÃ,  ÂÃ,  // Unable to invoke the method
ÂÃ,  ÂÃ,  return 0;
}

ULONG dwRef;
dwRef = pIDispatch->Release();


Like Parker said, it's complicated. You're basically using COM the same way that you would in C, and even using trivial objects becomes a non-trivial exercise.
Mike Stefanik
www.catalyst.com
Catalyst Development Corporation

Ionic Wind Support Team

Well you can make it easier.  C++ creates a container class when you use a custom ole control.  That class contains all of the Invoke methods wrapped into class method calls.  Which is doable in Aurora.

You can also use the TypeLib browser (http://www.forum.it-berater.org/index.php?topic=7.0) to generate the interface definitions and use CoCreateInstance to get a pointer to the interface. Saves a lot of typing and the code generated is very useful.

Ionic Wind Support Team

Mike Stefanik

He specifically mentioned late binding, which means you can't use vtable interfaces; you have to use GetIDsOfNames (getting the dispatch IDs for the methods that you want to call) and then use Invoke. Of course, you could create your own wrapper class that implemented each method, called Invoke, etc. but you're still in for a lot of coding. For the kind of "ease of use" that you have with COM objects in VB, there'd need to be direct support for them in the compiler. In other words, for late binding support to COM objects, the compiler would have to allow the use of class methods that are completely undefined at compile time, and dynamically package up the arguments and call Invoke.

object o = CreateObject("TestControl.TestClass");
o->About();


Upon seeing an undefined method, the compiler would need to blow code like what I have above, rather than generate an error. Of course, if there was no "About" method, then you'd get an error at runtime.
Mike Stefanik
www.catalyst.com
Catalyst Development Corporation

Ionic Wind Support Team

Or create a little helper class that does the lookup and uses a variable number of parameters in a method named "Call" or something.

CObject o;
o.Create("TestControl.TestClass");
o.Call("About");
o.Call("SetHandle",1, h);

Wouldn't be too hard to implement.  I did something similar using the AtlAxWin class in IBasic.  This is part of the 'Invoke' method for that demo.


SUB InvokeMethod(iFace as IDispatch,name as STRING,numargs as INT,...),VARIANT
POINTER pArgs,pArgs2,pTemp,pStr:pArgs = VA_START(numargs)
UINT puArgErr:puArgErr = 0
INT dispID,x
STRING method,strTemp
POINTER pMethod:pMethod = method
MultiByteToWideChar(0,0,name,-1,method,125)
variant result
DISPPARAMS param
param.rgvarg = NULL
param.cArgs = 0
param.cNamedArgs = 0
param.rgdispidNamedArgs = NULL
SET_INTERFACE iFace,IDispatch
IF iFace->GetIDsOfNames(NULL,&pMethod,1,NULL,dispID)>=0
'create the argument list
IF numargs
pArgs2 = pArgs + (4 * numargs)
param.cArgs = numargs
param.rgvarg = NEW(VARIANT,numargs)
FOR x=0 to numargs-1
param.#<VARIANT>rgvarg[x].vt = #<WORD>pArgs
pTemp = param.#<VARIANT>rgvarg[x].item
SELECT #<WORD>pArgs & 0xFFF
CASE VT_I1
CASE& VT_UI1
#<CHAR>pTemp = #<CHAR>pArgs2
pArgs2 += 4


Which in Aurora could be simplified down a bit since the class would have the interface pointer returned by the Create method.

Ionic Wind Support Team

Ionic Wind Support Team

We should suggest it to Sapero ;)  He seems to really enjoy that kind of programming.  Betcha he could whip out a CObject class in a few hours.
Ionic Wind Support Team

JR

I have just uploaded a new version of the TypeLib Browser that fixes a few small bugs:



  • Bug fix: OUT parameters of the type VT_USERDEFINED that are members of an enumeration or an alias for a standard type didn't get an "*" before its name.
  • Bug fix: The IUnknown interface (defined in STDOLE2.TLB) got an " :" after its name.
  • Bug fix: Interfaces without an vtable are fully skipped. In version 1.01 where listed, originating an interface definition without virtual declarations.
  • Bug fix: In the generated code for the identifiers, the name of the interface was omitted in the remed line DEFINE_GUID(_IID_xxx).

http://www.forum.it-berater.org/index.php?topic=420.0

Mike Stefanik

Thanks, it's a great tool for folks who need to work with COM in Aurora.
Mike Stefanik
www.catalyst.com
Catalyst Development Corporation

JR

Quote from: Jonathan (zumwalt) Wood on October 21, 2006, 12:54:59 PM
Late binding has major advantages, especially when the user can have different versions of the application, for instance one could have office 97, another 200, another 2003, each have there own class enstansiation, if I late bind, I don't care about a specific com dll, I just attach to what is registered.

This is not what late binding means. The only difference between late binding and early binding is that, when using late binding, an additional call to the GetIDsOfNames method should be performed to retrieve the DispID (dispatch identifier) of the method or property before calling Invoke. Compilers supporting Automation natively retrieve the DispID at compile file and hardcode it in the call to Invoke.

Being able to use the same code with different versions of a component involves the use of a version independent ProgID (program identifier), if the component supports it, which is the same ProgID that the version dependent one without the version number, e.g. Excel.Application.10 is a version dependent ProgID and only will work with version 10 of Excel, whereas Excel.Application will work with any version. But this is not always as wonderful as it looks, because if you are using a method available in version 10 but not in lesser versions, your application will fail if the user has not version 10 installed. Microsoft is now increasingly using side-by-side installations, so you can have different versions of a component installed at the same time.

No matter which syntax you use to create an instance of the component, it ends in a call to CoCreateInstance, that in turn is a wrapper for CoGetClassObject. The returned pointer (in fact the address of a pointer to the virtual table of the object, that is, a pointer to a pointer), can be used both to call the methods and properties directly or through the Invoke method of the IDispatch interface.  With Aurora, using direct calls is more straightforward, although some components such Excel, that have been designed to work with scripting languages, have many optional variant parameters that you can omit when using a language that supports automation but that, when using direct calls, you have to pass a variant of type VT_ERROR filled with the constant value DISP_E_PARAMNOTFOUND.

J B Wood (Zumwalt)


Mike Stefanik

Quote from: JosÃÆ'Ã,© Roca on November 05, 2006, 12:11:56 PM
The only difference between late binding and early binding is that, when using late binding, an additional call to the GetIDsOfNames method should be performed to retrieve the DispID (dispatch identifier) of the method or property before calling Invoke. Compilers supporting Automation natively retrieve the DispID at compile file and hardcode it in the call to Invoke.

The other difference is the use of Invoke, rather than calling the method directly through the vtable interface. And technically speaking, a compiler that hardcodes the dispatch ID (i.e.: resolves it at compile time and just stores the ID, rather than resolving the method name at runtime) is behaving incorrectly. With late binding, the only thing that the compiler should store is a table of the method names; the dispatch IDs should always be determined dynamically. In other words, with late binding the object should be treated in exactly the same way that it would be in a scriptable environment.
Mike Stefanik
www.catalyst.com
Catalyst Development Corporation

JR

November 05, 2006, 01:46:35 PM #17 Last Edit: November 05, 2006, 01:49:41 PM by José Roca
Quote
The other difference is the use of Invoke, rather than calling the method directly through the vtable interface.

I was talking of the difference betweel early and late binding, not between late binding and direct calls.

Quote
And technically speaking, a compiler that hardcodes the dispatch ID (i.e.: resolves it at compile time and just stores the ID, rather than resolving the method name at runtime) is behaving incorrectly.

With early binding, the compiler can check if your call is correct because it knows about the parameters and also provides better performance not only because doesn't need to call GetIDsOfNames, but because having checked the correctness of the syntax and the parameters doesn't need to call VariantChangeType to coerce the passed values. However, most implementations of Automation rely on DispInvoke, so this last optimization isn't performed. Only components that have dynamic properties need late binding.

Quote
With late binding, the only thing that the compiler should store is a table of the method names; the dispatch IDs should always be determined dynamically. In other words, with late binding the object should be treated in exactly the same way that it would be in a scriptable environment.

Yes, of course. I think that you are misunderstanding me or that I haven't explained it well. The compiler hardcodes the DispIDs when using early binding.

J B Wood (Zumwalt)

I just need something that works.
All this is great info, but gets me nowhere :)
I am not educated enough.

sapero

November 06, 2006, 04:11:52 AM #19 Last Edit: November 11, 2006, 02:25:47 PM by sapero
Following Paul's suggestion, the first version is here ;D
For the demo I've used windows media player, assuming you have any standard(directshow) audio or video file.
The main methods are:
Create(wstring *progID) - creates the object from registered ProgID like "somelibrary.listview"
CreateFromResult(CInvoker *from) - creates the object from result of a call from other CInvoker instance.
Can be explained as CInvoker1 = CInvoker2.get(objectX)
GetInterfaceName() - usefull to search for methods in typelibrary browser
get(wstring *property,opt void *ppValue=NULL) - get a property
put(...) - put a property (wstring propertyname, ? value)
invoke(...) - call a method (wstring methodname, ...)

Why put and invoke are cdecl? cdecl calling conversion allows to pass any type of value in proper size without (de)referencing pointers etc.
So put(L"int", 6) or put(L"double", 1.2) or put(L"name", L"blah") will always work correctly.

Number and types of arguments are retrieved from object typelibrary, so you must know how many arguments the method requires.
Method get and invoke are storing return value in a VARIANT class variable - you can access this variable and read the type and value.

Method get can return this value directly by reference - get(L"duration", &double_duration), but you must know what the property returns.
Some types are extra handled here to copy exact number of bytes: byte,word,int,double,int64 and wstring
A bool is always converted to/from ole VARIANT_TRUE/VARIANT_FALSE to standard bool (also int).
A wstring is converted to/from BSTR, if you call get where BSTR is returned - be sure to set the second argument to a wstring buffer (or NULL)
All other types are defaulted to 4-byte in size.
Not all VARIANT types are correctly handled here (BRECORD or DECIMAL)
#include "CInvoker.inc"
#typedef HWND int
#typedef HRESULT int
#use "atl.lib"

// the file to be played
#define VIDEO_PATH L"file:///G:\video\rtl animals.avi" // or mp3,wav

declare import, AtlAxAttachControl(IUnknown* pControl, HWND hWnd, IUnknown* ppUnkContainer),HRESULT;
declare import, AtlAxWinInit(),BOOL;

class CWnd : CWindow
{
declare virtual OnClose() {Destroy();}
}



//------------------------------------------------------
sub main()
{
CWnd win;

CInvoker player;
CInvoker controls;
CInvoker media;
CInvoker settings;

// Create accepts progID like "MSDataListLib.DataCombo" or "MSComctlLib.ListViewCtrl"
if (!player.Create(L"WMPlayer.OCX"))
MessageBox(0, "failed to create object!", "", 16)
else
{
win.Create(0,0,800,600,0x10CF0040,0, "MediaPlayer demo - " + player.GetInterfaceName(),NULL);
AtlAxWinInit();
AtlAxAttachControl(player.m_object, win.m_hwnd, NULL);

player.get(L"controls");
controls.CreateFromResult(player); // grab IWMPControls from player.m_retval

player.get(L"settings");
settings.CreateFromResult(player); // grab IWMPSettings from player.m_retval

settings.put(L"autoStart", TRUE); // FALSE works too

if (!player.put(L"URL",VIDEO_PATH))
MessageBox(win,"failed to open clip", "");

if (player.get(L"URL"))
MessageBox(win,sprint("url:",player.GetResultAsString()), "");


controls.invoke(L"play"); // pause,stop,fastForward,fastReverse ...

player.get(L"currentMedia");
media.CreateFromResult(player); // grab IWMPMedia from player.m_retval

int w,h;
double duration;
if media.get(L"imageSourceWidth", &w)
&& media.get(L"imageSourceHeight", &h)
&& media.get(L"duration", &duration)
MessageBox(win,sprint("video size: ",w,"x",h,"\nduration:",duration), "");

while (win.m_hwnd) {wait();}
}
return;
}


invoke and get return values you can convert to string using GetResultAsString()

//edit: added callback mechanism

J B Wood (Zumwalt)

Sweet! Going to give this puppy a try today :) THANKS

Steven Picard

Wow!  I'm going to play around with this when I get a chance.  8)

J B Wood (Zumwalt)

typelibviewer crashes on my machine:

AppName: typelibviewer.exe    AppVer: 0.0.1.705    ModName: user32.dll
ModVer: 5.1.2600.2622    Offset: 0001204f

Mike Stefanik

November 06, 2006, 09:49:45 AM #23 Last Edit: November 06, 2006, 09:53:12 AM by Mike Stefanik
I have the same issue, it crashes shortly after it loads. Have you tried the latest version of the typelib browser from JosÃÆ'Ã,©?

http://www.forum.it-berater.org/index.php?topic=420.0
Mike Stefanik
www.catalyst.com
Catalyst Development Corporation

sapero

The exe was compressed with older version of pecompact2, maybe it was the crash issue. On my two XP it was running clean as packed and unpacked version.
I've changed it to unpacked and added source. This tool uses scintilla too ;D
Yes, i know JosÃÆ'Ã,©'s browser and have the latest version, but as every programmer will my own.
After running it does only enumerate all controls and typelibs from the registry.

Added: callback mechanism for controls:
class CWMPEvents : CInvoker
{
// [...] defined all virtual methods
}

CWMPEvents::OnClick(word nButton, word nShiftState, int fX, int fY)
{
MessageBox(0,"OnClick","");
}

sub main()
{
CWMPEvents player;
player.Create(L"WMPlayer.OCX");
player.Advise();
...
}


The Advise method assumes that 'player' instance  implements all methods for current control declared as virtual methods.
All here goes automatically: events guid is requested from typelibrary, eventsink is connected, IDispatch converts Invoke calls to your methods.
IDispatch::Invoke has been renamed to Invoke2 :)
Only VARIANT_TRUE and VARIANT_FALSE are converted to BOOL, BSTR is pushed unchanged, but you can use it as wstring.

In CWMPEvents.inc you can find all event classes for WMP, only one class is "created" - CWMPEvents

Compile this new example (and change video path), then click the player :)