Author Topic: Script BASIC COM  (Read 322 times)

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Script BASIC COM
« on: November 26, 2017, 12:56:49 PM »
I'n no COM/OLE pro but I have been spending some time with it trying to get the Script BASIC COM extension module working with the Sage 100 accounting software  BOI (Busness Object Interface) I have it working in VBScript but still not 100% there yet with Script BASIC. See the Open Sage Forum Code Challange for where I'm at with it.


Offline petelomax

  • BASIC Developer
  • Posts: 4
  • Author of Phix
    • The Phix Programming Language
Re: Script BASIC COM
« Reply #1 on: November 28, 2017, 05:23:13 PM »
Well, without a working version of Sage there is little I can even try.

Actually, I used to work for online50 (and yes it is Sage 50 not Sage 100) - while I have no interest in promoting their services anymore, if what you need is a business solution, as opposed to a programming solution, you could do worse than peruse http://www.online50.net/Online_Sage/Sage50AccountsAddOnsOnline.html as there are a numer of "Adept" extensions, one of which might just possibly fit the bill. Before you ask, there is no way I could get any source code, but with a bit of google-fu you might find someone worth talking to.
« Last Edit: November 28, 2017, 05:35:19 PM by petelomax »

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #2 on: November 29, 2017, 09:06:09 AM »
Thanks Pete fpr having a peek at my Sage issue with Script BASIC COM.

The reason I think I'm getting a ProvideX ERR=95 is that the Script BASIC COM extension module isn't passing the VT_Dispatch object (SY_Session) as an object.  Is have no problem using  SY_Session to call methods or get/let properties. I feel I'm close to getting this working after I discovered I needed Script BASIC to be a Windows process and not a console process. (got me by the ERR=65 issue)


Here is the Bitbucket Repository Script BASIC COM if you would be so kind to have a look. The COM.cpp is the extension module interface.

« Last Edit: November 29, 2017, 11:45:53 AM by John »

Offline petelomax

  • BASIC Developer
  • Posts: 4
  • Author of Phix
    • The Phix Programming Language
Re: Script BASIC COM
« Reply #3 on: November 30, 2017, 09:18:37 AM »
All a bit above my pay grade, unfortunately. The only (minor, very minor) thing I noticed was the free(sz) in __C2W should probably be moved up one line to be inside the if.

Actually, turning this around on its head, can you replicate the fileopen (see links above) in ScriptBasic? The aim is pretty simple: to have a file open/save that has
an ansi/utf8/utf16 encoding dropdown that auto-changes as you select different files and, of course, can be manually overidden - exactly like the standard Windows Notepad.

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #4 on: November 30, 2017, 12:28:40 PM »
Thanks Pete for having a look. I can see your point of moving free(sz) into the IF block. I'm not sure if this will solve my problem with the ERR=95 in the ProvideX COM interface but it's worth fixing. I see a lot of GetIDsOfNames failures trying to call methods and maybe this is related.

What looks like that is happening is osession which Script BASIC sees as a VT_DISPATCH type prior to the ProvideX.Script NewObject() call to instantiate the AR_Customer_bus business object isn't seeing the argument as a passed VT_DISPATCH object reference but a VT_LONG. Calling NewObject without the object reference as an argument as in the SY_Session() call works fine. If I can just get by this issue, I feel everything will work and give me full control over the BOI interface and not have to count on static VBScripts to process the requests.  :-\

I recompiled the COM interface with the suggested change and still having the same issue.


C:\ScriptBASIC\examples>sbwin comcustcc.sb
The number of arguments is: 1
CreateObject(ProvideX.Script)
CallByName 4 args
CallByName(obj=4bb454, method='Init', calltype=1 , comArgs=1)
CallByName 4 args
CallByName(obj=4bb454, method='NewObject', calltype=1 , comArgs=1)
return value from COM function was numeric: 4961652
CallByName 5 args
CallByName(obj=4bb574, method='nSetUser', calltype=1 , comArgs=2)
return value from COM function was numeric: 1
CallByName 4 args
CallByName(obj=4bb574, method='nsetcompany', calltype=1 , comArgs=1)
return value from COM function was numeric: 1
CallByName 5 args
CallByName(obj=4bb574, method='nSetDate', calltype=1 , comArgs=2)
return value from COM function was numeric: 1
CallByName 4 args
CallByName(obj=4bb574, method='nSetModule', calltype=1 , comArgs=1)
return value from COM function was numeric: 1
CallByName 4 args
CallByName(obj=4bb574, method='nLookupTask', calltype=1 , comArgs=1)
return value from COM function was numeric: 40000001
CallByName 4 args
CallByName(obj=4bb574, method='nSetProgram', calltype=1 , comArgs=1)
return value from COM function was numeric: 100006
CallByName 5 args
CallByName(obj=4bb454, method='NewObject', calltype=1 , comArgs=2)
Invoke failed
CallByName 4 args
CallByName(NULL) called
CallByName 4 args
CallByName(NULL) called
CallByName 3 args
CallByName(NULL) called
CallByName 5 args
CallByName(NULL) called
CallByName 5 args
CallByName(NULL) called
CallByName 5 args
CallByName(NULL) called
CallByName 5 args
CallByName(NULL) called
CallByName 5 args
CallByName(NULL) called
CallByName 2 args
CallByName(obj=4bb574, method='DropObject', calltype=1 , comArgs=0)

