digitalmars.D - (help) automation on windows
- Carlos Santander B. (48/48) Mar 09 2005 I fond some reference on how to do late binding and automation (it's
- John C (4/52) Mar 09 2005 If you create a WebBrowser object instead, with IID_IWebBrowser2, you sh...
- Carlos Santander B. (7/13) Mar 09 2005 I guess I should've said that: it's supposed to be like that. I mean, if...
- John C (9/22) Mar 09 2005 Could this line be the source of the problem?
- Carlos Santander B. (8/23) Mar 09 2005 Thanks, that worked. Partially. "Invoke" returns -1073741819. It doesn't...
- John C (25/46) Mar 10 2005 Conveniently, IDispatch has a GetTypeInfo method that returns an ITypeIn...
- Carlos Santander B. (4/41) Mar 10 2005 It works! Thanks, again!
- Carlos Santander B. (51/54) Mar 11 2005 (progress update and another request for help)
- John C (14/68) Mar 12 2005 Documentation on this stuff is sparse (as you've no doubt gathered). But...
- Carlos Santander B. (39/57) Mar 12 2005 Thanks. I'll try that to see how it goes. The problem (still) is being
- John C (80/135) Mar 13 2005 After a fair amount of trial and error, I've got it working.
- Carlos Santander B. (6/114) Mar 13 2005 SysAllocString did the trick. Thanks! Uh, and using 1 instead of -1 does...
- John C (23/26) Mar 13 2005 And by clever use of subclassing, the syntax you proposed in your initia...
- =?iso-8859-1?Q?Robert_M=2E_M=FCnch?= (11/13) Apr 07 2005 Hi, I'm just trying to play using D (version 0.120) for COM
- jicman (3/151) Mar 13 2005 Will you zip these files and post them again? Sorry and thanks.
- jicman (152/206) Mar 12 2005 Carlos,
- Carlos Santander B. (4/14) Mar 12 2005 Thanks, I think that could work as a test case.
- Petr (1/1) Apr 19 2010 Deleaker ( http://deleaker.com/ ) is the best tool for memory leaks dete...
- J C Calvarese (11/31) Mar 12 2005 Not that I should receive any credit for the code which I blantantly
- Carlos Santander B. (4/13) Mar 12 2005 Thanks. "The key is in the details" (or is the problem, or the issue?)
- jicman (6/36) Mar 13 2005 Which I, purposely, grab from a Microsoft site. Though, I did have to s...
I fond some reference on how to do late binding and automation (it's cool when you know the right terms... at least I hope they're right...), so I have this D code right now: //---------------------------------------- void main() { wchar * prog="InternetExplorer.Application"; wchar * member="Visible"; LCID defaultLCID = GetUserDefaultLCID(); HRESULT hr = CoInitialize(null); printf("1:%d\n",hr); CLSID bu; hr=CLSIDFromProgID(prog, &bu); printf("2:%d\n",hr); IDispatch* pIDispatch; hr = CoCreateInstance(&bu, null, CLSCTX_SERVER, &IID_IDispatch,cast (void**)&pIDispatch); printf("3:%d\n",hr); DISPID dispid; hr = pIDispatch.GetIDsOfNames(cast(REFIID) &IID_NULL, &member, 1, defaultLCID, &dispid); printf("4:%d\n",hr); VARIANTARG myArg; myArg.n1.n2.vt = VT_I4|VT_BYREF; myArg.n1.n2.n3.lVal = 1; DISPPARAMS param; param.cArgs=1; param.rgvarg=&myArg; VARIANT * result=new VARIANT; hr = pIDispatch.Invoke(dispid, cast(REFIID) &IID_NULL, defaultLCID, DISPATCH_METHOD, ¶m, result, null, null); printf("5:%d\n",hr); //Sleep(2000); CoUninitialize(); } //---------------------------------------- The idea is to do something like (pseudo-code): app=new InternetExplorer.Application(); app.Visible=1; //or app.Visible(1), I don't know But the call to GetIDsOfNames (between printfs 3 and 4) produces an access violation. Any ideas? The C++ version does a bit better: the Invoke call returns -2147024809, whatever that means. (BTW, I'm using the core32 library for all that IDispatch stuff, even if the one I have is a bit outdated) _______________________ Carlos Santander Bernal
Mar 09 2005
"Carlos Santander B." <csantander619 gmail.com> wrote in message news:d0nuj0$eds$1 digitaldaemon.com...I fond some reference on how to do late binding and automation (it's cool when you know the right terms... at least I hope they're right...), so I have this D code right now: //---------------------------------------- void main() { wchar * prog="InternetExplorer.Application"; wchar * member="Visible"; LCID defaultLCID = GetUserDefaultLCID(); HRESULT hr = CoInitialize(null); printf("1:%d\n",hr); CLSID bu; hr=CLSIDFromProgID(prog, &bu); printf("2:%d\n",hr); IDispatch* pIDispatch; hr = CoCreateInstance(&bu, null, CLSCTX_SERVER, &IID_IDispatch,cast (void**)&pIDispatch); printf("3:%d\n",hr); DISPID dispid; hr = pIDispatch.GetIDsOfNames(cast(REFIID) &IID_NULL, &member, 1, defaultLCID, &dispid); printf("4:%d\n",hr);If you create a WebBrowser object instead, with IID_IWebBrowser2, you should be able to directly call put_Visible(VARIANT_TRUE).VARIANTARG myArg; myArg.n1.n2.vt = VT_I4|VT_BYREF; myArg.n1.n2.n3.lVal = 1; DISPPARAMS param; param.cArgs=1; param.rgvarg=&myArg; VARIANT * result=new VARIANT; hr = pIDispatch.Invoke(dispid, cast(REFIID) &IID_NULL, defaultLCID, DISPATCH_METHOD, ¶m, result, null, null); printf("5:%d\n",hr); //Sleep(2000); CoUninitialize(); } //---------------------------------------- The idea is to do something like (pseudo-code): app=new InternetExplorer.Application(); app.Visible=1; //or app.Visible(1), I don't know But the call to GetIDsOfNames (between printfs 3 and 4) produces an access violation. Any ideas? The C++ version does a bit better: the Invoke call returns -2147024809, whatever that means. (BTW, I'm using the core32 library for all that IDispatch stuff, even if the one I have is a bit outdated) _______________________ Carlos Santander Bernal
Mar 09 2005
John C wrote:If you create a WebBrowser object instead, with IID_IWebBrowser2, you should be able to directly call put_Visible(VARIANT_TRUE).I guess I should've said that: it's supposed to be like that. I mean, if later I want to replace "InternetExplorer.Application" with something else, that's all I should change, without having to worry about GUIDs. Same for "Visible". The goal is late binding. But thanks, though. _______________________ Carlos Santander Bernal
Mar 09 2005
"Carlos Santander B." <csantander619 gmail.com> wrote in message news:d0o2ht$i82$1 digitaldaemon.com...John C wrote:Could this line be the source of the problem? IDispatch* pIDispatch; Change it to IDispatch pIDispatch; It worked for me. By the way, when you call Invoke, try using "DISPATCH_PROPERTYPUT" instead of "DISPATCH_METHOD". Hope this helps.If you create a WebBrowser object instead, with IID_IWebBrowser2, you should be able to directly call put_Visible(VARIANT_TRUE).I guess I should've said that: it's supposed to be like that. I mean, if later I want to replace "InternetExplorer.Application" with something else, that's all I should change, without having to worry about GUIDs. Same for "Visible". The goal is late binding. But thanks, though. _______________________ Carlos Santander Bernal
Mar 09 2005
John C wrote:Could this line be the source of the problem? IDispatch* pIDispatch; Change it to IDispatch pIDispatch; It worked for me. By the way, when you call Invoke, try using "DISPATCH_PROPERTYPUT" instead of "DISPATCH_METHOD". Hope this helps.Thanks, that worked. Partially. "Invoke" returns -1073741819. It doesn't matter if I use DISPATCH_METHOD or DISPATCH_PROPERTYPUT. Regarding that, is there a way to know (automagically, programmatically) when to use DISPATCH_PROPERTYPUT, DISPATCH_METHOD, DISPATCH_PROPERTYGET or DISPATCH_PROPERTYPUTREF? Maybe by the result of calling GetIDsOfNames? _______________________ Carlos Santander Bernal
Mar 09 2005
"Carlos Santander B." <csantander619 gmail.com> wrote in message news:d0o9va$pgh$1 digitaldaemon.com...John C wrote:Conveniently, IDispatch has a GetTypeInfo method that returns an ITypeInfo interface. ITypeInfo::GetFuncDesc returns a FUNCDESC structure containing an INVOKEKIND enumeration, which is defined as follows: typedef enum tagINVOKEKIND { INVOKE_FUNC = DISPATCH_METHOD, INVOKE_PROPERTYGET = DISPATCH_PROPERTYGET, INVOKE_PROPERTYPUT = DISPATCH_PROPERTYPUT, INVOKE_PROPERTYPUTREF = DISPATCH_PROPERTYPUTREF } INVOKEKIND; That looks like what you need. Or you could try calling Invoke() with each flag in turn until you get S_OK. If you don't get S_OK after trying all flags, then throw an exception saying the method or property doesn't exist. I think this how scripting languages do late binding for automation objects. By the way, MSDN says "when you use IDispatch::Invoke() with DISPATCH_PROPERTYPUT you have to specially initialize the cNamedArgs and rgdispidNamedArgs elements of your DISPPARAMS structure with the following: DISPID dispidNamed = DISPID_PROPERTYPUT; dispparams.cNamedArgs = 1; dispparams.rgdispidNamedArgs = &dispidNamed;" I found an example on Microsoft's support site (it's for Excel, but you can adapt it for other automation objects): http://support.microsoft.com/kb/181473/EN-US/Could this line be the source of the problem? IDispatch* pIDispatch; Change it to IDispatch pIDispatch; It worked for me. By the way, when you call Invoke, try using "DISPATCH_PROPERTYPUT" instead of "DISPATCH_METHOD". Hope this helps.Thanks, that worked. Partially. "Invoke" returns -1073741819. It doesn't matter if I use DISPATCH_METHOD or DISPATCH_PROPERTYPUT. Regarding that, is there a way to know (automagically, programmatically) when to use DISPATCH_PROPERTYPUT, DISPATCH_METHOD, DISPATCH_PROPERTYGET or DISPATCH_PROPERTYPUTREF? Maybe by the result of calling GetIDsOfNames?_______________________ Carlos Santander Bernal
Mar 10 2005
John C wrote:Conveniently, IDispatch has a GetTypeInfo method that returns an ITypeInfo interface. ITypeInfo::GetFuncDesc returns a FUNCDESC structure containing an INVOKEKIND enumeration, which is defined as follows: typedef enum tagINVOKEKIND { INVOKE_FUNC = DISPATCH_METHOD, INVOKE_PROPERTYGET = DISPATCH_PROPERTYGET, INVOKE_PROPERTYPUT = DISPATCH_PROPERTYPUT, INVOKE_PROPERTYPUTREF = DISPATCH_PROPERTYPUTREF } INVOKEKIND; That looks like what you need. Or you could try calling Invoke() with each flag in turn until you get S_OK. If you don't get S_OK after trying all flags, then throw an exception saying the method or property doesn't exist. I think this how scripting languages do late binding for automation objects. By the way, MSDN says "when you use IDispatch::Invoke() with DISPATCH_PROPERTYPUT you have to specially initialize the cNamedArgs and rgdispidNamedArgs elements of your DISPPARAMS structure with the following: DISPID dispidNamed = DISPID_PROPERTYPUT; dispparams.cNamedArgs = 1; dispparams.rgdispidNamedArgs = &dispidNamed;" I found an example on Microsoft's support site (it's for Excel, but you can adapt it for other automation objects): http://support.microsoft.com/kb/181473/EN-US/It works! Thanks, again! _______________________ Carlos Santander Bernal_______________________ Carlos Santander Bernal
Mar 10 2005
Carlos Santander B. wrote:It works! Thanks, again!(progress update and another request for help) PROPERTYGET and PROPERTYPUT work. PROPERTYPUTREF, I don't know, because I haven't tried and I don't know how to try. METHOD without parameters also works. With parameters, however, it's not as good. So far, this code works (adapted from Justin's code in http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/18805, I hope the URL is ok): //---------------------------------------------------------------- AXO ie=new AXO("InternetExplorer.Application"); try { VARIANTARG a1, a2, a3, a4; a1.n1.n2.vt=20; a2.n1.n2.vt=6008; a3.n1.n2.vt=0; a4.n1.n2.vt=17; VARIANTARG myArg; myArg.n1.n2.vt = VT_BSTR; myArg.n1.n2.n3.bstrVal = "about:blank"; ie.call("Navigate",a4,a3,a2,a1,myArg); myArg.n1.n2.vt = VT_UI4; myArg.n1.n2.n3.lVal = 850; ie.set("Width",myArg); myArg.n1.n2.n3.lVal = 710; ie.set("Height",myArg); myArg.n1.n2.n3.lVal = 10; ie.set("Top",myArg); myArg.n1.n2.n3.lVal = 10; ie.set("Left",myArg); myArg.n1.n2.vt = VT_BOOL; myArg.n1.n2.n3.boolVal = 0; ie.set("ToolBar",myArg); ie.set("MenuBar",myArg); ie.set("StatusBar",myArg); myArg.n1.n2.n3.boolVal = 1; ie.set("Visible",myArg); VARIANT tmp = ie.get("Visible"); bool isVisible = cast(bool) tmp.n1.n2.n3.boolVal; } finally ie.call("Quit"); //---------------------------------------------------------------- Works, except for the Navigate call. Going through the parameters I found their VARTYPEs, so that's why I set them, but I have no idea what they should receive. Also, I'm aware some methods are optional (Navigate has 4, I assume the only required is the URL), but only passing one doesn't work either. So, again, some help would be nice. _______________________ Carlos Santander Bernal
Mar 11 2005
"Carlos Santander B." <csantander619 gmail.com> wrote in message news:d0tjun$15ie$1 digitaldaemon.com...Carlos Santander B. wrote:Documentation on this stuff is sparse (as you've no doubt gathered). But if I remember correctly, the parameters for Navigate() are as follows: url [VT_BSTR, bstrVal] flags [VT_UI4, lVal (see BrowserNavConstants)] targetFrameName [VT_BSTR, bstrVal] postData [VT_ARRAY | VT_VARIANT of VT_UI1, parray] headers [VT_BSTR, bstrVal] cancel [VT_BOOL | VT_BYREF, pboolVal] (Note that "cancel" isn't available as a parameter in early binding.) Without seeing the code behind your set() methods, it's difficult to know why it's failing. Your rgvarg array's elements have to be in reverse order, but I think you're doing that anyway.It works! Thanks, again!(progress update and another request for help) PROPERTYGET and PROPERTYPUT work. PROPERTYPUTREF, I don't know, because I haven't tried and I don't know how to try. METHOD without parameters also works. With parameters, however, it's not as good. So far, this code works (adapted from Justin's code in http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/18805, I hope the URL is ok): //---------------------------------------------------------------- AXO ie=new AXO("InternetExplorer.Application"); try { VARIANTARG a1, a2, a3, a4; a1.n1.n2.vt=20; a2.n1.n2.vt=6008; a3.n1.n2.vt=0; a4.n1.n2.vt=17; VARIANTARG myArg; myArg.n1.n2.vt = VT_BSTR; myArg.n1.n2.n3.bstrVal = "about:blank"; ie.call("Navigate",a4,a3,a2,a1,myArg); myArg.n1.n2.vt = VT_UI4; myArg.n1.n2.n3.lVal = 850; ie.set("Width",myArg); myArg.n1.n2.n3.lVal = 710; ie.set("Height",myArg); myArg.n1.n2.n3.lVal = 10; ie.set("Top",myArg); myArg.n1.n2.n3.lVal = 10; ie.set("Left",myArg); myArg.n1.n2.vt = VT_BOOL; myArg.n1.n2.n3.boolVal = 0; ie.set("ToolBar",myArg); ie.set("MenuBar",myArg); ie.set("StatusBar",myArg); myArg.n1.n2.n3.boolVal = 1; ie.set("Visible",myArg); VARIANT tmp = ie.get("Visible"); bool isVisible = cast(bool) tmp.n1.n2.n3.boolVal; } finally ie.call("Quit"); //---------------------------------------------------------------- Works, except for the Navigate call. Going through the parameters I found their VARTYPEs, so that's why I set them, but I have no idea what they should receive. Also, I'm aware some methods are optional (Navigate has 4, I assume the only required is the URL), but only passing one doesn't work either. So, again, some help would be nice._______________________ Carlos Santander Bernal
Mar 12 2005
John C wrote:Documentation on this stuff is sparse (as you've no doubt gathered). But if I remember correctly, the parameters for Navigate() are as follows: url [VT_BSTR, bstrVal] flags [VT_UI4, lVal (see BrowserNavConstants)] targetFrameName [VT_BSTR, bstrVal] postData [VT_ARRAY | VT_VARIANT of VT_UI1, parray] headers [VT_BSTR, bstrVal] cancel [VT_BOOL | VT_BYREF, pboolVal] (Note that "cancel" isn't available as a parameter in early binding.)Thanks. I'll try that to see how it goes. The problem (still) is being generic: I'm just trying with IE, but I want it to be 100% functional with any ActiveX object.Without seeing the code behind your set() methods, it's difficult to know why it's failing. Your rgvarg array's elements have to be in reverse order, but I think you're doing that anyway.set() isn't the problem. call() is: VARIANT call(char [] member,...) { INVOKEKIND ik=INVOKE_FUNC; DISPID dispid = findMember(member,ik); if (!(dispid in methods)) throw new Exception("can only call methods"); VARIANTARG [] myArgs = makeArray(_arguments,_argptr); DISPPARAMS param; param.cArgs=myArgs.length; param.rgvarg=myArgs.ptr; VARIANT result; HRESULT hr = pIDispatch.Invoke(dispid, cast(REFIID) &IID_NULL, defaultLCID, ik, ¶m, &result, null, null); return result; } and makeArray() is: VARIANTARG [] makeArray(TypeInfo [] args, void* ptr) { VARIANTARG [] array; array.length = args.length; for (uint i;i<args.length;++i) { if (args[i] == typeid(VARIANTARG)) array [i] = va_arg!(VARIANTARG)(ptr); else throw new Exception( "Expected arguments of type VARIANTARG" ); } return array; } The problem seems to be after the findMember() call, because calling Quit (no parameters) works fine. _______________________ Carlos Santander Bernal
Mar 12 2005
"Carlos Santander B." <csantander619 gmail.com> wrote in message news:d10aai$12dk$1 digitaldaemon.com...John C wrote:After a fair amount of trial and error, I've got it working. /*------------------------ activex.d -------------------------*/ module test.activex; private import std.stdarg; static this() { CoInitialize(null); } static ~this() { CoUninitialize(); } public class ActiveXObject { private IDispatch disp_; public this(char[] progId) { CLSID clsid; CLSIDFromProgID(toUTF16z(progId), &clsid); CoCreateInstance(&clsid, null, CLSCTX_SERVER, &IID_IDispatch, (void**)&_disp); } public VARIANT call(char[] member, ...) { DISPID memberId = findMember(member); VARIANT[] args = makeArray(_arguments, _argptr); DISPPARAMS params; params.rgvarg = agrs.ptr; params.cArgs = args.length; VARIANT result; disp_.Invoke(memberId, &IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, ¶ms, &result, null, null); return result; } public void set(wchar[] member, VARIANT value) // omitted public VARIANT get(wchar[] member) // omitted private DISPID findMember(char[] member) { // Simplified version wchar* pszMember = toUTF16z(member); DISPID dispidResult; disp_.GetIDsOfNames(&IID_NULL, &pszMember, 1, GetUserDefaultLCID(), &dispidResult); return dispidResult; } private VARIANT[] makeArray(TypeInfo[] argTypes, void* ptr) { int argsLength = argTypes.length; VARIANT[] args; foreach (TypeInfo argType; argTypes) { if (argType == typeid(VARIANT)) args ~= va_arg!(VARIANT)(ptr); } return args; } } /*-------------------------- test.d -------------------------*/ module test.app; private import test.activex; int main() { ActiveXObject axObject= new ActiveXObject("InternetExplorer.Application"); VARIANT variantTrue; variantTrue.vt = VT_BOOL; variantTrue.boolVal = -1; // VARIANT_TRUE VARIANT variantFalse; variantFalse.vt = VT_BOOL; variantFalse.boolVal = 0; // VARIANT_FALSE axObject.set("ToolBar", variantFalse); axObject.set("Visible", variantTrue); VARIANT url; url.vt = VT_BSTR; // Must call SysAllocString() to allocate a BSTR url.bstrVal = SysAllocString("about:blank"); axObject.call("Navigate", VARIANT.init, VARIANT.init, VARIANT.init, VARIANT.init, url); // Now free the BSTR SysFreeString(url.bstrVal); return 0; } I can't see that you're doing anything radically different, apart from using the wrong value for VARIANT_TRUE and not allocating your BSTRs with SysAllocString(). If you're still having problems after trying it, let me know and I'll send you my source files. Good luck.Documentation on this stuff is sparse (as you've no doubt gathered). But if I remember correctly, the parameters for Navigate() are as follows: url [VT_BSTR, bstrVal] flags [VT_UI4, lVal (see BrowserNavConstants)] targetFrameName [VT_BSTR, bstrVal] postData [VT_ARRAY | VT_VARIANT of VT_UI1, parray] headers [VT_BSTR, bstrVal] cancel [VT_BOOL | VT_BYREF, pboolVal] (Note that "cancel" isn't available as a parameter in early binding.)Thanks. I'll try that to see how it goes. The problem (still) is being generic: I'm just trying with IE, but I want it to be 100% functional with any ActiveX object.Without seeing the code behind your set() methods, it's difficult to know why it's failing. Your rgvarg array's elements have to be in reverse order, but I think you're doing that anyway.set() isn't the problem. call() is: VARIANT call(char [] member,...) { INVOKEKIND ik=INVOKE_FUNC; DISPID dispid = findMember(member,ik); if (!(dispid in methods)) throw new Exception("can only call methods"); VARIANTARG [] myArgs = makeArray(_arguments,_argptr); DISPPARAMS param; param.cArgs=myArgs.length; param.rgvarg=myArgs.ptr; VARIANT result; HRESULT hr = pIDispatch.Invoke(dispid, cast(REFIID) &IID_NULL, defaultLCID, ik, ¶m, &result, null, null); return result; } and makeArray() is: VARIANTARG [] makeArray(TypeInfo [] args, void* ptr) { VARIANTARG [] array; array.length = args.length; for (uint i;i<args.length;++i) { if (args[i] == typeid(VARIANTARG)) array [i] = va_arg!(VARIANTARG)(ptr); else throw new Exception( "Expected arguments of type VARIANTARG" ); } return array; } The problem seems to be after the findMember() call, because calling Quit (no parameters) works fine.
Mar 13 2005
John C wrote:/*------------------------ activex.d -------------------------*/ module test.activex; private import std.stdarg; static this() { CoInitialize(null); } static ~this() { CoUninitialize(); } public class ActiveXObject { private IDispatch disp_; public this(char[] progId) { CLSID clsid; CLSIDFromProgID(toUTF16z(progId), &clsid); CoCreateInstance(&clsid, null, CLSCTX_SERVER, &IID_IDispatch, (void**)&_disp); } public VARIANT call(char[] member, ...) { DISPID memberId = findMember(member); VARIANT[] args = makeArray(_arguments, _argptr); DISPPARAMS params; params.rgvarg = agrs.ptr; params.cArgs = args.length; VARIANT result; disp_.Invoke(memberId, &IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, ¶ms, &result, null, null); return result; } public void set(wchar[] member, VARIANT value) // omitted public VARIANT get(wchar[] member) // omitted private DISPID findMember(char[] member) { // Simplified version wchar* pszMember = toUTF16z(member); DISPID dispidResult; disp_.GetIDsOfNames(&IID_NULL, &pszMember, 1, GetUserDefaultLCID(), &dispidResult); return dispidResult; } private VARIANT[] makeArray(TypeInfo[] argTypes, void* ptr) { int argsLength = argTypes.length; VARIANT[] args; foreach (TypeInfo argType; argTypes) { if (argType == typeid(VARIANT)) args ~= va_arg!(VARIANT)(ptr); } return args; } } /*-------------------------- test.d -------------------------*/ module test.app; private import test.activex; int main() { ActiveXObject axObject= new ActiveXObject("InternetExplorer.Application"); VARIANT variantTrue; variantTrue.vt = VT_BOOL; variantTrue.boolVal = -1; // VARIANT_TRUE VARIANT variantFalse; variantFalse.vt = VT_BOOL; variantFalse.boolVal = 0; // VARIANT_FALSE axObject.set("ToolBar", variantFalse); axObject.set("Visible", variantTrue); VARIANT url; url.vt = VT_BSTR; // Must call SysAllocString() to allocate a BSTR url.bstrVal = SysAllocString("about:blank"); axObject.call("Navigate", VARIANT.init, VARIANT.init, VARIANT.init, VARIANT.init, url); // Now free the BSTR SysFreeString(url.bstrVal); return 0; } I can't see that you're doing anything radically different, apart from using the wrong value for VARIANT_TRUE and not allocating your BSTRs with SysAllocString(). If you're still having problems after trying it, let me know and I'll send you my source files. Good luck.SysAllocString did the trick. Thanks! Uh, and using 1 instead of -1 does work. But thanks again! It's funny that your activex module is basically identical to what I have. _______________________ Carlos Santander Bernal
Mar 13 2005
"Carlos Santander B." <csantander619 gmail.com> wrote in message news:d11n8k$2cd2$1 digitaldaemon.com...SysAllocString did the trick. Thanks! Uh, and using 1 instead of -1 does work. But thanks again! It's funny that your activex module is basically identical to what I have.And by clever use of subclassing, the syntax you proposed in your initial post is almost possible: public final abstract class InternetExplorer { public final class Application : ActiveXObject { public this() { super("InternetExplorer.Application"); } } } public final abstract class Excel { public final class Application : ActiveXObject { public this() { super("Excel.Application"); } } } ActiveXObject ieObject = new InternetExplorer.Application; ActiveXObject excelObject = new Excel.Application; Just make the ActiveXObject class abstract and its constructor protected, and you've got a fairly safe idiom to guard users from creating invalid objects ("Notepad.Application" etc, which doesn't exist).
Mar 13 2005
On Sun, 13 Mar 2005 20:38:42 +0100, John C <johnch_atms hotmail.com> wrote:And by clever use of subclassing, the syntax you proposed in your initial post is almost possible:Hi, I'm just trying to play using D (version 0.120) for COM programming. Can someone please send a complete working example? Or send me a ZIP file? That would be great!! I tried fiddling around with all pieces here but didn't made it yet. Thanks a lot. -- Robert M. Münch Management & IT Freelancer http://www.robertmuench.de
Apr 07 2005
Will you zip these files and post them again? Sorry and thanks. jic John C says..."Carlos Santander B." <csantander619 gmail.com> wrote in message news:d10aai$12dk$1 digitaldaemon.com...John C wrote:After a fair amount of trial and error, I've got it working. /*------------------------ activex.d -------------------------*/ module test.activex; private import std.stdarg; static this() { CoInitialize(null); } static ~this() { CoUninitialize(); } public class ActiveXObject { private IDispatch disp_; public this(char[] progId) { CLSID clsid; CLSIDFromProgID(toUTF16z(progId), &clsid); CoCreateInstance(&clsid, null, CLSCTX_SERVER, &IID_IDispatch, (void**)&_disp); } public VARIANT call(char[] member, ...) { DISPID memberId = findMember(member); VARIANT[] args = makeArray(_arguments, _argptr); DISPPARAMS params; params.rgvarg = agrs.ptr; params.cArgs = args.length; VARIANT result; disp_.Invoke(memberId, &IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, ¶ms, &result, null, null); return result; } public void set(wchar[] member, VARIANT value) // omitted public VARIANT get(wchar[] member) // omitted private DISPID findMember(char[] member) { // Simplified version wchar* pszMember = toUTF16z(member); DISPID dispidResult; disp_.GetIDsOfNames(&IID_NULL, &pszMember, 1, GetUserDefaultLCID(), &dispidResult); return dispidResult; } private VARIANT[] makeArray(TypeInfo[] argTypes, void* ptr) { int argsLength = argTypes.length; VARIANT[] args; foreach (TypeInfo argType; argTypes) { if (argType == typeid(VARIANT)) args ~= va_arg!(VARIANT)(ptr); } return args; } } /*-------------------------- test.d -------------------------*/ module test.app; private import test.activex; int main() { ActiveXObject axObject= new ActiveXObject("InternetExplorer.Application"); VARIANT variantTrue; variantTrue.vt = VT_BOOL; variantTrue.boolVal = -1; // VARIANT_TRUE VARIANT variantFalse; variantFalse.vt = VT_BOOL; variantFalse.boolVal = 0; // VARIANT_FALSE axObject.set("ToolBar", variantFalse); axObject.set("Visible", variantTrue); VARIANT url; url.vt = VT_BSTR; // Must call SysAllocString() to allocate a BSTR url.bstrVal = SysAllocString("about:blank"); axObject.call("Navigate", VARIANT.init, VARIANT.init, VARIANT.init, VARIANT.init, url); // Now free the BSTR SysFreeString(url.bstrVal); return 0; } I can't see that you're doing anything radically different, apart from using the wrong value for VARIANT_TRUE and not allocating your BSTRs with SysAllocString(). If you're still having problems after trying it, let me know and I'll send you my source files. Good luck.Documentation on this stuff is sparse (as you've no doubt gathered). But if I remember correctly, the parameters for Navigate() are as follows: url [VT_BSTR, bstrVal] flags [VT_UI4, lVal (see BrowserNavConstants)] targetFrameName [VT_BSTR, bstrVal] postData [VT_ARRAY | VT_VARIANT of VT_UI1, parray] headers [VT_BSTR, bstrVal] cancel [VT_BOOL | VT_BYREF, pboolVal] (Note that "cancel" isn't available as a parameter in early binding.)Thanks. I'll try that to see how it goes. The problem (still) is being generic: I'm just trying with IE, but I want it to be 100% functional with any ActiveX object.Without seeing the code behind your set() methods, it's difficult to know why it's failing. Your rgvarg array's elements have to be in reverse order, but I think you're doing that anyway.set() isn't the problem. call() is: VARIANT call(char [] member,...) { INVOKEKIND ik=INVOKE_FUNC; DISPID dispid = findMember(member,ik); if (!(dispid in methods)) throw new Exception("can only call methods"); VARIANTARG [] myArgs = makeArray(_arguments,_argptr); DISPPARAMS param; param.cArgs=myArgs.length; param.rgvarg=myArgs.ptr; VARIANT result; HRESULT hr = pIDispatch.Invoke(dispid, cast(REFIID) &IID_NULL, defaultLCID, ik, ¶m, &result, null, null); return result; } and makeArray() is: VARIANTARG [] makeArray(TypeInfo [] args, void* ptr) { VARIANTARG [] array; array.length = args.length; for (uint i;i<args.length;++i) { if (args[i] == typeid(VARIANTARG)) array [i] = va_arg!(VARIANTARG)(ptr); else throw new Exception( "Expected arguments of type VARIANTARG" ); } return array; } The problem seems to be after the findMember() call, because calling Quit (no parameters) works fine.
Mar 13 2005
Carlos, I am very interested in this project. Here is a JScript program that does a lot with COMs. It uses IE, Excel and MySQL COM calls. I wish I can help you more but I am out of the country on business. I will keep on watching the posts on this subject. Thanks. josé Carlos Santander B. says...Carlos Santander B. wrote:begin 0644 COMobjectsSample.zip M92YJ<^P]:W/;MK+?.Y/_ '"FQU)%2Y9LQTYD>4;QHW5/;*>1;MW>.*=#D9 MAB)U^(BLIOGO=Q</$N!+LI-TSKVW=.V0P.YBL=A=[((`V_ENZ_.O)]^,""$) M3H=G;Q;QA>_&C>:3;SX^^08:G;E1>^)8,856!\3H[>SL[AP:_;3.]=VIZU&L M.PDIP%T&?CSS5D`(ZQ#RD]($AX$FS &I`0"RH0]62*8^D($R2;2?5D10$4Q^ M\,T;:NL]OK+FK$M*1P&&NA^H'!W"Y+&3U3/J:FVN?LB:>^U9OB_!M/HS_P[8 MIJ'KWY7BC\:C:Z)?6$\Z'3**K3A!$:%P2!`2R6N*?'U^D</-$0^\!-D[=:.% M%=LSX%"O3R9S-X[5_FGUK.$:^N, MKS>*5<CM5X=B9\LWQEN,A11RHW6B,U' M):/*D0%F/*I4;=+ES3`>4B/DX.,1**3E,$V74"&-D]!G1L5*/N$?ZD54:;73 MB8.+L_;T[B3P0%%`KB%UC+Y6N40;\GQ9UC"N`M%W["1AYKZT(C(-$M]I$W+C M>AZQF6&3P*?$BD'2E%"H/)J$QT:S7]7VQ+/L]T8U^_FA.;MQ?2=8-E2G,79C M[GZ`1?(JN`/E7P1AS#T--+.-F.1GRTMH9/21"X$23,G%&5DRBI*8Z\<GT*N8 M0LL M GE5*9!R-%0<E`2DCF-(NN.1FTR]**`1-`)8-<!`_6L%< X#MU)$M.( M,-R\#P M$$,OOW&=>,:&94`.]]6:'ZA[-XMYS4%7K7E%IS$1.%K%.%B07$6G,P*Y1P&X MQ:BWQP9I"0UN$>.H(PK7X4;Q"C"0L\&M$8.7Z=A1=&O4(V)-PY $SHI\1)QM MRW/O_!?$`Q'V02)^O#VUYJZW>D'L`.R`AJ(T<O^ +\CS1?QI+6,=QMGZ#G1J MB-QD2['P2;?53NEU&-A X<(QW1I]P)=T+(^&<>/6X'5 RS9=P&S7OH7>I\U] M2N]`A(QGX+A>B%M'R#SQ848 MH]<!US4%OQS-L&HYPPF P1N9)-&J23Z297OD4;IH$` 0 <E/PGAFP1+P,YY^ MX7LPV)^MT+6 U:B!\X^9"SI/QV2`]X1LD:/%\187*CZ1,^;ED;,Y!K4HAI"/ M(]`_^A6NR\MCDF$<N?XB$1K%%,H0LF9SMT'0* 9&=\^`AF%D!\96*YW96UO& M<04EKG>2EGS* -7RG &\V>!SWLL2KJ]&!B[:YB&=<4Q&,%%S-53:[R`:]`V$ MBD5J7-`X'7/I"T5 =4SCVQ&U0GO6,)09MTF.R8ZJ(7N9 N3"&D9(J"4O;S-> M86BM&COF;M?L'>+?W1WEK_;(&\<8>15^NT<&`^2?$WO;>P?T>L_[:1OP>)7, M+0I7*U9(NKT7.SMD>&F4M'#F.Z7DL M4`NC8A`E%G4PDW.LM]UWC$]D'9YZ[,D43_M:W>Z[?HG2&RP',1U'DQ5_S`T$ MF,`/8C*S8+*T_!7!R`C&-&JWVUFCMFVGTVSJ49YRPD/4N>LIJ`*UYDT.(?CE MR%5L2)B\C+>WV2`PM)8ALCZ("7T(F(4#EZ0!8XXJC;!RW$TC(XU"93!O]]ZA M*8;:OJ,Q-[AFJZM"K%2(<^#Q5W MYSMYIZ\ X:6NC^'%U^[DD[92)PN5Y3E9A"MR\O[Z_"(M+JREI35RW2K% C&4 M]]FZE"S!M5E^AW8SNL$^G+!Y[<8**;FADZQ'&<2Y%RQ?0Y21U>'`-%!G!LL5 M^<<_F'8-R!*F"9A`&\O58&"S\N4<BNUY4]&*4SI)[LZ%B]3TPV22-T<W)C : M0Q?4K)XDUUTBE+>&DJ;DZVCR_+.&FF(=];2N8`;[?+F!V7PVD5-JXQ3T:,70 M([H&M5P#RBLD M8ZV 5L<Y=M=SJ,L I30_S E\/:VT5YH6?ETGWVJ9O6HWSQ I>^, 4[,3R]^* M<1G)8:'BU,?0O4T :HY%<B;A18:6$L3472F3V8X(8?J%,)Z]1]+C>%;TUP7R M]>'S9T7G&T7>$GB34'ZSF%SXSL-WY"GSG1[5)H2<07' P?%[[4N8L,H_D"/ ML7UNA'C,+AO*`OSLI:X^,FF,3[;UX2CC-66U?$S6A\?U`?)G><^\_]3X?T"4 M_/`X&2]=6C41\KH()4\J* E2- I3\H3L?*12'JN4QP3Y>*40%%3%+`7`/%M! M++4^U$GY^:HQ3ZY41#VIB^_K84\NA L\\X+UYP9N<?W#M/W8A'Z6]$I;S&9M MKB0832:+!87($I+6N2_+P[0\Y*]4<8T9 6$2S`-[85H>9EMBAEU0-ZC8(L.7 M`". ^B_0"K1:C]5ZXBW"O=?F'7T,A3:75,XP3E /V3X1%)^-)H`/4GC0)`>) MPD(O.%B>&`KZDH9W=. [/&X"L HM+DX`;?\0A.X?$+5:WA#W?^"F`J!Z4$;P M,82V][H[ASEBPR " !/J>>PU)^NP:9OL?;`J;0"`#IMVL\W )IXO\4ARUA\ M:=GOOP\QEF6;1L0H!&$IQVS/DQN$;09[X3OT'K,%?.A7`[^V8MPJE69XM5`: M993%?H[K<Y`8NE_DU.=NN, H`LE%<00JH?$R\)RJD6'H^&?$] E!F(S01 F5 MD?L'XP0W,U230BB,[^&?$AH;")Y1*16Z2NQE$#HTE,ZQE!`'B1K[S39FOK)_ MP_/->7C^,!ZZ.QLS :";<H&P#V*C^R`-ZO;*P55EOPFM!:XW56FZK"<BCR]? M&,/5*N9-)`VVBUELX;=?W-XRJ-.7I\E\T;:C#SQ-KMV%#W^4?? 2//JWMX-3 M:&X7Z?#T^O0EB-+W.=LB"$9H%D\UC-/1U>!R-?KIE3,Q3`,WP\(_"RN*EB`L M98>`/<<E"V-T]NKL9$PB"', J__-=4QR!^Y]P>YDJ0T<W`7ARB0+]+UNC)&? MV.V41FUIK/;;!``M- 7!0QR8;&WF-]R29/*E(G$?\9WW15+D_,WU9=IZ2/^= MT"C.1!G2*/%PH%BWZ3VU$X 'H$.\=W(]D(.U:3!50V]YS*'!JQM&UG6CR;=' M+5>8_D/[K;I`]B:%YWN1X$CA%FJAKH1A7L&Y,JZ">`;X1DG5CY:?`%99U3F= MA(D5EM9=XD[=LHHA3)%>.48IH1\3W"M15NZ5P ^3.S"HLIH1[M_`3;1EE==V M'%1478&A5:&=4KNR3A>J&$,.D1]&/J/PC=%L*ELRC[Y:J4,9+F+Q(LBXA6L^ MV^(L6*?CQH0R*$8 6^[F9' _.+&43!E?G!W62[8:?.^)3=]"3`IN3MTA_1"A M_C 8Q4&(VX6EE+`_J]5 .=SFY*4D6%L=_>[SWM&UGD!O%<)O%L$WJ\$WBL" M/ZL$WB\"'U0"/].52*O>/7A^8'P%?]CY,H>\\5PNGA69DDO+];_8<6U4RAM* MWY_RK>&9=S-&B>^`*X%P"&Q-W(TA\6"WQ UU?''/%;?T,L:S)(P$\GGH<MP1 M3.FA(]P4,K`DZ2E!42+/ZI"JXX.RE$&EA)CU5.+P0C!<MC%CM(K`C?&ZE``[ MJ $$M/.AH Y?4D`5/\TAST.).GZN5SWP"Q7%MQH` .T#N M<77!PNUVNYWRD]*M/GWT\(9&OX[&9Y<L\%7;DA, ;[!D.A1PEATGEIR[V3:% M*:YNB'J,XC(8/6GJH]L0$0>/`N6;.XFEGACA]&1X5( K9&-5=>R,"1EDAYZD MFQKX8)?.EZ4:J( 3=8QK7YWF90-=>VQ$JI$YGT-$(_:<(3KT CH9D6*ZD6UN M4Q1NW=ZVRI84&L407WVEC&*<T#L6G MX%_(!MKW#MT5H5P0WK'8B%//T`H18FI!)A_/XC=%2 >9 >$0G]W;U!,G.6%` M^6,T"Q+/(38>]4X6[%A-1&V81N60RQ-.^_R$$YM>Y/D^5)5[[AH`TV%^ (A! M8E_GB(0)&RIF#U MD%'"6VM9)&.)50U)!6-3<IUKWYIG)ZPDWHJ/;>G9W/1P(?C5;$]C%5PSG?6U MPV-XZ#X[>\:03>/C>/B2'R;_RZN%07P=XL**OE;'SJ[&9V\D0$Z%*\]XIX.W M[AAL1K)NZ/YE53/X+SN=R>^K0T=FHH7/3=Q[RGER\<8#RG!:>1D$[Z/VT''0 M!>H;*+KPT]LWGS%^JUX(]SC#VP^^$(N?&T=/P_P`.$7KL<1*>(<?*;%PT.WS MY5/"WW.5O35OM4S;3.VR'J*W'F2W"L3$75-IUFW4 V5':&K!^"F'-4#Y(R^U MP*?T`_6"!>8!:R#Q=-<:D.OSBW5$R XBU6/(Y>!U;;,S,+4 +#S X4I>C0!B M&_YBG;HS`![5EXSPF'O!#B6 M#ZM^EPLAX:H&9A_I6 N%7^-:3ZKX2:[U..F'L=;S /O8JX2H?",+O]Z`-#YR M9-X(MUK DUN&;;3LEO$B-%IA5SXU_Z;Q-XV_CD:=L18-]0%A2C$N>?*-LG67 ME/+1[?)B"+H YH)<P#W:4^.L CQ=1!&O7BOV_S))?BK//`1NF>QSPZ-N%.WN MRA(]O:IRJWL'Y8T4.-I-.=*:V^$<U72^J'D;M);U'W^85T='SCXZ*-)E94,R MZ:8[D <$>D>G>-:\2<+M[;[J^IOL*<35%]SNIR9SA8E#W_YM=O<J;*8K3&;3 M3E4`8_N1]8%B-T/!=+H4SO+<]1FL=B$6Z^F^I"Y)U^6O./W]1"SB$X<,V<>2 MF^F1KV*VF=ID=BQ,AJ'J&1[^\A2/9[&K",P/]VP"R4X/;0"G1\$L+Z[A429U M.4:A&:7.<2/'&U5ZWB(Z#Q8A3CS:W5'])/MNP[>]IZF>:>&&*\*-3Z4<[V$8 ML;<ANRRF0`1V4XZD35A\DE"Y$22>R3FB5VS'!J9L#HB"!.46(=9:#O_/8N:$ M"/E1KR!9R(X."X6[0M1U[R.TZ)#O[,+0UV*'L>?LD+:5G35.7^Z*NW9(%YYE MJ^]VL[<-=?`=LV,:^1E<VWX'03G_&#J^%YH$P?OV"(0U3/?ZF7C49A>(X'_L MZQK\+\>6)[G',QI2 M^5B&+\;CO[+J?K*Z]N\9O83TGZM5&R"(.[T)K+0][\ M59IRH#O[$BG[/SCH6&1";2N)J$+?Y1]]Y._H8!I?T;C]/P/G1TH\"'$5XKSV M7BD`%"LI8&=#CTK.S+.*R8O)`\T P^(%E)(09^/F)Y<&9X!"%F(>T#C\4\OD M2P$"%``4````"`"G3FPRS7\^/FH8``"G; ``$P`````````!`"`````````` I0T]-;V)J96-T<U-A;7!L92YJ<U!+!08``````0`!`$$```";&``````` ` endIt works! Thanks, again!(progress update and another request for help) PROPERTYGET and PROPERTYPUT work. PROPERTYPUTREF, I don't know, because I haven't tried and I don't know how to try. METHOD without parameters also works. With parameters, however, it's not as good. So far, this code works (adapted from Justin's code in http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/18805, I hope the URL is ok): //---------------------------------------------------------------- AXO ie=new AXO("InternetExplorer.Application"); try { VARIANTARG a1, a2, a3, a4; a1.n1.n2.vt=20; a2.n1.n2.vt=6008; a3.n1.n2.vt=0; a4.n1.n2.vt=17; VARIANTARG myArg; myArg.n1.n2.vt = VT_BSTR; myArg.n1.n2.n3.bstrVal = "about:blank"; ie.call("Navigate",a4,a3,a2,a1,myArg); myArg.n1.n2.vt = VT_UI4; myArg.n1.n2.n3.lVal = 850; ie.set("Width",myArg); myArg.n1.n2.n3.lVal = 710; ie.set("Height",myArg); myArg.n1.n2.n3.lVal = 10; ie.set("Top",myArg); myArg.n1.n2.n3.lVal = 10; ie.set("Left",myArg); myArg.n1.n2.vt = VT_BOOL; myArg.n1.n2.n3.boolVal = 0; ie.set("ToolBar",myArg); ie.set("MenuBar",myArg); ie.set("StatusBar",myArg); myArg.n1.n2.n3.boolVal = 1; ie.set("Visible",myArg); VARIANT tmp = ie.get("Visible"); bool isVisible = cast(bool) tmp.n1.n2.n3.boolVal; } finally ie.call("Quit"); //---------------------------------------------------------------- Works, except for the Navigate call. Going through the parameters I found their VARTYPEs, so that's why I set them, but I have no idea what they should receive. Also, I'm aware some methods are optional (Navigate has 4, I assume the only required is the URL), but only passing one doesn't work either. So, again, some help would be nice. _______________________ Carlos Santander Bernal
Mar 12 2005
jicman wrote:Carlos, I am very interested in this project. Here is a JScript program that does a lot with COMs. It uses IE, Excel and MySQL COM calls. I wish I can help you more but I am out of the country on business. I will keep on watching the posts on this subject. Thanks. joséThanks, I think that could work as a test case. _______________________ Carlos Santander Bernal
Mar 12 2005
Deleaker ( http://deleaker.com/ ) is the best tool for memory leaks detection and localization in C++
Apr 19 2010
Carlos Santander B. wrote:Carlos Santander B. wrote:Not that I should receive any credit for the code which I blantantly ripped off of jicman. ;) ...It works! Thanks, again!(progress update and another request for help) PROPERTYGET and PROPERTYPUT work. PROPERTYPUTREF, I don't know, because I haven't tried and I don't know how to try. METHOD without parameters also works. With parameters, however, it's not as good. So far, this code works (adapted from Justin's code in http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/18805, I hope the URL is ok):Works, except for the Navigate call. Going through the parameters I found their VARTYPEs, so that's why I set them, but I have no idea what they should receive. Also, I'm aware some methods are optional (Navigate has 4, I assume the only required is the URL), but only passing one doesn't work either.I'm hoping John C's observations are helpful because I don't have any ideas. I would like to congratulate you, though. It looks to me like you'd figured out the truly hard part and only the nagging details remain. Good job! -- Justin (a/k/a jcc7) http://jcc_7.tripod.com/d/
Mar 12 2005
J C Calvarese wrote:I'm hoping John C's observations are helpful because I don't have any ideas. I would like to congratulate you, though. It looks to me like you'd figured out the truly hard part and only the nagging details remain. Good job!Thanks. "The key is in the details" (or is the problem, or the issue?) _______________________ Carlos Santander Bernal
Mar 12 2005
J C Calvarese says...Carlos Santander B. wrote:Which I, purposely, grab from a Microsoft site. Though, I did have to suffer translating it to JScript, since it was VBScript! But, code it's never stolen. Just copied and pasted. ja ja ja ja (That's me, laughing in Spanish! By the way, it sounds just like ha ha ha ha. For you see, in Spanish, the j sounds like an h. Ja ja ja ja ja)Carlos Santander B. wrote:Not that I should receive any credit for the code which I blantantly ripped off of jicman. ;)It works! Thanks, again!(progress update and another request for help) PROPERTYGET and PROPERTYPUT work. PROPERTYPUTREF, I don't know, because I haven't tried and I don't know how to try. METHOD without parameters also works. With parameters, however, it's not as good. So far, this code works (adapted from Justin's code in http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/18805, I hope the URL is ok):Works, except for the Navigate call. Going through the parameters I found their VARTYPEs, so that's why I set them, but I have no idea what they should receive. Also, I'm aware some methods are optional (Navigate has 4, I assume the only required is the URL), but only passing one doesn't work either.I'm hoping John C's observations are helpful because I don't have any ideas. I would like to congratulate you, though. It looks to me like you'd figured out the truly hard part and only the nagging details remain. Good job! -- Justin (a/k/a jcc7) http://jcc_7.tripod.com/d/
Mar 13 2005