C:\ScriptBASIC\examples>

« Last Edit: November 30, 2017, 12:50:17 PM by John »

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #5 on: November 30, 2017, 12:54:22 PM »
Quote
Actually, turning this around on its head, can you replicate the fileopen (see links above) in ScriptBasic? The aim is pretty simple: to have a file open/save that has
an ansi/utf8/utf16 encoding dropdown that auto-changes as you select different files and, of course, can be manually overidden - exactly like the standard Windows Notepad.

Charles Pegge (OxygenBasic author) wrote a FFI on steroids (DLLC) as an extension module for Script BASIC. It handles wide strings, BStrings, conversions and auto freeing.

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #6 on: November 30, 2017, 07:18:27 PM »
This looks suspcious.

besFUNCTION(CallByName)

Code: C
  1.         case VT_DISPATCH:
  2.  
  3.                 //if(retVal.vt == VT_DISPATCH) todo: register handle
  4.                 if(com_dbg) color_printf(colors::myellow,"return value from COM function was numeric: %d\n", retVal.lVal);
  5.         LONGVALUE(besRETURNVALUE) = retVal.lVal;
  6.                 break;
  7.  

register handle - Seems like a good idea. It looks like I was right about passing the LONG object pointer reference.

Can you provide any help with registering the VT_DISPATCH handle? (if you agree this may be the problem)

FYI: An ERR=95 means the object doesn't exists. Makes sense if it's not registered.
« Last Edit: November 30, 2017, 09:05:33 PM by John »

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #7 on: December 02, 2017, 06:03:08 PM »
The DescribeInterface function provides a basic typelib viwewer. What caught my eye is the Script BASIC argument being a previously defined object pointer required
Code: C
  1. IDispatch* IDisp = (IDispatch*)LONGVALUE(Argument);
  2.  
before calling the typelib viewer. I'm wondering if this same assignment used for the passed object handle that currently is passed aLONG as an object pointer reference,  Basically giving the argument an iDispatch reference.

Code: C
  1. besFUNCTION(DescribeInterface)
  2.  
  3.         VARIABLE Argument ;
  4.         char* unk = "Failed";
  5.         besRETURNVALUE = besNEWMORTALLONG;
  6.  
  7.         if( besARGNR != 1) RETURN0("DescribeInterface takes one argument!")
  8.  
  9.         Argument = besARGUMENT(1);
  10.         besDEREFERENCE(Argument);
  11.  
  12.         if( TYPE(Argument) != VTYPE_LONG) RETURN0("DescribeInterface requires a long argument")
  13.         if( LONGVALUE(Argument) == 0) RETURN0("DescribeInterface(NULL) called")
  14.         IDispatch* IDisp = (IDispatch*)LONGVALUE(Argument);
  15.        
  16.         try{
  17.                 DescribeInterface(IDisp);
  18.         }catch(...){
  19.                 RETURN0("DescribeInterface threw an error?")
  20.         }
  21.  
  22. cleanup:
  23.         return 0;
  24.  
  25. besEND
  26.  

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #8 on: December 04, 2017, 06:36:10 PM »
I don't believe the argument assignment for loop ever addresses a type of VT_DISPATCH. The argument is being passed as a type VT_DISPATCH but I fear the argument never gets assigned for the INVOKE if it was passed as a VT_DISPATCH type.

Code: C
  1.   /* map in argument values and types    ->[ IN REVERSE ORDER ]<-    */
  2.   for(int i=0; i < com_args; i++){
  3.           VARIABLE arg_x;              
  4.           arg_x = besARGUMENT(3 + com_args - i);
  5.           besDEREFERENCE(arg_x);
  6.  
  7.                 switch( TYPE(arg_x) ){ //script basic type to COM variant type
  8.  
  9.                           case VTYPE_DOUBLE:
  10.                           case VTYPE_ARRAY:
  11.                           case VTYPE_REF:
  12.                                 RETURN0("Arguments of script basic types [double, ref, array] not supported")
  13.                                 break;
  14.  
  15.                           case VTYPE_LONG:
  16.                                 pvarg[i].vt = VT_I4;
  17.                                 pvarg[i].lVal = LONGVALUE(arg_x);
  18.                                 break;
  19.                          
  20.                           case VTYPE_STRING:
  21.                                 char* myStr = GetCString(arg_x);
  22.                                
  23.                                 //peek at data and see if an explicit VT_ type was specified.. scriptbasic only supports a few types
  24.                                 if( !HandleSpecial(&pvarg[i], myStr) ){
  25.                                         //nope its just a standard string type
  26.                                         LPWSTR wStr = __C2W(myStr);
  27.                                         BSTR bstr = SysAllocString(wStr);
  28.                                         bstrs.push_back(bstr); //track these to free after call to prevent leak
  29.                                         pvarg[i].vt = VT_BSTR;
  30.                                         pvarg[i].bstrVal = bstr;
  31.                                         free(myStr);
  32.                                         free(wStr);
  33.                                 }
  34.  
  35.                                 break;                   
  36.                                
  37.           }
  38.  
  39.   }
  40.  


I added an additional debug print to display the argument types and values just prior to the INVOKE.

CallByName(obj=3750bc, method='NewObject', calltype=1 , comArgs=2)

pvarg[0].vt=3, pvarg[1].vt=8

CallByName(obj=2850bc, method='NewObject', calltype=1 , comArgs=2)
pvarg[0].lVal=2642396, pvarg[1].lVal=2712612

The question is did the predefined object handle (SY_Session) get passed as a LONG to INVOKE and not as DISPATCH pointer?

Charles Pegge (OxygenBasic author) sent me an informative article about COM in plain C.
« Last Edit: December 05, 2017, 01:05:17 PM by John »

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #9 on: December 06, 2017, 07:30:22 PM »
I think I may be able to fix this by adding a special VT_ type for getting the IDispatch pointer before doing the Invoke. Dave already supports a couple VT_ special types for wide and BStr arguments.

If that doesn't do it, then I'm at a loss.

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #10 on: December 07, 2017, 07:05:04 PM »
This is going to my attempt to try to handle objects as CallByName arguments enabling and extending Dave's special VT_ types routine. I going to add the VT_DISPATCH to the routine and get the IDispatch handle before doing the CallByName method Invoke.

Code: C
  1.                                 //peek at data and see if an explicit VT_ type was specified.. scriptbasic only supports a few types
  2.                                 if( !HandleSpecial(&pvarg[i], myStr) ){
  3.                                         //nope its just a standard string type
  4.                                         LPWSTR wStr = __C2W(myStr);
  5.                                         BSTR bstr = SysAllocString(wStr);
  6.                                         bstrs.push_back(bstr); //track these to free after call to prevent leak
  7.                                         pvarg[i].vt = VT_BSTR;
  8.                                         pvarg[i].bstrVal = bstr;
  9.                                         free(myStr);
  10.                                         free(wStr);
  11.                                 }
  12.  

Code: C
  1. bool HandleSpecial(VARIANTARG* va, char* str){
  2.  
  3.         return false; //disabled for now see notes above..
  4.  
  5.         if(str==0) return false;
  6.  
  7.         std::string s = str;
  8.          
  9.         if(s.length() < 3) return false;
  10.         if(s.substr(0,3) != "VT_") return false;
  11.        
  12.         int pos = s.find(":",0);
  13.         if(pos < 1) return false;
  14.  
  15.         std::string cmd = s.substr(0,pos);
  16.         if(s.length() < pos+2) return false;
  17.  
  18.         s = s.substr(pos+1);
  19.  
  20.         //todo implement handling of these types (there are many more than this)
  21.         if(cmd == "VT_I1"){
  22.         }else if(cmd == "VT_I2"){
  23.         }else if(cmd == "VT_I8"){
  24.     }else if(cmd == "VT_BOOL"){
  25.         }
  26.        
  27.         return true;
  28. }
  29.  

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #11 on: December 09, 2017, 07:59:44 PM »
I'm almost there but having an issue conversing a string to a IDispatch pointer. Any suggestions would be appreciated.

As part of the CallByName argument checking and assigning for LONG and STRING values, it also checks for special types. Dave disabled the routine think it was needed for this first cut of the interface. I enable to prefix VT_ special type function and tried to assign the argument array for CallByName with a IDispatch pointer to the object pointer (LONG) passed in the call.

I'm getting the following errors trying to make this code work.

Error   30   error C2664: 'atoi' : cannot convert parameter 1 from 'std::string' to 'const char *'   c:\scriptbasic_control-master\engine\com_extension_dll\com.cpp   504   COM
Error   31   error C2227: left of '->Value' must point to class/struct/union/generic type   c:\scriptbasic_control-master\engine\com_extension_dll\com.cpp   504   COM
Error   32   error C2228: left of '.lValue' must have class/struct/union   c:\scriptbasic_control-master\engine\com_extension_dll\com.cpp   504   COM


This is the HandleSpecial routine I enabled and trying to use to assign an IDispatch pointer using the object pointer (SY_Session / osession) being passed as a string value. I'm having a problem with making the va (current com_arg array index) assignment.

Here is the COM.cpp original source on the Bitbucket repository.

Code: C
  1. // the idea behind this one is that we can use a string to embed a type specifier
  2. // to explicitly declare and cast a variable to the type we want such as "VT_I2:2"
  3. //
  4. // in testing with VB6 however, if we pass .vt = VT_I4 when vb6 expects a VT_I1 (char)
  5. // it works as long as the value is < 255, also works with VT_BOOL
  6. //
  7. // do we really need this function ? I prefer less complexity if possible.
  8. //
  9. // Note: there are many COM types, I have no plans to cover them all
  10.  
  11. bool HandleSpecial(VARIANTARG* va, char* str){
  12.  
  13.         // return false; //disabled for now see notes above..
  14.  
  15.         if(str==0) return false;
  16.  
  17.         std::string s = str;
  18.          
  19.         if(s.length() < 3) return false;
  20.         if(s.substr(0,3) != "VT_") return false;
  21.        
  22.         int pos = s.find(":",0);
  23.         if(pos < 1) return false;
  24.  
  25.         std::string cmd = s.substr(0,pos);
  26.         if(s.length() < pos+2) return false;
  27.  
  28.         s = s.substr(pos+1);
  29.  
  30.         //todo implement handling of these types (there are many more than this)
  31.         if(cmd == "VT_I1"){
  32.         }else if(cmd == "VT_I2"){
  33.         }else if(cmd == "VT_I8"){
  34.   }else if(cmd == "VT_BOOL"){
  35.   }else if(cmd == "VT_DISPATCH"){
  36.         IDispatch* va = (IDispatch*)LONGVALUE(atoi(s));
  37.         }
  38.        
  39.         return true;
  40. }
  41.  
« Last Edit: December 09, 2017, 08:07:54 PM by John »

Offline erosolmi

  • BASIC Developer
  • Posts: 3
Re: Script BASIC COM
« Reply #12 on: December 10, 2017, 03:58:23 AM »
Ciao John

va is already a VARIANTARG pointer, you cannot cast to another type like "IDispatch* va = (IDispatch*)"
See: https://msdn.microsoft.com/en-us/library/ms891678.aspx

You just need to assign a value to va.vt = VT_DISPATCH (or VT_DISPATCH | VT_BYREF if passed BYREF) that is the type of value inside va.
And put populate correct va member: put dispatch pointer you have into the string into va.pdispVal (or va.ppdispVal if passed BYREF)

I know what you are doing because I did for thinBasic.
In any case I think ScriptBasic is on the wrong path, I mean ... why to give to user the complexity to use something like CallByName(<object>, ...) syntax?
Try to give the user a syntax like <objectname>.<property> or <objectname>.<method>

In any case passing from a CallByName(<object>, ...) is a good step to understand what's going on.

Ciao and good luck

Offline John

  • Forum Support / SB Dev
  • Posts: 1674
    • ScriptBasic Open Source Project
Re: Script BASIC COM
« Reply #13 on: December 10, 2017, 10:51:59 AM »
Thank You Eros for helping out with this problem I've been struggling with for a couple weeks.

Could you be so kind to show me the replacement line of code that would make this work? My knowledge of C++ and low level COM/OLE is marginal at best.

The va variable (passed byref) is actually the CallByName() &pvarg index i I'm tying to populate with a passed string version (converted to a LONG) of the SY_Session object pointer. Currently the Invoke is seeing the pointer as just a LONG which fails.

The argument passed is "VT_DISPATCH:" & osession. SB converts the LONG pointer to a string when using a concatenating & operator.
« Last Edit: December 10, 2017, 11:00:33 AM by John »

Offline erosolmi

  • BASIC Developer
  • Posts: 3
Re: Script BASIC COM
« Reply #14 on: December 10, 2017, 12:00:53 PM »
John,

have a look at Jose CallByName: https://forum.powerbasic.com/forum/user-to-user-discussions/source-code/24964-com-callbyname
The general idea is that in order to call a method/property of an interface, you need:
  • parse your arguments in some way
  • fill an array of variants with the values you parsed
  • than determine the DispId of the method you need to call. It is the number that identify the method/property inside the Interface
  • Call Invoke method of your Interface passing the variant array in reverse order
  • Get result

Start from very simple, that are numbers.
Then add strings that are BSTR using SYS* functions
Passing a Dispatch object is nothing  more than passing a variant of type VT_DISPATCH and setting the correct pointer.

When done with numbers let me know and I will help more

Ciao
Eros