Oxygen Basic

Information => Development => Topic started by: JRS on October 10, 2020, 07:54:09 AM

Title: COM
Post by: JRS on October 10, 2020, 07:54:09 AM
Charles,

Is it possible to add COM / OLE automation support to O2 as a library? You touched on this with DLLC.
Title: Re: COM
Post by: Charles Pegge on October 12, 2020, 05:19:27 AM
Yes, John. We just need a COM automation developer to do it.

For low level COM, we have the SAPI5 text-to speech-example. It's the only COM I am familiar with, so other examples would be welcome.
Title: Re: COM
Post by: JRS on October 12, 2020, 05:37:28 AM
Charles,

Could you send Jose Roca an e-mail inviting him back to participate in the forum. I don't know anyone that knows COM better than Jose. Mike when he was alive was my goto guy with COM.

Dave Zimmer's (SB COM ext. module author) CallByName COM/OLE automation interface is written C. I use VS2012 to maintain the interface. Maybe using that as a base for O2 COM automation is the way to go.
Title: Re: COM
Post by: Brian Alvarez on October 12, 2020, 06:42:48 AM

  I hope im wrong... but i doubt Jose will Join in. I have reasons to think so, but i would like if he would.
Title: Re: COM
Post by: JRS on October 12, 2020, 07:08:02 AM
If I'm the reason Jose isn't participating I wish to apologize if I said anything that was offending.  I really do miss him a lot.
Title: Re: COM
Post by: JRS on October 12, 2020, 09:31:04 AM
Here is thread by Jose Roca on his FreeBasic OLE automation interface. Maybe this could be used as a base for a O2 automation interface library?

JR COM (https://www.planetsquires.com/protect/forum/index.php?topic=3745.0)

I'm going to extract the CallByNane C code from the SB COM extention module and post it here. Maybe I can get some help porting it to O2.
Title: Re: COM
Post by: JRS on October 12, 2020, 10:47:11 AM
Here is Dave Zimmer's code for COM/OLE automation with ScriptBasic. My gut says this shouldn't be too hard to convert to Oxygen Basic.

Code: C
  1. /*
  2.  Author:  David Zimmer <dzzie@yahoo.com>
  3.  Site:    http://sandsprite.com
  4.  
  5.  Notes: Not all COM types are currently handled, but enough to be useful
  6.         this is still a bit of work in progress. I will make additions as
  7.         I use it and find it necessary.
  8.  
  9.  Script Basic Declarations to use this extension:
  10.  
  11.                 declare sub CreateObject alias "CreateObject" lib "test.exe"
  12.                 declare sub CallByName alias "CallByName" lib "test.exe"
  13.                 declare sub ReleaseObject alias "ReleaseObject" lib "test.exe"
  14.         declare sub GetHostObject alias "GetHostObject" lib "sb_engine.dll"
  15.         declare sub GetHostString alias "GetHostString" lib "sb_engine.dll"
  16.         declare sub TypeName alias "TypeName" lib "sb_engine.dll"
  17.  
  18.                 const VbGet = 2
  19.                 const VbLet = 4
  20.                 const VbMethod = 1
  21.                 const VbSet = 8
  22.  
  23.  Example:
  24.  
  25.                 'you can load objects either by ProgID or CLSID
  26.                 'obj = CreateObject("SAPI.SpVoice")
  27.                 obj = CreateObject("{96749377-3391-11D2-9EE3-00C04F797396}")
  28.  
  29.                 if obj = 0 then
  30.                         print "CreateObject failed!\n"
  31.                 else
  32.                         CallByName(obj, "rate", VbLet, 2)
  33.                         CallByName(obj, "volume", VbLet, 60)
  34.                         CallByName(obj, "speak", VbMethod, "This is my test")
  35.                         ReleaseObject(obj)
  36.                 end if
  37.  
  38. */
  39.  
  40. #include <stdio.h>
  41. #include <list>
  42. #include <string>
  43.  
  44. #include <comdef.h>
  45. #include <AtlBase.h>
  46. #include <AtlConv.h>
  47. #include <atlsafe.h>
  48.  
  49. #include "basext.h"
  50. #include "vb.h"
  51.  
  52. int com_dbg = 0;
  53. int initilized=0;
  54. vbHostResolverCallback vbHostResolver = NULL;
  55.  
  56. pSupportTable g_pSt = NULL;
  57. #define EXPORT comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__)
  58.  
  59. int __stdcall SBCallBack(int EntryPoint)
  60. {
  61. #pragma EXPORT
  62.  
  63.   pSupportTable pSt = g_pSt;
  64.   VARIABLE FunctionResult;
  65.   int retVal;
  66.  
  67.   if(pSt==NULL) return -1;
  68.   besHOOK_CALLSCRIBAFUNCTION(EntryPoint, 0, 0, &FunctionResult);
  69.   retVal = FunctionResult->Value.lValue;
  70.   besRELEASE(FunctionResult);
  71.   return retVal;
  72. }
  73.  
  74. //vbCallType aligns with DISPATCH_XX values for Invoke
  75. enum vbCallType{ VbGet = 2, VbLet = 4, VbMethod = 1, VbSet = 8 };
  76. enum colors{ mwhite=15, mgreen=10, mred=12, myellow=14, mblue=9, mpurple=5, mgrey=7, mdkgrey=8 };
  77.  
  78. //char* to wide string
  79. LPWSTR __C2W(char *szString){
  80.         DWORD n;
  81.         char *sz = NULL;
  82.         LPWSTR ws= NULL;
  83.         if(*szString && szString){
  84.                 sz = strdup(szString);
  85.                 n = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, NULL, 0);
  86.                 if(n){
  87.                         ws = (LPWSTR)malloc(n*2);
  88.                         MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, ws, n);
  89.                 }
  90.         }
  91.         free(sz);
  92.         return ws;
  93. }
  94.  
  95.  // BSTR to C String conversion
  96. char* __B2C(BSTR bString)
  97. {
  98.         int i;
  99.         int n = (int)SysStringLen(bString);
  100.         char *sz;
  101.         sz = (char *)malloc(n + 1);
  102.  
  103.         for(i = 0; i < n; i++){
  104.                 sz[i] = (char)bString[i];
  105.         }
  106.         sz[i] = 0;
  107.         return sz;
  108. }
  109.  
  110. // BSTR to std::String conversion
  111. std::string __B2S(BSTR bString)
  112. {
  113.         char *sz = __B2C(bString);
  114.         std::string tmp = sz;
  115.         free(sz);
  116.         return tmp;
  117. }
  118.  
  119. //script basic STRING type to char*
  120. char* GetCString(VARIABLE v){
  121.  
  122.         int slen;
  123.         char *s;
  124.     char* myCopy = NULL;
  125.  
  126.         s = STRINGVALUE(v);
  127.         slen = STRLEN(v);
  128.         if(slen==0) return strdup("");
  129.  
  130.         myCopy = (char*)malloc(slen+1);
  131.         if(myCopy==0) return 0;
  132.  
  133.         memcpy(myCopy,s, slen);
  134.         myCopy[slen]=0;
  135.         return myCopy;
  136.  
  137. }
  138.  
  139.  
  140. void color_printf(colors c, const char *format, ...)
  141. {
  142.         DWORD dwErr = GetLastError();
  143.         HANDLE hConOut = GetStdHandle( STD_OUTPUT_HANDLE );
  144.  
  145.         if(format){
  146.                 char buf[1024];
  147.                 va_list args;
  148.                 va_start(args,format);
  149.                 try{
  150.                          _vsnprintf(buf,1024,format,args);
  151.                          if(vbStdOut){
  152.                                  vbStdOut(cb_dbgout, buf, strlen(buf));
  153.                          }else{
  154.                                  SetConsoleTextAttribute(hConOut, c);
  155.                                  printf("%s",buf);
  156.                                  SetConsoleTextAttribute(hConOut,7);
  157.                          }
  158.                 }
  159.                 catch(...){}
  160.         }
  161.  
  162.         SetLastError(dwErr);
  163. }
  164.  
  165. HRESULT TypeName(IDispatch* pDisp, std::string *retVal)
  166. {
  167.     HRESULT hr = S_OK;
  168.         UINT count = 0;
  169.  
  170.     CComPtr<IDispatch> spDisp(pDisp);
  171.     if(!spDisp)
  172.         return E_INVALIDARG;
  173.  
  174.     CComPtr<ITypeInfo> spTypeInfo;
  175.     hr = spDisp->GetTypeInfo(0, 0, &spTypeInfo);
  176.  
  177.     if(SUCCEEDED(hr) && spTypeInfo)
  178.     {
  179.         CComBSTR funcName;
  180.         hr = spTypeInfo->GetDocumentation(-1, &funcName, 0, 0, 0);
  181.         if(SUCCEEDED(hr) && funcName.Length()> 0 )
  182.         {
  183.           char* c = __B2C(funcName);
  184.                   *retVal = c;
  185.                   free(c);
  186.         }        
  187.     }
  188.  
  189.     return hr;
  190.  
  191. }
  192.  
  193. VARIANT __stdcall SBCallBackEx(int EntryPoint, VARIANT *pVal)
  194. {
  195. #pragma EXPORT
  196.  
  197.   pSupportTable pSt = g_pSt;
  198.   VARIABLE FunctionResult;
  199.   _variant_t vRet;
  200.  
  201.   if(pSt==NULL){
  202.           MessageBox(0,"pSupportTable is not set?","",0);
  203.           return vRet.Detach();
  204.   }
  205.  
  206.     USES_CONVERSION;
  207.         char buf[1024]={0};
  208.     HRESULT hr;
  209.         long lResult;
  210.         long lb;
  211.         long ub;
  212.     SAFEARRAY *pSA = NULL;
  213.  
  214.         //we only accept variant arrays..
  215.         if (V_VT(pVal) == (VT_ARRAY | VT_VARIANT | VT_BYREF)) //24588
  216.                 pSA = *(pVal->pparray);
  217.         //else if (V_ISARRAY(pVal) && V_ISBYREF(pVal)) //array of longs here didnt work out maybe latter
  218.         //      pSA = *(pVal->pparray);
  219.         else
  220.         {
  221.                 if (V_VT(pVal) == (VT_ARRAY | VT_VARIANT))
  222.                         pSA = pVal->parray;
  223.                 else
  224.                         return vRet.Detach();//"Type Mismatch [in] Parameter."
  225.         };
  226.  
  227.     long dim = SafeArrayGetDim(pSA);
  228.         if(dim != 1) return vRet.Detach();
  229.  
  230.         lResult = SafeArrayGetLBound(pSA,1,&lb);
  231.         lResult = SafeArrayGetUBound(pSA,1,&ub);
  232.  
  233.         lResult=SafeArrayLock(pSA);
  234.     if(lResult) return vRet.Detach();
  235.  
  236.     _variant_t vOut;
  237.         _bstr_t cs;
  238.  
  239.         int sz = ub-lb+1;
  240.     VARIABLE pArg = besNEWARRAY(0,sz);
  241.  
  242.         //here we proxy the array of COM types into the array of script basic types element by element.
  243.         //      note this we only support longs and strings. floats will be rounded, objects converted to objptr()
  244.         //  bytes and integers are ok too..basically just not float and currency..which SB doesnt support anyway..
  245.     for (long l=lb; l<=ub; l++) {
  246.                 if( SafeArrayGetElement(pSA, &l, &vOut) == S_OK ){
  247.                         if(vOut.vt == VT_BSTR){
  248.                                 char* cstr = __B2C(vOut.bstrVal);
  249.                                 int slen = strlen(cstr);
  250.                                 pArg->Value.aValue[l] = besNEWMORTALSTRING(slen);
  251.                                 memcpy(STRINGVALUE(pArg->Value.aValue[l]),cstr,slen);
  252.                                 free(cstr);
  253.                         }
  254.                         else{
  255.                                 if(vOut.vt == VT_DISPATCH){
  256.                                         //todo register handle? but how do we know the lifetime of it..
  257.                                         //might only be valid until this exits, or forever?
  258.                                 }
  259.                                 pArg->Value.aValue[l] = besNEWMORTALLONG;
  260.                                 LONGVALUE(pArg->Value.aValue[l]) = vOut.lVal;
  261.                         }
  262.                 }
  263.     }
  264.  
  265.   lResult=SafeArrayUnlock(pSA);
  266.   if (lResult) return vRet.Detach();
  267.  
  268.   besHOOK_CALLSCRIBAFUNCTION(EntryPoint,
  269.                                                          pArg->Value.aValue,
  270.                              sz,
  271.                              &FunctionResult);
  272.  
  273.   for (long l=0; l <= sz; l++) {
  274.          besRELEASE(pArg->Value.aValue[l]);
  275.      pArg->Value.aValue[l] = NULL;
  276.   }
  277.        
  278.   if(FunctionResult->vType == VTYPE_STRING){
  279.         char* myStr = GetCString(FunctionResult);
  280.         vRet.SetString(myStr);
  281.         free(myStr);
  282.   }
  283.   else{
  284.           switch( TYPE(FunctionResult) )
  285.           {      
  286.                 case VTYPE_DOUBLE:
  287.                 case VTYPE_ARRAY:
  288.                 case VTYPE_REF:
  289.                                 MessageBoxA(0,"Arguments of script basic types [double, ref, array] not supported","Error",0);
  290.                                 break;
  291.                 default:
  292.                                 vRet = LONGVALUE(FunctionResult);
  293.           }
  294.   }
  295.  
  296.   besRELEASE(pArg);
  297.   besRELEASE(FunctionResult);
  298.  
  299.   return vRet.Detach();
  300. }
  301.  
  302. //note the braces..required so if(x)RETURN0(msg) uses the whole blob
  303. //should this be goto cleanup instead of return 0?
  304. #define RETURN0(msg) {if(com_dbg) color_printf(colors::mred, "%s\n", msg); \
  305.                          LONGVALUE(besRETURNVALUE) = 0; \
  306.                                          goto cleanup;}
  307.  
  308.  
  309.  
  310. besFUNCTION(TypeName)
  311.  
  312.         VARIABLE Argument ;
  313.         char* unk = "Failed";
  314.         besRETURNVALUE = besNEWMORTALLONG;
  315.  
  316.         if( besARGNR != 1) RETURN0("TypeName takes one argument!")
  317.  
  318.         Argument = besARGUMENT(1);
  319.         besDEREFERENCE(Argument);
  320.  
  321.         if( TYPE(Argument) != VTYPE_LONG) RETURN0("TypeName requires a long argument")
  322.         if( LONGVALUE(Argument) == 0) RETURN0("TypeName(NULL) called")
  323.         IDispatch* IDisp = (IDispatch*)LONGVALUE(Argument);
  324.        
  325.         try{
  326.                 std::string retVal;
  327.                 if(TypeName(IDisp, &retVal) == S_OK){
  328.                         besALLOC_RETURN_STRING(retVal.length());
  329.                         memcpy(STRINGVALUE(besRETURNVALUE),retVal.c_str(),retVal.length());
  330.                 }else{
  331.                         besALLOC_RETURN_STRING(strlen(unk));
  332.                         memcpy(STRINGVALUE(besRETURNVALUE),unk,strlen(unk));
  333.                 }
  334.         }catch(...){
  335.                 RETURN0("Invalid IDisp pointer?")
  336.         }
  337.  
  338. cleanup:
  339.         return 0;
  340.  
  341. besEND
  342.  
  343. //Object GetHostObject("Form1")
  344. //this is for embedded hosts, so script clients can dynamically look up obj pointers
  345. //for use with teh COM functions. Instead of the MS Script host design of AddObject
  346. //here we allow the script to query values from a host resolver. Its easier than
  347. //trying to mess with the internal Symbol tables, and cleaner than editing an include
  348. //script on the fly every single launch to add global variables which would then show up
  349. //in the debug pane. this function can be used for retrieving any long value
  350. besFUNCTION(GetHostObject)
  351.   int retVal=0;
  352.   int slen;
  353.   char* myCopy = NULL;
  354.   VARIABLE Argument;
  355.   besRETURNVALUE = besNEWMORTALLONG;
  356.  
  357.   if( besARGNR != 1) RETURN0("GetHostObject takes one argument!")
  358.  
  359.   Argument = besARGUMENT(1);
  360.   besDEREFERENCE(Argument);
  361.  
  362.   if( TYPE(Argument) != VTYPE_STRING) RETURN0("GetHostObject requires a string argument")
  363.   if( STRLEN(Argument) > 1000) RETURN0("GetHostObject argument to long")
  364.  
  365.   myCopy = GetCString(Argument);
  366.   if(myCopy==0) RETURN0("malloc failed low mem")
  367.  
  368.   if(vbHostResolver==NULL) RETURN0("GetHostObject requires vbHostResolver callback to be set")
  369.        
  370.   retVal = vbHostResolver(myCopy, strlen(myCopy), 0);
  371.  
  372. cleanup:
  373.     LONGVALUE(besRETURNVALUE) = retVal;    
  374.         if(myCopy) free(myCopy);
  375.         return 0;
  376.  
  377. besEND
  378.  
  379. //as above but for retrieving strings up to 1024 chars long
  380. besFUNCTION(GetHostString)
  381.   int retVal=0;
  382.   int slen=0;
  383.   char* myCopy = NULL;
  384.   char buf[1026];
  385.   VARIABLE Argument;
  386.   besRETURNVALUE = besNEWMORTALLONG;
  387.  
  388.   if( besARGNR != 1) RETURN0("GetHostString takes one argument!")
  389.  
  390.   Argument = besARGUMENT(1);
  391.   besDEREFERENCE(Argument);
  392.  
  393.   if( TYPE(Argument) != VTYPE_STRING) RETURN0("GetHostString requires a string argument")
  394.   if( STRLEN(Argument) > 1000) RETURN0("GetHostString argument to long")
  395.  
  396.   myCopy = GetCString(Argument);
  397.   if(myCopy==0) RETURN0("malloc failed low mem")
  398.  
  399.   if(vbHostResolver==NULL) RETURN0("GetHostStringt requires vbHostResolver callback to be set")
  400.        
  401.   //we are actually going to use our own fixed size buffer for this in case its a string value to be returned..
  402.   strcpy(buf, myCopy);
  403.   retVal = vbHostResolver(buf, strlen(buf), 1024);
  404.   slen = strlen(buf);
  405.  
  406. cleanup:
  407.     besALLOC_RETURN_STRING(slen);
  408.     if(slen > 0) memcpy(STRINGVALUE(besRETURNVALUE),buf,slen);
  409.         if(myCopy) free(myCopy);
  410.         return 0;
  411.  
  412. besEND
  413.  
  414.  
  415. //ReleaseObject(obj)
  416. besFUNCTION(ReleaseObject)
  417.  
  418.         VARIABLE Argument;
  419.         besRETURNVALUE = besNEWMORTALLONG;
  420.  
  421.         if( besARGNR != 1) RETURN0("ReleaseObject takes one argument!")
  422.  
  423.         Argument = besARGUMENT(1);
  424.         besDEREFERENCE(Argument);
  425.  
  426.         if( TYPE(Argument) != VTYPE_LONG) RETURN0("ReleaseObject requires a long argument")
  427.         if( LONGVALUE(Argument) == 0) RETURN0("ReleaseObject(NULL) called")
  428.         IDispatch* IDisp = (IDispatch*)LONGVALUE(Argument);
  429.        
  430.         try{
  431.                 IDisp->Release();
  432.         }catch(...){
  433.                 RETURN0("Invalid IDisp pointer?")
  434.         }
  435.  
  436.         Argument->Value.lValue = 0;
  437.  
  438. cleanup:
  439.         return 0;
  440.  
  441. besEND
  442.  
  443. //Object CreateObject("ProgID")
  444. besFUNCTION(CreateObject)
  445.   int i;
  446.   int slen;
  447.   char *s;
  448.   char* myCopy = NULL;
  449.   LPWSTR wStr = NULL;
  450.   VARIABLE Argument;
  451.   besRETURNVALUE = besNEWMORTALLONG;
  452.   CLSID     clsid;
  453.   HRESULT       hr;
  454.   IDispatch *IDisp = NULL;
  455.  
  456.   if(com_dbg) color_printf(colors::myellow, "The number of arguments is: %ld\n",besARGNR);
  457.  
  458.   if( besARGNR != 1) RETURN0("CreateObject takes one argument!")
  459.  
  460.   Argument = besARGUMENT(1);
  461.   besDEREFERENCE(Argument);
  462.  
  463.   if( TYPE(Argument) != VTYPE_STRING) RETURN0("CreateObject requires a string argument")
  464.  
  465.   if(!initilized){
  466.           CoInitialize(NULL);
  467.           initilized = 1;
  468.   }
  469.  
  470.   myCopy = GetCString(Argument);
  471.   if(myCopy==0) RETURN0("malloc failed low mem")
  472.  
  473.   wStr = __C2W(myCopy);
  474.   if(wStr==0) RETURN0("unicode conversion failed")
  475.  
  476.   if(com_dbg) color_printf(colors::myellow,"CreateObject(%s)\n", myCopy);
  477.  
  478.   if(myCopy[0] == '{'){
  479.         hr = CLSIDFromString( wStr , &clsid); //its a string CLSID directly
  480.   }else{
  481.         hr = CLSIDFromProgID( wStr , &clsid); //its a progid
  482.   }
  483.  
  484.   if( hr != S_OK  ) RETURN0("Failed to get clsid")
  485.  
  486.   hr =  CoCreateInstance( clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch,(void**)&IDisp);
  487.   if ( hr != S_OK ){
  488.           //ok maybe its an activex exe..
  489.           hr =  CoCreateInstance( clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch,(void**)&IDisp);
  490.           if ( hr != S_OK ) RETURN0("CoCreateInstance failed does object support IDispatch?")
  491.   }
  492.  
  493.   //todo: keep track of valid objects we create for release/call sanity check latter?
  494.   //      tracking would break operation though if an embedded host used setvariable to add an obj reference..
  495.   //      unless it used an AddObject(name,pointer) method to add it to the tracker..
  496.   //      how else can we know if a random number is a valid com object other than tracking?
  497.   //      handled with a try/catch block in CallByName right now
  498.  
  499. cleanup:
  500.         LONGVALUE(besRETURNVALUE) = (int)IDisp;    
  501.         if(myCopy) free(myCopy);
  502.         if(wStr)   free(wStr);
  503.         return 0;
  504.  
  505. besEND
  506.  
  507.  
  508. // the idea behind this one is that we can use a string to embed a type specifier
  509. // to explicitly declare and cast a variable to the type we want such as "VT_I2:2"
  510. //
  511. // in testing with VB6 however, if we pass .vt = VT_I4 when vb6 expects a VT_I1 (char)
  512. // it works as long as the value is < 255, also works with VT_BOOL
  513. //
  514. // do we really need this function ? I prefer less complexity if possible.
  515. //
  516. // Note: there are many COM types, I have no plans to cover them all
  517.  
  518. bool HandleSpecial(VARIANTARG* va, char* str){
  519.  
  520.         return false; //disabled for now see notes above..
  521.  
  522.         if(str==0) return false;
  523.  
  524.         std::string s = str;
  525.          
  526.         if(s.length() < 3) return false;
  527.         if(s.substr(0,3) != "VT_") return false;
  528.        
  529.         int pos = s.find(":",0);
  530.         if(pos < 1) return false;
  531.  
  532.         std::string cmd = s.substr(0,pos);
  533.         if(s.length() < pos+2) return false;
  534.  
  535.         s = s.substr(pos+1);
  536.  
  537.         //todo implement handling of these types (there are many more than this)
  538.         if(cmd == "VT_I1"){
  539.         }else if(cmd == "VT_I2"){
  540.         }else if(cmd == "VT_I8"){
  541.     }else if(cmd == "VT_BOOL"){
  542.         }
  543.        
  544.         return true;
  545. }
  546.  
  547. /*
  548.     arguments in [] are optional, default calltype = method
  549.         callbyname object, "procname", [vbcalltype = VbMethod], [arg0], [arg1] ...
  550. */     
  551.  
  552. besFUNCTION(CallByName)
  553.  
  554.   int i;
  555.   int slen;
  556.   char *s;
  557.   int com_args = 0;
  558.   char* myCopy = NULL;
  559.   LPWSTR wMethodName = NULL;
  560.   vbCallType CallType = VbMethod;
  561.   std::list<BSTR> bstrs;
  562.   VARIANTARG* pvarg = NULL;
  563.  
  564.   VARIABLE arg_obj;
  565.   VARIABLE arg_procName;
  566.   VARIABLE arg_CallType;
  567.  
  568.   besRETURNVALUE = besNEWMORTALLONG;
  569.   LONGVALUE(besRETURNVALUE) = 0;
  570.  
  571.   g_pSt = pSt;
  572.  
  573.   if(com_dbg) color_printf(colors::myellow,"CallByName %ld args\n",besARGNR);
  574.  
  575.   if(besARGNR < 2) RETURN0("CallByName requires at least 2 args..")
  576.  
  577.   arg_obj = besARGUMENT(1);
  578.   besDEREFERENCE(arg_obj);
  579.  
  580.   if( TYPE(arg_obj) != VTYPE_LONG) RETURN0("CallByName first argument must be a long")
  581.  
  582.   arg_procName = besARGUMENT(2);
  583.   besDEREFERENCE(arg_procName);
  584.  
  585.   if( TYPE(arg_procName) != VTYPE_STRING) RETURN0("CallByName second argument must be a string")
  586.  
  587.   if( besARGNR >= 3 ){
  588.     arg_CallType = besARGUMENT(3);
  589.     besDEREFERENCE(arg_CallType);
  590.         CallType = (vbCallType)LONGVALUE(arg_CallType);
  591.   }
  592.  
  593.   myCopy = GetCString(arg_procName);
  594.   if(myCopy==0) RETURN0("malloc failed low mem")
  595.  
  596.   wMethodName = __C2W(myCopy);
  597.   if(wMethodName==0) RETURN0("unicode conversion failed")
  598.  
  599.   if( LONGVALUE(arg_obj) == 0) RETURN0("CallByName(NULL) called")
  600.   IDispatch* IDisp = (IDispatch*)LONGVALUE(arg_obj);
  601.   DISPID  dispid; // long integer containing the dispatch ID
  602.   HRESULT hr;
  603.  
  604.   // Get the Dispatch ID for the method name,
  605.   // try block is in case client passed in an invalid pointer
  606.   try{
  607.           hr = IDisp->GetIDsOfNames(IID_NULL, &wMethodName, 1, LOCALE_USER_DEFAULT, &dispid);
  608.           if( FAILED(hr) ) RETURN0("GetIDsOfNames failed")
  609.   }
  610.   catch(...){
  611.           RETURN0("Invalid IDisp pointer?")
  612.   }
  613.          
  614.   VARIANT    retVal;
  615.   DISPPARAMS dispparams;
  616.   memset(&dispparams, 0, sizeof(dispparams));
  617.  
  618.   com_args = besARGNR - 3;
  619.   if(com_args < 0) com_args = 0;
  620.    
  621.   if(com_dbg) color_printf(colors::myellow,"CallByName(obj=%x, method='%s', calltype=%d , comArgs=%d)\n", LONGVALUE(arg_obj), myCopy, CallType, com_args);
  622.  
  623.   // Allocate memory for all VARIANTARG parameters.
  624.   if(com_args > 0){
  625.          pvarg = new VARIANTARG[com_args];
  626.          if(pvarg == NULL) RETURN0("failed to alloc VARIANTARGs")
  627.   }
  628.  
  629.   dispparams.rgvarg = pvarg;
  630.   if(com_args > 0) memset(pvarg, 0, sizeof(VARIANTARG) * com_args);
  631.          
  632.   dispparams.cArgs = com_args;  // num of args function takes
  633.   dispparams.cNamedArgs = 0;
  634.  
  635.   /* map in argument values and types    ->[ IN REVERSE ORDER ]<-    */
  636.   for(int i=0; i < com_args; i++){
  637.           VARIABLE arg_x;              
  638.           arg_x = besARGUMENT(3 + com_args - i);
  639.           besDEREFERENCE(arg_x);
  640.  
  641.                 switch( TYPE(arg_x) ){ //script basic type to COM variant type
  642.  
  643.                           case VTYPE_DOUBLE:
  644.                           case VTYPE_ARRAY:
  645.                           case VTYPE_REF:
  646.                                 RETURN0("Arguments of script basic types [double, ref, array] not supported")
  647.                                 break;
  648.  
  649.                           case VTYPE_LONG:
  650.                                 pvarg[i].vt = VT_I4;
  651.                                 pvarg[i].lVal = LONGVALUE(arg_x);
  652.                                 break;
  653.                          
  654.                           case VTYPE_STRING:
  655.                                 char* myStr = GetCString(arg_x);
  656.                                
  657.                                 //peek at data and see if an explicit VT_ type was specified.. scriptbasic only supports a few types
  658.                                 if( !HandleSpecial(&pvarg[i], myStr) ){
  659.                                         //nope its just a standard string type
  660.                                         LPWSTR wStr = __C2W(myStr);
  661.                                         BSTR bstr = SysAllocString(wStr);
  662.                                         bstrs.push_back(bstr); //track these to free after call to prevent leak
  663.                                         pvarg[i].vt = VT_BSTR;
  664.                                         pvarg[i].bstrVal = bstr;
  665.                                         free(myStr);
  666.                                         free(wStr);
  667.                                 }
  668.  
  669.                                 break;                   
  670.                                
  671.           }
  672.  
  673.   }
  674.    
  675.   //invoke should not need a try catch block because IDisp is already known to be ok and COM should only return a hr result?
  676.  
  677.   //property put gets special handling..
  678.   if(CallType == VbLet){
  679.             DISPID mydispid = DISPID_PROPERTYPUT;
  680.         dispparams.rgdispidNamedArgs = &mydispid;
  681.                 dispparams.cNamedArgs = 1;
  682.                 hr=IDisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, CallType, &dispparams, NULL, NULL, NULL); //no return value arg
  683.                 if( FAILED(hr) ) RETURN0("Invoke failed")
  684.                 goto cleanup;
  685.   }
  686.  
  687.   hr=IDisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, CallType, &dispparams, &retVal, NULL, NULL);
  688.   if( FAILED(hr) ) RETURN0("Invoke failed")
  689.  
  690.   char* cstr = 0;
  691.   //map in return value to scriptbasic return val
  692.   switch(retVal.vt)
  693.   {
  694.         case VT_EMPTY: break;
  695.  
  696.         case VT_BSTR:
  697.  
  698.             cstr = __B2C(retVal.bstrVal);
  699.                 slen = strlen(cstr);
  700.                 if(com_dbg) color_printf(colors::myellow,"return value from COM function was string: %s\n", cstr);
  701.                 besALLOC_RETURN_STRING(slen);
  702.                 memcpy(STRINGVALUE(besRETURNVALUE),cstr,slen);
  703.                 free(cstr);
  704.                 break;
  705.  
  706.         case VT_I4:  /* this might be being really lazy but at least with VB6 it works ok.. */
  707.         case VT_I2:
  708.         case VT_I1:
  709.     case VT_BOOL:
  710.         case VT_UI1:
  711.         case VT_UI2:
  712.         case VT_UI4:
  713.         case VT_I8:
  714.         case VT_UI8:
  715.         case VT_INT:
  716.         case VT_UINT:
  717.         case VT_DISPATCH:
  718.  
  719.                 if(com_dbg) color_printf(colors::myellow,"return value from COM function was numeric: %d\n", retVal.lVal);
  720.         LONGVALUE(besRETURNVALUE) = retVal.lVal;
  721.                 break;
  722.  
  723.         default:
  724.                 color_printf(colors::mred,"currently unsupported VT return type: %x\n", retVal.vt);
  725.                 break;
  726.   }
  727.  
  728.  
  729. cleanup:
  730.  
  731.   for (std::list<BSTR>::iterator it=bstrs.begin(); it != bstrs.end(); ++it) SysFreeString(*it);
  732.   if(pvarg)       delete pvarg;
  733.   if(wMethodName) free(wMethodName); //return0 maybe should goto cleanup cause these would leak
  734.   if(myCopy)      free(myCopy);
  735.   return 0;
  736.  
  737. besEND
  738.  
Title: Re: COM
Post by: JRS on October 12, 2020, 02:13:56 PM
Here is your COM code in the current distribution. It seems you already have the functions needed to implement the CallByName (invoke) method. (OLE Automation)

Code: OxygenBasic
  1.  
  2.   #case capital
  3.  
  4.   typedef struct _guid
  5.   {
  6.     a as dword
  7.     b as word
  8.     c as word
  9.     d[8] as byte
  10.   } guid, GUID
  11.  
  12.   'typedef struct {
  13.  'dword d1
  14.  'word  w1,w2
  15.  'byte  b1,b2,b3,b4,b5,b6,b7,b8
  16.  '} GUID,CLSID,*refiid
  17.  '
  18.  '#define DEFINE_GUID(gu,d1,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) GUID gu<=(d1,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8)
  19.  
  20.  
  21.   'standalone definitions ad hoc
  22.  
  23.   #define __in
  24.   #define __out
  25.   #define __in_opt      optional
  26.   #define __out_opt     optional
  27.   #define __RPC__in_opt optional
  28.  
  29.   typedef int       HRESULT
  30.   typedef sys       HANDLE,HWND,WMSG,WPARAM,LPARAM
  31.   typedef dword     DWORD,ULONG,UINT
  32.   typedef qword     ULONGLONG
  33.   typedef bool      BOOL
  34.   typedef wchar     *LPCWSTR
  35.   typedef word      USHORT
  36.   typedef void      *LPVOID
  37.   typedef guid      *REFCLSID, *REFIID
  38.   typedef void      *LPVOID,*COSERVERINFO
  39.   typedef wbstring  LPOLESTR
  40.   typedef uint      LCID
  41.   typedef uint      DISPID
  42.  
  43.   typedef void      ITypeInfo 'to be defined
  44.  
  45.  
  46.   '
  47.  typedef void SPNOTIFYCALLBACK, ISpNotifyCallback, ISpNotifySink
  48.   typedef void ISpObjectToken,IStream,ISpStreamFormat,SPVOICESTATUS,SPVPRIORITY,SPEVENTENUM      
  49.   typedef void SPEVENT,SPEVENTSOURCEINFO
  50.  
  51.   'typedef sys SPEVENT,SPEVENTSOURCEINFO
  52.  
  53.   uses com\VariantUtil
  54.   '
  55.  typedef VARIANT VARIANTARG
  56.  
  57.   typedef struct tagDISPPARAMS {
  58.   VARIANTARG *rgvarg
  59.   DISPID     *rgdispidNamedArgs
  60.   UINT       cArgs
  61.   UINT       cNamedArgs
  62.   } DISPPARAMS
  63.  
  64.   typedef bstr BSTR
  65.   typedef dword *ULONG_PTR
  66.  
  67.    typedef struct tagEXCEPINFO {
  68.    WORD wCode
  69.    WORD wReserved
  70.    BSTR bstrSource
  71.    BSTR bstrDescription
  72.    BSTR bstrHelpFile
  73.    DWORD dwHelpContext
  74.    ULONG_PTR pvReserved
  75.    ULONG_PTR pfnDeferredFillIn
  76.    HRESULT scode
  77.    } EXCEPINFO
  78.  
  79.  
  80.   'from Unknwn.h
  81.  
  82.   extern virtual
  83.  
  84.   --------------
  85.   class IUnknown
  86.   ==============
  87.  
  88.     HRESULT QueryInterface(REFIID id, pvObject* ppv)
  89.     ULONG   AddRef()
  90.     ULONG   Release()
  91.  
  92.   end class
  93.  
  94.   typedef IUnknown *LPUNKNOWN
  95.  
  96.  
  97.   ---------------
  98.   class IDispatch
  99.   ===============
  100.  
  101.   extends IUnknown
  102.  
  103.   HRESULT GetIDsOfNames(
  104.   REFIID   riid,
  105.   LPOLESTR *rgszNames,
  106.   UINT     cNames,
  107.   LCID     lcid,
  108.   DISPID   *rgDispId)
  109.  
  110.   HRESULT GetTypeInfo(
  111.   UINT      iTInfo,
  112.   LCID      lcid,
  113.   ITypeInfo **ppTInfo)
  114.  
  115.   HRESULT GetTypeInfoCount(
  116.   UINT *pctinfo)
  117.  
  118.   HRESULT Invoke(
  119.   DISPID     dispIdMember,
  120.   REFIID     riid,
  121.   LCID       lcid,
  122.   WORD       wFlags,
  123.   DISPPARAMS *pDispParams,
  124.   VARIANT    *pVarResult,
  125.   EXCEPINFO  *pExcepInfo,
  126.   UINT       *puArgErr)
  127.  
  128.   end class
  129.  
  130. '#recordof idispatch
  131.  
  132.  
  133.  
  134.   extern lib "ole32.dll"
  135.  
  136.  
  137.   HRESULT CoInitialize(
  138.     __in_opt  LPVOID pvReserved
  139.   );
  140.  
  141.  
  142.   void CoUninitialize(void);
  143.  
  144.  
  145.  
  146.   HRESULT CoCreateInstance(
  147.     __in   REFCLSID rclsid,
  148.     __in   LPUNKNOWN pUnkOuter,
  149.     __in   DWORD dwClsContext,
  150.     __in   REFIID riid,
  151.     __out  LPVOID *ppv
  152.   )
  153.  
  154.  
  155.  
  156.   HRESULT CoGetClassObject(
  157.     __in      REFCLSID rclsid,
  158.     __in      DWORD dwClsContext,
  159.     __in_opt  COSERVERINFO pServerInfo,
  160.     __in      REFIID riid,
  161.     __out     LPVOID *ppv
  162.   );
  163.  
  164.  
  165.   'HRESULT __stdcall DllGetClassObject(
  166.  '  __in   REFCLSID rclsid,
  167.  '  __in   REFIID riid,
  168.  '  __out  LPVOID *ppv
  169.  ');
  170.  
  171.   'HRESULT __stdcall DllCanUnloadNow(void);
  172.  
  173.  
  174.  
  175.   HRESULT CoRegisterClassObject(
  176.     __in   REFCLSID rclsid,
  177.     __in   LPUNKNOWN pUnk,
  178.     __in   DWORD dwClsContext,
  179.     __in   DWORD flags,
  180.     __out  LPDWORD lpdwRegister
  181.   );
  182.  
  183.  
  184.  
  185.   HRESULT CoRevokeClassObject(
  186.     __in  DWORD dwRegister
  187.   );
  188.  
  189.  
  190.  
  191.  
  192.   end extern
  193.  
  194.  
  195.   'CLSCTX_INPROC_SERVER            = 0x1
  196.  'CLSCTX_INPROC_HANDLER           = 0x2
  197.  'CLSCTX_LOCAL_SERVER             = 0x4
  198.  'CLSCTX_INPROC_SERVER16          = 0x8
  199.  'CLSCTX_REMOTE_SERVER            = 0x10
  200.  '
  201.  #define context 7
  202.  
  203.   sys Err
  204.  

Code: OxygenBasic
  1. #case capital
  2.  
  3. typedef quad  longlong, LONGLONG
  4. typedef qword ulonglong, ULONGLONG
  5. typedef void *pvoid, PVOID
  6. typedef int SCODE
  7. typedef double DATE
  8. typedef word USHORT
  9. typedef short SHORT,VARIANT_BOOL
  10. typedef byte BYTE
  11. typedef char CHAR
  12. typedef word WORD
  13. typedef long LONG
  14. typedef int INT
  15. typedef uint UINT
  16. typedef dword ULONG
  17. typedef float FLOAT
  18. typedef double DOUBLE
  19. typedef bstring BSTR
  20.  
  21. typedef void IUnknown,IDispatch
  22. typedef void IRecordInfo
  23.  
  24.  
  25. type DECIMAL
  26.    WORD wReserved
  27.    BYTE scale
  28.    BYTE sign
  29.    ULONG Hi32
  30.    ULONGLONG Lo64
  31. end type
  32.  
  33. type CY
  34.   LONGLONG int64
  35. end type
  36.  
  37. type SAFEARRAYBOUND
  38.    ULONG cElements
  39.    LONG lLbound
  40. end type
  41.  
  42. type SAFEARRAY
  43.   USHORT         cDims
  44.   USHORT         fFeatures
  45.   ULONG          cbElements
  46.   ULONG          cLocks
  47.   PVOID          pvData
  48.   SAFEARRAYBOUND rgsabound[1]
  49. end type
  50.  
  51.  
  52. enum VARTYPE {
  53.    VT_EMPTY = 0x0000,
  54.    VT_NULL = 0x0001,
  55.    VT_I2 = 0x0002,
  56.    VT_I4 = 0x0003,
  57.    VT_R4 = 0x0004,
  58.    VT_R8 = 0x0005,
  59.    VT_CY = 0x0006,
  60.    VT_DATE = 0x0007,
  61.    VT_BSTR = 0x0008,
  62.    VT_DISPATCH = 0x0009,
  63.    VT_ERROR = 0x000A,
  64.    VT_BOOL = 0x000B,
  65.    VT_VARIANT = 0x000C,
  66.    VT_UNKNOWN = 0x000D,
  67.    VT_DECIMAL = 0x000E,
  68.    VT_I1 = 0x0010,
  69.    VT_UI1 = 0x0011,
  70.    VT_UI2 = 0x0012,
  71.    VT_UI4 = 0x0013,
  72.    VT_I8 = 0x0014,
  73.    VT_UI8 = 0x0015,
  74.    VT_INT = 0x0016,
  75.    VT_UINT = 0x0017,
  76.    VT_VOID = 0x0018,
  77.    VT_HRESULT = 0x0019,
  78.    VT_PTR = 0x001A,
  79.    VT_SAFEARRAY = 0x001B,
  80.    VT_CARRAY = 0x001C,
  81.    VT_USERDEFINED = 0x001D,
  82.    VT_LPSTR = 0x001E,
  83.    VT_LPWSTR = 0x001F,
  84.    VT_RECORD = 0x0024,
  85.    VT_INT_PTR = 0x0025,
  86.    VT_UINT_PTR = 0x0026,
  87.    VT_ARRAY = 0x2000,
  88.    VT_BYREF = 0x4000
  89. }
  90.  
  91.  
  92. type VARIANT
  93.   DECIMAL decVal
  94.   =
  95.   WORD    vt 'VARTYPE
  96.  WORD    wReserved1
  97.   WORD    wReserved2
  98.   WORD    wReserved3
  99.   + 8
  100.   PVOID       pvRecord
  101.   IRecordInfo *pRecInfo
  102.   =
  103.   LONGLONG     llVal
  104.   =
  105.   LONG         lVal
  106.   =
  107.   BYTE         bVal
  108.   =
  109.   SHORT        iVal
  110.   =
  111.   FLOAT        fltVal
  112.   =
  113.   DOUBLE       dblVal
  114.   =
  115.   VARIANT_BOOL *pboolVal
  116.   =
  117.   SCODE        *pscode
  118.   =
  119.   CY           *pcyVal
  120.   '=
  121.  DATE         *pdate
  122.   =
  123.   BSTR         *pbstrVal
  124.   =
  125.   IUnknown     *punkVal
  126.   =
  127.   IDispatch    *pdispVal
  128.   =
  129.   SAFEARRAY    **pparray
  130.   =
  131.   'VARIANT      *pvarVal 'itr VARIANT
  132.  'VARIANT V
  133.  =
  134.   PVOID        byref
  135.   =
  136.   CHAR         cVal
  137.   =
  138.   USHORT       uiVal
  139.   =
  140.   ULONG        ulVal
  141.   =
  142.   ULONGLONG    ullVal
  143.   =
  144.   INT          intVal
  145.   =
  146.   UINT         uintVal
  147.   =
  148.   DECIMAL      *pdecVal
  149.   =
  150.   CHAR         *pcVal
  151.   =
  152.   USHORT       *puiVal
  153.   =
  154.   ULONG        *pulVal
  155.   =
  156.   ULONGLONG    *pullVal
  157.   =
  158.   INT          *pintVal
  159.   =
  160.   UINT         *puintVal
  161. end type
  162.  
  163. '#recordof VARIANT
  164.  
  165.  
  166. 'oleaut
  167. 'https://docs.microsoft.com/en-us/windows/desktop/api/oleauto/nf-oleauto-variantinit
  168.  
Title: Re: COM
Post by: JRS on October 13, 2020, 08:22:15 AM
Here is a Rosetta code challenge using OLE automation.

https://rosettacode.org/wiki/OLE_Automation
Title: Re: COM
Post by: JRS on October 13, 2020, 12:26:38 PM
Charles,

I'm going to try and convert your SAPI COM DLLC example to see if I can get it to load my VB6 ActiveX DLL form.

Here is the DLLC SAPI code. Can you show me how I can CREATE an instance of my COM DLL?

Code: OxygenBasic
  1. 'DECLARE MORE PROCEDURES FOR COM
  2.  
  3. CoInitialize     = dllproc(ole32,"CoInitialize (i)")
  4. CoUninitialize   = dllproc(ole32,"CoUninitialize (i)")
  5. CoCreateInstance = dllproc(ole32,"CoCreateInstance i=(t*ObjGuid ,i pUnkOuter,i context, t*IspGuid, i*Iface)" )
  6.  
  7. 'TRAP ERRORS
  8.  
  9. if dllerrc() then
  10.   print "DLLC logged errors: " & dllerrc() & "\n" & dllerrs()
  11.   goto ending
  12. end if
  13.  
  14.  
  15. 'COM SPEECH
  16.  
  17. VoiceObjGuid = dllguid("96749377-3391-11D2-9EE3-00C04F797396")
  18. ISpVoiceGuid = dllguid("6C44DF74-72B9-4992-A1EC-EF996E0422D4")
  19. Context      = 7
  20. pUnkOuter    = 0
  21. Voice        = 0
  22. Release      = dllmeth( 2,"Release i=()")
  23. Speak        = dllmeth(20,"Speak i=(z*pwcs,i flags,i pulstreamno)")
  24. WaitUntilDone= dllmeth(32,"WaitUntilDone i=(i)")
  25. print dllreco(speak)
  26. Text         = dllwstr("Hello Everyone!\0")
  27. hr=0
  28. dllcall(CoInitialize,0)
  29. hr=dllcall(CoCreateInstance, VoiceObjGuid, pUnkouter, Context, ISpVoiceGuid, Voice)
  30. if (hr=0) then
  31.   print "connected to voice\n\n"
  32.   print dllastr(Text) & "\n\n"
  33.   dllcobj(Voice,Speak,Text,0,0)
  34.   dllcobj(Voice,WaitUntilDone,0xFFFFFFFF)
  35.   dllcobj(Voice,Release)
  36. else
  37.   print "SAPI Error " & format("%x",hr) & "\n\n"
  38. end if
  39. dllcall(CoUninitialize)
  40.  

Here the ScriptBasic code to load the OCX form.

Code: Script BASIC
  1. obj = COM::CREATE(:SET, "MASLink.OCXForm")
  2.  

Attached is the VB6 ActiveX DLL you will need to register with regsvr32.

Note: The FlexGrid won't be sized or data populated with the raw load.
Title: Re: COM
Post by: JRS on October 13, 2020, 02:41:59 PM
You will also need the updated common controls OCX the form uses. It doesn't need to be registered.
Title: Re: COM
Post by: JRS on October 13, 2020, 06:21:45 PM
I forgot the enhanced FlexGrid is a separate OCX the form uses.  It doesn't need to be registered either.

This is the minimal statements needed to instantiate the form.

Code: Script BASIC
  1. IMPORT COM.sbi
  2.  
  3. obj = COM::CREATE(:SET, "MASLink.OCXForm")
  4.  
  5. COM::CBN obj, "ShowOCXForm"
  6.  
  7. COM::RELEASE obj
  8.  

Attached it what it should like if you get it working in O2.

 
Title: Re: COM
Post by: Charles Pegge on October 14, 2020, 05:26:04 AM
A useful reference for creating COM clients and servers:

COM in plain C
Jeff Glatt
28 Mar 2006
https://www.codeproject.com/Articles/13601/COM-in-plain-C
parts
https://www.codeproject.com/search.aspx?q=COM+in+plain+C&x=0&y=0&sbo=kw
Title: Re: COM
Post by: JRS on October 14, 2020, 06:11:09 AM
Outstanding resources Charles!

They really help understanding OLE automation.

Reminds me of your DLLC FFI interface.

Lua FFI (https://luajit.org/ext_ffi.html)
Title: Re: COM
Post by: JRS on October 18, 2020, 08:59:02 AM
Charles,

Can you post an example of using Invoke() with your existing COM offering?
Title: Re: COM
Post by: Charles Pegge on October 18, 2020, 09:39:23 PM
Hi John,

I don't have any idispatch examples

https://docs.microsoft.com/en-us/previous-versions/windows/desktop/automat/implementing-the-idispatch-interface
Title: Re: COM
Post by: JRS on October 18, 2020, 10:03:40 PM
There doesn"t look like there is much interest in COM here so I'll just have to be happy with what I have going with ScriptBasic.

I might try to take Dave's C COM extension module code and make it a generic DLL one could call from O2.
Title: Re: COM
Post by: JRS on October 23, 2020, 01:58:40 PM
Charles,

Do you see anything in Jose's CallByName PowerBasic code that wouldn't port to O2?

Code: [Select]
' EXCEPINFO structure
' ********************************************************************************************
TYPE EXCEPINFO
   wCode AS WORD               ' An error code describing the error.
   wReserved AS WORD           ' Reserved
   bstrSource AS DWORD         ' Source of the exception.
   bstrDescription AS DWORD    ' Textual description of the error.
   bstrHelpFile AS DWORD       ' Help file path.
   dwHelpContext AS DWORD      ' Help context ID.
   pvReserved AS DWORD         ' Reserved.
   pfnDeferredFillIn AS DWORD  ' Pointer to function that fills in Help and description info.
   scode AS DWORD              ' An error code describing the error.
END TYPE
' ********************************************************************************************

' ********************************************************************************************
' Helper function to calculate the VTable address.
' ********************************************************************************************
FUNCTION TB_VTableAddress (BYVAL pthis AS DWORD, BYVAL dwOffset AS DWORD) AS DWORD
   LOCAL ppthis AS DWORD PTR
   LOCAL pvtbl AS DWORD PTR
   LOCAL ppmethod AS DWORD PTR
   ppthis = pthis
   pvtbl = @ppthis
   ppmethod = pvtbl + dwOffset
   FUNCTION = @ppmethod
END FUNCTION
' ********************************************************************************************

' ********************************************************************************************
' Maps a single member to a corresponding DispID, which can be used on subsequent calls to
' IDispatch_Invoke.
' Parameters:
' riid
'   Reserved for future use. Must be IID_NULL.
' strName
'   Name to be mapped.
' rgDispId
'   Retrieved DispID value.
' Return Value:
'   The return value obtained from the returned HRESULT is one of the following:
'   %S_OK                Success
'   %E_OUTOFMEMORY       Out of memory
'   %DISP_E_UNKNOWNNAME  One or more of the names were not known. The returned array of DISPIDs
'                        contains DISPID_UNKNOWN for each entry that corresponds to an unknown name.
'   %DISP_E_UNKNOWNLCID  The locale identifier (LCID) was not recognized.
' ********************************************************************************************
DECLARE FUNCTION Proto_IDispatch_GetIDOfName (BYVAL pthis AS DWORD, BYREF riid AS GUID, BYref rgszNames AS string, BYVAL cNames AS DWORD, BYVAL lcid AS DWORD, BYref rgdispid AS long) AS DWORD
FUNCTION IDispatch_GetIDOfName (BYVAL pthis AS DWORD, BYref strName AS string, BYref rgdispid AS long) AS DWORD
   LOCAL HRESULT AS DWORD
   LOCAL pmethod AS DWORD
   local riid as guid
   if pthis = 0 then exit function
   pmethod = TB_VTableAddress (pthis, 20)
   CALL DWORD pmethod USING Proto_IDispatch_GetIDOfName (pthis, riid, strName, 1, 0, rgdispid) TO HRESULT
   FUNCTION = HRESULT
END FUNCTION
' ********************************************************************************************

' ********************************************************************************************
' Provides access to properties and methods exposed by an object. The dispatch function DispInvoke
' provides a standard implementation of IDispatch_Invoke.
' Parameters:
' dispIdMember
'   Identifies the member. Use GetIDsOfNames or the object's documentation to obtain the dispatch identifier.
' riid
'    Reserved for future use. Must be IID_NULL.
' lcid
'   The locale context in which to interpret arguments. The lcid is used by the GetIDsOfNames
'   function, and is also passed to IDispatch_Invoke to allow the object to interpret its
'   arguments specific to a locale.
'   Applications that do not support multiple national languages can ignore this parameter.
' wFlags
'   Flags describing the context of the Invoke call, include:
'     %DISPATCH_METHOD
'       The member is invoked as a method. If a property has the same name, both this and the
'       %DISPATCH_PROPERTYGET flag may be set.
'     %DISPATCH_PROPERTYGET
'       The member is retrieved as a property or data member.
'     %DISPATCH_PROPERTYPUT
'       The member is changed as a property or data member.
'     %DISPATCH_PROPERTYPUTREF
'       The member is changed by a reference assignment, rather than a value assignment. This
'       flag is valid only when the property accepts a reference to an object.
' pDispParams
'   Pointer to a structure containing an array of arguments, an array of argument DISPIDs for
'   named arguments, and counts for the number of elements in the arrays.
' pVarResult
'   Pointer to the location where the result is to be stored, or NULL if the caller expects no
'   result. This argument is ignored if DISPATCH_PROPERTYPUT or DISPATCH_PROPERTYPUTREF is specified.
' pExcepInfo
'   Pointer to a structure that contains exception information. This structure should be filled
'   in if DISP_E_EXCEPTION is returned. Can be NULL.
' puArgErr
'   The index within rgvarg of the first argument that has an error. Arguments are stored in
'   pDispParams->rgvarg in reverse order, so the first argument is the one with the highest index
'   in the array. This parameter is returned only when the resulting return value is
'   %DISP_E_TYPEMISMATCH or %DISP_E_PARAMNOTFOUND. This argument can be set to null.
' Return Value:
'   The return value obtained from the returned HRESULT is one of the following:
'   %S_OK                     Success
'   %DISP_E_BADPARAMCOUNT     The number of elements provided to DISPPARAMS is different from the
'                             number of arguments accepted by the method or property.
'   %DISP_E_BADVARTYPE        One of the arguments in rgvarg is not a valid variant type.
'   %DISP_E_EXCEPTION         The application needs to raise an exception. In this case, the
'                             structure passed in pExcepInfo should be filled in.
'   %DISP_E_MEMBERNOTFOUND    The requested member does not exist, or the call to Invoke tried to
'                             set the value of a read-only property.
'   %DISP_E_NONAMEDARGS       This implementation of IDispatch does not support named arguments.
'   %DISP_E_OVERFLOW          One of the arguments in rgvarg could not be coerced to the specified type.
'   %DISP_E_PARAMNOTFOUND     One of the parameter DISPIDs does not correspond to a parameter on
'                             the method. In this case, puArgErr should be set to the first
'                             argument that contains the error.
'   %DISP_E_TYPEMISMATCH      One or more of the arguments could not be coerced. The index within
'                             rgvarg of the first parameter with the incorrect type is returned
'                             in the puArgErr parameter.
'   %DISP_E_UNKNOWNINTERFACE  The interface identifier passed in riid is not IID_NULL.
'   %DISP_E_UNKNOWNLCID       The member being invoked interprets string arguments according to
'                             the LCID, and the LCID is not recognized. If the LCID is not needed
'                             to interpret arguments, this error should not be returned.
'   %DISP_E_PARAMNOTOPTIONAL  A required parameter was omitted.
' ********************************************************************************************
FUNCTION IDispatch_Invoke (BYVAL pthis AS DWORD, BYVAL dispidMember AS LONG, BYREF riid AS GUID, _
BYVAL lcid AS DWORD, BYVAL wFlags AS WORD, BYREF pdispparams AS DISPPARAMS, BYREF pvarResult AS VARIANT, _
BYREF pexcepinfo AS EXCEPINFO, BYREF puArgErr AS DWORD) AS DWORD
   LOCAL HRESULT AS DWORD
   LOCAL pmethod AS DWORD
   if pthis = 0 then exit function
   pmethod = TB_VTableAddress (pthis, 24)
   CALL DWORD pmethod USING IDispatch_Invoke (pthis, dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr) TO HRESULT
   FUNCTION = HRESULT
END FUNCTION
' ********************************************************************************************

' ********************************************************************************************
' CallByName
' ********************************************************************************************
function TB_CallByName ( _
    BYVAL pthis AS DWORD, _                                    ' *IDispatch
    BYVAL vNameOrId AS VARIANT, _                              ' Name or identifier
    byval callType as long, _                                  ' Call type
    byref vParams() as VARIANT, _                              ' Array of variants
    byref vResult as variant, _                                ' Variant result
    byref pex as EXCEPINFO _                                   ' EXCEPINFO
    ) EXPORT AS LONG                                           ' Error code

    dim dw_puArgErr AS DWORD, DISPID_PROPERTYPUT AS LONG, IID_NULL AS GUID
    dim vArgs(0) AS VARIANT, udt_DispParams AS DISPPARAMS
    dim strName as string, DispID as long, nParams as long, i as long, idx as long

    ' Check for null pointer
    if pthis = 0 then function = -1 : exit function

    ' Get the DispID
    if variantvt(vNameOrId) = %VT_BSTR then
       strName = ucode$(variant$(vNameOrId))
       IDispatch_GetIDOfName pthis, strName, DispID
    else
       DispID = variant#(vNameOrId)
    end if

    ' Copy the array in reversed order
    if varptr(vParams()) then
       nParams = ubound(vParams) - lbound (vParams) + 1
       if nParams > 0 then
          redim vArgs(nParams - 1)
          idx = nParams - 1
          for i = Lbound(vParams) to Ubound(vParams)
             if variantvt(vParams(i)) = %VT_EMPTY then
                vArgs(idx) = error %DISP_E_PARAMNOTFOUND
             else
                vArgs(idx) = vParams(i)
             end if
             DECR idx
             if idx < 0 then exit for
          next
       end if
   end if

   if CallType = 4 or CallType = 8 then  ' %DISPATCH_PROPERTYPUT and %DISPATCH_PROPERTYPUTREF
      DISPID_PROPERTYPUT = -3
      udt_DispParams.CountNamed = 1
      udt_DispParams.NamedDispId = VARPTR(DISPID_PROPERTYPUT)
   end if

   udt_DispParams.CountArgs = nParams
   if nParams > 0 then udt_DispParams.VariantArgs = varptr(vArgs(0))

   function = IDispatch_Invoke(pthis, DispID, IID_NULL, 0, CallType, udt_DispParams, vResult, pex, dw_puArgErr)

END function
' ********************************************************************************************

Example Use
Code: [Select]
#COMPILE EXE
#DIM ALL
#DEBUG ERROR ON
#INCLUDE "WIN32API.INC"
#INCLUDE "CallByName.INC"

%adOpenKeyset     = &H00000001
%adLockOptimistic = &H00000003
%adCmdText        = &H00000001

' ********************************************************************************************
' Main
' ********************************************************************************************
function pbmain

   local oCon as dispatch
   local oRec as dispatch
   local hr as dword
   local pex as EXCEPINFO
   local vResult as VARIANT
   local bstrlen as long
   dim vParams(0) as variant
   
   ' Creates a connection instance
   set oCon = new dispatch in "ADODB.Connection"
   if isfalse isobject(oCon) then goto Terminate
   
   redim vParams(3)  ' Four parameters (0:3) - Empty variants are considered as optional parameters
   vParams(0) = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=f:\ffado\biblio.mdb"  ' <-- change as needed
   hr = TB_CallByName(objptr(oCon), "Open", %DISPATCH_METHOD, vParams(), BYVAL %NULL, BYVAL %NULL)

   ' Creates a recordset instance
   set oRec = new dispatch in "ADODB.Recordset"
   if isfalse isobject(oRec) then goto Terminate
   
   ' Opens the recordset
   redim vParams(4)  ' Five parameters (0:4)
   vParams(0) = "SELECT TOP 20 * FROM Authors ORDER BY Author"
   set vParams(1) = oCon  ' This is a dispatch variable, so we have to assign it using SET
   vParams(2) = %adOpenKeyset
   vParams(3) = %adLockOptimistic
   vParams(4) = %adCmdText
   hr = TB_CallByName(objptr(oRec), "Open", %DISPATCH_METHOD, vParams(), BYVAL %NULL, BYVAL %NULL)

   do
      hr = TB_CallByName(objptr(oRec), "Eof", %DISPATCH_PROPERTYGET, byval %NULL, vResult, BYVAL %NULL)
      if variant#(vResult) then exit do
      redim vParams(0)  ' One parameter
      vParams(0) = "Author"
      hr = TB_CallByName(objptr(oRec), "Collect", %DISPATCH_PROPERTYGET, vParams(), vResult, BYVAL %NULL)
      print variant$(vResult)
      ' Fetch the next row
      hr = TB_CallByName(objptr(oRec), "MoveNext", %DISPATCH_METHOD, byval %NULL, BYVAL %NULL, BYVAL %NULL)
   loop
   
   
Terminate:

   ' Close the reordset
   hr = TB_CallByName(objptr(oRec), "Close", %DISPATCH_METHOD, BYVAL %NULL, BYVAL %NULL, BYVAL %NULL)
   ' Close the connection
   hr = TB_CallByName(objptr(oCon), "Close", %DISPATCH_METHOD, BYVAL %NULL, BYVAL %NULL, BYVAL %NULL)

   ' Cleanup
   set oRec = nothing
   set oCon = nothing

   waitkey$

end function
' ********************************************************************************************
Title: Re: COM
Post by: JRS on October 24, 2020, 01:09:02 PM
Brian,

If you can get CallByName working in O2 I will buy a copy of your BASIC.
Title: Re: COM
Post by: Charles Pegge on October 24, 2020, 03:22:02 PM
Hi John,

What does this do? where is ADODB?

Code: [Select]
  set oCon = new dispatch in "ADODB.Connection"
Title: Re: COM
Post by: JRS on October 24, 2020, 08:06:55 PM
ADO is a Windows DB layer COM object. I would use Excel for your testing.

https://www.allbasic.info/forum/index.php?topic=414.msg4520#msg4520
Title: Re: COM
Post by: Charles Pegge on October 25, 2020, 11:01:24 AM
How is VBNEW implemented. I see no GUIDs, so are you using ATL to locate the interfaces?
Title: Re: COM
Post by: JRS on October 25, 2020, 11:09:42 AM
I would use Dave Zimmer's C CallByName example. He even used the same SAPI example you did in DLLC. (see code in comments)

The SB COM extension module let's you use a GUID or it's registered name.

Here is Dave's original SB COM repository. It doesn't have the improvements Mike made.

Dave's SB COM (https://github.com/dzzie/ScriptBasic_Control)
Title: Re: COM
Post by: Charles Pegge on October 25, 2020, 06:51:00 PM
Thanks John,  this is what I was after:

Code: [Select]
if(myCopy[0] == '{'){
hr = CLSIDFromString( wStr , &clsid); //its a string CLSID directly
  }else{
hr = CLSIDFromProgID( wStr , &clsid); //its a progid
  }
Title: Re: COM
Post by: JRS on October 26, 2020, 12:22:15 AM
Great!

The C code I posted above has the improvements Mike made that are not in Dave's repo.

FYI: ADO is the OLE automation version of ODBC.

Note: regsvr32 needs to run in an administrator console when registering controls.
Title: Re: COM
Post by: JRS on November 02, 2020, 07:09:44 AM
Hi Charles,

Are you still looking at a CallByName library for O2?

This is my last attempt at trying to get something going here on the forum.

It's getting towards the end of the year and the forums I host are lacking any activity. Please give me a reason to keep spending money so others have a resouce to go to.
Title: Re: COM
Post by: Charles Pegge on November 02, 2020, 02:30:25 PM
Hi John,

Unfortunately, I seem to have hit a brick wall with the iDispatch interface. I have tried it on the SAPI voice and also the WebBrowser control accessed via ATL. But it appears defunct in both cases, at least in my Windows10 environment.

But I was able to establish low-level COM control on Aurel's ATL webBrowser Control demo - attaching GoForward/GoBack/GoHome to the Window Buttons.
Title: Re: COM
Post by: JRS on November 02, 2020, 02:59:57 PM
Thanks for giving it try.

If I find some time I will try to convert the SB COM extension to something generic O2 can use.

Posting your code and getting a few more eyes looking at it might discover the problem.
Title: Re: COM
Post by: JRS on November 03, 2020, 08:35:09 AM
Charles,

Writing a BASIC compiler, a MD5 function in ScriptBasic and the magic of DLLC makes it hard to imagine you getting stuck on anything.
Title: Re: COM
Post by: Charles Pegge on November 03, 2020, 12:05:21 PM
I think the problem is Windows10.
Title: Re: COM
Post by: JRS on November 03, 2020, 01:22:16 PM
I'm running Windows 10 Pro and have no issues with Dave's code.

Were you able to get it to work on Win7 or Vista?

Dave has a debug print setup with the SB extension. I will enable the flag and post the results of calling the SAPI example you are trying to get working. Who knows, it might help.
Title: Re: COM
Post by: JRS on November 03, 2020, 10:59:43 PM
Here is the debug trace from the SB COM extension module and the SAPI example.

Code: Script BASIC
  1. IMPORT com.sbi
  2.  
  3. voice = COM::CREATE(:SET, "SAPI.SpVoice")
  4. COM::CBN(voice, "speak", :CALL, "Hello World")
  5.  
  6. COM::RELEASE(voice)
  7.  

Title: Re: COM
Post by: Charles Pegge on November 05, 2020, 12:47:45 PM
Thanks John,

My IDispatch functions were declared in the wrong order, so now we are back in business.
Title: Re: COM
Post by: JRS on November 05, 2020, 12:57:50 PM
I never lost confidence in you. Persistence is the key to all rewards.

Having COM/OLE automation support in O2 will open up a huge array of plug in functionality too difficult and time consuming done at a low level.

I just made my computer talk with 3 lines of code.
Title: Re: COM
Post by: Brian Alvarez on November 07, 2020, 09:08:10 PM
Thanks John,

My IDispatch functions were declared in the wrong order, so now we are back in business.

Good stuff!  ;D
Title: Re: COM
Post by: Charles Pegge on November 10, 2020, 01:52:41 PM
My proposed syntax for COM automation:
Code: [Select]
  uses  COM/COMutil
  CoInitialize null
  CreateInterfaceByName "SAPI.SpVoice" voice
  CallByName( voice, "speak", {"Hello World"} )
  ...
  voice.Release
  CoUninitialize

CallByName converts all arguments into BSTRs, and also supports return values with an optional extra param.

Title: Re: COM
Post by: JRS on November 10, 2020, 03:59:24 PM
Looking good!

How does one indicate a LET, GET and SET (object ref)?

The above assumes a method call.

Not sure making all passed arguments BStr will work. I have many methods that are expecting a long/int.

Minimum you need to be able to pass strings, long/int, floats and iDispatch pointers.

How about the following syntax?

LetByName()
GetByName()
SetByName()

com_arg = VT_STRING::"Hello World"

Variant casting would be nice.
Title: Re: COM
Post by: JRS on November 11, 2020, 06:57:56 PM
Here is a ScriptBasic COM script to get the first customer from Sage 100 accounting software. It's a good example as it uses most of the COM syntax.


This BOI script example gets the first customer in the table / file and displays the selected columns.

Code: Script BASIC
  1. ' BOI - first customer - selected columns
  2.  
  3. IMPORT BOI.sbi
  4.  
  5. oscript = BOI::CREATE(:SET, "ProvideX.Script")
  6. BOI::CBN oScript, "Init", :CALL, "C:\\Sage\\Sage 100 Advanced ERP\\MAS90\\HOME"
  7. osession = BOI::CBN(oscript, "NewObject", :SET, "SY_Session")
  8. BOI::CBN osession, "nSetUser", :CALL, "JRS", "MyPassword"
  9. BOI::CBN osession, "nsetcompany", :CALL, "ABC"
  10. BOI::CBN osession, "nSetDate", :CALL, "A/R", "20171218"
  11. BOI::CBN osession, "nSetModule", :CALL, "A/R"
  12. ocust = BOI::CBN(oscript, "NewObject", :SET, "AR_Customer_svc", osession)
  13. BOI::CBN ocust, "nMoveFirst"
  14. CustomerNo$ = BOI::CBN(ocust, "sCustomerNo", :GET)
  15. CustomerName$ = BOI::CBN(ocust, "sCustomerName", :GET)
  16. City$ = BOI::CBN(ocust, "sCity", :GET)
  17. State$ = BOI::CBN(ocust, "sState", :GET)
  18. TelephoneNo$ = BOI::CBN(ocust, "sTelephoneNo", :GET)
  19. BOI::CBN ocust, "DropObject"
  20. BOI::CBN osession, "DropObject"
  21. BOI::RELEASE oscript
  22.  
  23. PRINT "Customer:  ", CustomerNo$, "  ", CustomerName$, "  ", City$, "  ", State$, "  ", TelephoneNo$, "\n"
  24.  

Output

C:\ScriptBASIC\examples>scriba firstcust.sb
Customer:  ABF  American Business Futures  Milwaukee  WI  (414) 555-4787

C:\ScriptBASIC\examples>


Note: BOI is the same reference as COM. (Different include file)
Title: Re: COM
Post by: JRS on November 11, 2020, 08:16:03 PM
Here is how my ScriptBasic and VB6 project turned out.

(Windows 10 Pro and Windows Server 2012 R2)

I even have VB6 IDE running on Windows Server 2012 R2.

Title: Re: COM
Post by: Charles Pegge on November 11, 2020, 09:28:38 PM
Hi John,
Further proposed:
Code: [Select]
  uses  COM/COMutil
  CoInitialize null

  CreateInterfaceByName "SAPI.SpVoice" voice
  '
  BSTR r
  GetByName(voice,"volume",r) : print "vol " r
  GetByName(voice,"rate",r) : print "rate " r
  CallByName( voice,"speak", BSTR {"Hello World"} )
  LetByName(voice,"volume",7)
  LetByName(voice,"rate",3)
  CallByName( voice,"speak", BSTR {"Hello World"} )
  '
  VARIANT va
  va.vt=VT_BSTR : va.bstrval="Hello Sky"
  CallByNameV( voice, METHOD,"speak", va,1 )
  del va.bstrval
  'print err

  'CallByNameS( voice,1, "speak", BSTR {"Hello World"},countof )
  ...
  voice.Release
  CoUninitialize
Title: Re: COM
Post by: JRS on November 12, 2020, 06:26:01 AM
That looks great Charles!

Can't wait to give it a try when your ready to release something.
Title: Re: COM
Post by: JRS on November 12, 2020, 11:06:38 AM
I'm looking for a way to call an ActiveX DLL method (OLE server) as a self running 'thread' returning once it has been called.

I wonder if I put my code in the OnLoad function that would do it?
Title: Re: COM
Post by: JRS on November 12, 2020, 09:06:21 PM
I found a multi-threading VB example to display a Mandelbrot. (zoomable - select a section of the image and it will display that region)

I attached the source zip as well.
Title: Re: COM
Post by: JRS on November 13, 2020, 09:48:43 PM
Theo posted this on the JRS form. It's a public domain COM/OLE automation DLL but interface specific. (SAPI, Excel, Word and ADODB) It has Liberty Basic and C source examples. It might be worth taking a peek.

Quote
TTS DLL and TTW DLL are Dynamically Linked Libraries that contain 17 function calls between them. The function calls
cover Text-To-Speech and Text-To-WAV.
Title: Re: COM
Post by: JRS on November 14, 2020, 09:30:33 PM
Charles,

I tried to used SB/DLLC to call the tts.dll library. It gets as far as Begin_Speech and drops to the command prompt.  :(

Here is the C declares.

Code: C
  1. extern int Begin_Speech(DWORD);
  2. extern BOOL Speak_String(int,char *,int,int,int,int,DWORD,int,DWORD,int,DWORD);
  3. extern void End_Speech(void);
  4.  


Code: Script BASIC
  1. ' DLLC SAPI5
  2.  
  3. IMPORT dllc.sbi
  4.  
  5. sapi = dllfile("tts.dll")
  6.  
  7. Begin_Speech = dllproc(sapi, "Begin_Speech i = (i bufsz)")
  8. Speak_String = dllproc(sapi, "Speak_String i = (i usetxt, c *words, i clrbuf, i vidx, i nttsa, i udv, i vol, i udp, i pitch, i uds, i speed)")
  9. End_Speech   = dllproc(sapi, "End_Speech ()")
  10.  
  11. TEXT_BUFFER_SIZE = 5000
  12. USE_TEXT = 1
  13. CLEAR_TEXT_BUFFER = 1
  14. NEW_TTS_ALWAYS = 1
  15. USE_DEFAULT_VOLUME = 1
  16. USE_DEFAULT_PITCH = 1
  17. USE_DEFAULT_SPEED = 1
  18.  
  19. rtn = 0
  20. currentVoice = 0
  21. volume = 0
  22. pitch = 0
  23. speed = 0
  24.  
  25. rtn = dllcall(Begin_Speech, TEXT_BUFFER_SIZE)
  26. PRINT "Begin_Speech = ", rtn,"\n"
  27.  
  28. greeting$ = "Hello World"
  29.  
  30. rtn = dllcall(Speak_String,USE_TEXT,greeting$,CLEAR_TEXTSPEECH_BUFFER,currentVoice,NEW_TTS_ALWAYS,USE_DEFAULT_VOLUME,volume,USE_DEFAULT_PITCH,pitch,USE_DEFAULT_SPEED,speed)
  31. PRINT "Speak_String = ", rtn, "\n"
  32.  
  33. dllcall(End_Speech)
  34.  
  35. dllfile
  36.  
Title: Re: COM
Post by: JRS on November 15, 2020, 05:35:05 PM
I was able to get the Speak_String function to return an error.

Code: Script BASIC
  1. ' DLLC SAPI5
  2.  
  3. IMPORT dllc.sbi
  4.  
  5. sapi = dllfile("tts.dll")
  6.  
  7. Begin_Speech = dllproc(sapi, "Begin_Speech i = (i bufsz)")
  8. Speak_String = dllproc(sapi, "Speak_String i = (i usetxt, c *vtxt, i clrbuf, i vidx, i nttsa, i udv, i vol, i udp, i pitch, i uds, i speed)")
  9. End_Speech   = dllproc(sapi, "End_Speech ()")
  10.  
  11.  
  12. rtn = dllcall(Begin_Speech, 5000)
  13. PRINT "Begin_Speech = ", rtn,"\n"
  14.  
  15. rtn = dllcall(Speak_String,0,"Hello World",1,0,1,0,0,0,0,0,0)
  16. PRINT "Speak_String = ", rtn, "\n"
  17.  
  18. dllcall(End_Speech)
  19.  
  20. dllfile
  21.  


C:\ScriptBASIC\examples>sbc dllc_com.sb
Begin_Speech = 1
Speak_String = 0

C:\ScriptBASIC\examples>


I can't believe Theo would post a library reference and not even test if it works on current OS versions.  :-[
Title: Re: COM
Post by: Charles Pegge on November 16, 2020, 02:38:18 PM
Hi John,

speak will run in a thread. I do this in ProjectsA\speech\speech2.o2bas. I expect most COM interface methods will tolerate being in a thread

But speak is also capable of running asynchronously without requiring a thread. This is done by adding an extra arg '1'. voice.speak "hello ...",1

But I was unable to go asynchronous using iDispatch. I don't have a typelib browser and can't find the properties or methods  for automation ispVoice, to check for alternatives.
Title: Re: COM
Post by: JRS on November 16, 2020, 09:18:44 PM
Have you tried Jose Roca's typelib viewer?

I'm not a member of the JRS forum so I can't download attachments. Please post the latest here so I can update my version.

Title: Re: COM
Post by: JRS on November 17, 2020, 12:25:08 PM
If you have VB6 it comes with a typelib viewer.
Title: Re: COM
Post by: JRS on November 17, 2020, 05:34:23 PM
This works in VB6 as a button click event.

Code: Visual Basic
  1. Private Sub Command1_Click()
  2.   Dim Voice As SpVoice
  3.   Set Voice = New SpVoice
  4.   Voice.Speak "Hello World"
  5. End Sub
  6.  

I exported from OLEview to an .idl file with the SAPI COM object info. This file is readable in a text editor.
Title: Re: COM
Post by: Charles Pegge on November 20, 2020, 02:42:08 AM
Thanks John,

Josés TypeLib Browser is an excellent tool. It provides all the interfacing details you need. The virtual table offsets can be used to access the required functions generically without a header.

CallByOffset
Code: [Select]
  uses  COM/COMutil
  CoInitialize null
  CreateGuid clsid "96749377-3391-11D2-9EE3-00C04F797396" 'SAPI
  CreateGuid iid   "6C44DF74-72B9-4992-A1EC-EF996E0422D4" 'IspVoice
  VirtualInterface *voice
  err=CoCreateInstance clsid, null, context, iid, @voice
  CallByOffset voice(80) (L"Hello World",1,0) 'speak async
  print "ok" 'pause required before ending
  CallByOffset voice(8) () 'release
  CoUninitialize
Title: Re: COM
Post by: JRS on November 20, 2020, 02:54:33 AM
Sweet!

I hope you're having as much fun as I did playing with OLE automation.

VirtualInterface seems to answer my question about pasing iUnknown (object) pointers as arguments.

In the Sage accounting software ole automation interface it has a CreateObject call which often uses a previously created object as an argument to provide inherent use.

Take another peek at my example I posted to show this in use. Mike saved the day and added this feature (along with couple other improvements) that wasn't functional where Dave left off. Without this ability I can't access the Sage software which I personally do a lot of work with. My current project is using this feature in ScriptBasic.
Title: Re: COM
Post by: JRS on November 21, 2020, 01:48:01 PM
Charles,

Is using i for integer with DLLC to define a DWORD argument correct? Windows uses a lot of DWORD references. I was thinking p for pointer would be a better choice.
Title: Re: COM
Post by: JRS on November 25, 2020, 12:03:41 PM
Charles,

Basically I need to pass returned LONG object pointers as VT_DISPATCH type for the Sage CreateOject method. That's why Mike used SET.
Title: Re: COM
Post by: Charles Pegge on November 28, 2020, 07:14:16 AM
Hi John,

It does not matter whether you use dwords or ints when they are not used in arithmetical expressions. But on a 64bit platform, pointers and handles must be 64bit to address the extended memory space.

Variants on a 64bit platform also take up 24 bytes instead of 16 bytes since one of the data unions holds 2 pointers.

Does SAGE offer an Iunknown Interface? It might be easier to use than Idispatch and Variants. Most of low-level COM runs on ints,pointers and Unicode strings.
Title: Re: COM
Post by: JRS on November 28, 2020, 08:49:29 AM
Sage uses an IDispatch argument with CreateObject when inheriting a previously created object. My assumption is this only allows me one IDispatch pointer as the first argument to CreateObject. It would be nice if I could define argument types beyond the first argument in O2 as IDispatch.

Mike's Fix: (SET)

Code: C++
  1.         if (CallType == VbSet) {
  2.           CallType = VbMethod; // MLL: !!! A MUST !!!
  3.           // MLL HACK: va->lVal and va->pdispVal are a union, so ...
  4.           pvarg[i].vt = VT_DISPATCH; // ... we tag this variant as an IDispatch* ...
  5.           pvarg[i].lVal = LONGVALUE(arg_x); // ... but write its ptr value as an ordinary long to avoid compiler type mismatches
  6.           //pvarg[i]->pdispVal->AddRef(); // MLL: Still I don't think we really need it in this context!
  7.           if(com_dbg)
  8.             color_printf(mgreen, "VTYPE_LONG/IDISPATCH: %x is %svalid pointer\n", LONGVALUE(arg_x),
  9.             isValidIDisp((IDispatch*)LONGVALUE(arg_x)) ? "" : "in");
  10.         }
  11.         else {
  12.           pvarg[i].vt = VT_I4;
  13.           pvarg[i].lVal = LONGVALUE(arg_x);
  14.           if(com_dbg)
  15.             color_printf(mgreen, "VTYPE_LONG: %d (iter=%d)\n", pvarg[i].lVal, i);
  16.         }
  17.  
Title: Re: COM
Post by: JRS on November 29, 2020, 02:41:28 PM
Here is the SAGE one customer script using the DEBUG COM extension module.

Note:  The prefix of "s" and "n" for method names tells the ProvideX COM interface what the type (string or numeric) the method / :GET will return.

Code: Script BASIC
  1. ' BOI - first customer - selected columns
  2.  
  3. IMPORT COM.sbi
  4.  
  5. oscript = COM::CREATE(:SET, "ProvideX.Script")
  6. COM::CBN oScript, "Init", :CALL, "C:\\Sage\\Sage 100 Standard\\MAS90\\Home"
  7. osession = COM::CBN(oscript, "NewObject", :SET, "SY_Session")
  8. COM::CBN osession, "nSetUser", :CALL, "js", "northstar"
  9. COM::CBN osession, "nsetcompany", :CALL, "ABC"
  10. COM::CBN osession, "nSetDate", :CALL, "A/R", "20201129"
  11. COM::CBN osession, "nSetModule", :CALL, "A/R"
  12. ocust = COM::CBN(oscript, "NewObject", :SET, "AR_Customer_svc", osession)
  13. COM::CBN ocust, "nMoveFirst"
  14. CustomerNo$ = COM::CBN(ocust, "sCustomerNo", :GET)
  15. CustomerName$ = COM::CBN(ocust, "sCustomerName", :GET)
  16. City$ = COM::CBN(ocust, "sCity", :GET)
  17. State$ = COM::CBN(ocust, "sState", :GET)
  18. TelephoneNo$ = COM::CBN(ocust, "sTelephoneNo", :GET)
  19. COM::CBN ocust, "DropObject"
  20. COM::CBN osession, "DropObject"
  21. COM::RELEASE oscript
  22.  
  23. PRINT "Customer:  ", CustomerNo$, "  ", CustomerName$, "  ", City$, "  ", State$, "  ", TelephoneNo$, "\n"
  24.  


C:\ScriptBASIC\examples>sbc onecust.sb

CreateObject 2 args
CreateObject(:SET, ProvideX.Script)
ProvideX.Script seems to return d!]w☺valid IDISPATCH pointer

CallByName 4 args
CallByName(obj=a10b24, method='Init', calltype=1 , comArgs=1)
VTYPE_STRING: C:\Sage\Sage 100 Standard\MAS90\Home (iter=0)
VT_EMPTY returned : return value from COM function was empty

CallByName 4 args
CallByName(obj=a10b24, method='NewObject', calltype=8 , comArgs=1)
VTYPE_STRING/IDISPATCH: SY_Session (iter=0)
VT_DISPATCH returned : return value from COM function was integer: a1092c

CallByName 5 args
CallByName(obj=a1092c, method='nSetUser', calltype=1 , comArgs=2)
VTYPE_STRING: northstar (iter=0)
VTYPE_STRING: js (iter=1)
VT_I4 returned : return value from COM function was integer: 1

CallByName 4 args
CallByName(obj=a1092c, method='nsetcompany', calltype=1 , comArgs=1)
VTYPE_STRING: ABC (iter=0)
VT_I4 returned : return value from COM function was integer: 1

CallByName 5 args
CallByName(obj=a1092c, method='nSetDate', calltype=1 , comArgs=2)
VTYPE_STRING: 20201129 (iter=0)
VTYPE_STRING: A/R (iter=1)
VT_I4 returned : return value from COM function was integer: 1

CallByName 4 args
CallByName(obj=a1092c, method='nSetModule', calltype=1 , comArgs=1)
VTYPE_STRING: A/R (iter=0)
VT_I4 returned : return value from COM function was integer: 1

CallByName 5 args
CallByName(obj=a10b24, method='NewObject', calltype=8 , comArgs=2)
FROM BYREF => VTYPE_LONG/IDISPATCH: a1092c is valid pointer
VTYPE_STRING: AR_Customer_svc (iter=1)
VT_DISPATCH returned : return value from COM function was integer: a10a4c

CallByName 2 args
CallByName(obj=a10a4c, method='nMoveFirst', calltype=1 , comArgs=0)
VT_I4 returned : return value from COM function was integer: 1

CallByName 3 args
CallByName(obj=a10a4c, method='sCustomerNo', calltype=2 , comArgs=0)
VT_BSTR returned : return value from COM function was string: ABF

CallByName 3 args
CallByName(obj=a10a4c, method='sCustomerName', calltype=2 , comArgs=0)
VT_BSTR returned : return value from COM function was string: American Business Futures

CallByName 3 args
CallByName(obj=a10a4c, method='sCity', calltype=2 , comArgs=0)
VT_BSTR returned : return value from COM function was string: Milwaukee

CallByName 3 args
CallByName(obj=a10a4c, method='sState', calltype=2 , comArgs=0)
VT_BSTR returned : return value from COM function was string: WI

CallByName 3 args
CallByName(obj=a10a4c, method='sTelephoneNo', calltype=2 , comArgs=0)
VT_BSTR returned : return value from COM function was string: (414) 555-4787

CallByName 2 args
CallByName(obj=a10a4c, method='DropObject', calltype=1 , comArgs=0)
VT_EMPTY returned : return value from COM function was empty

CallByName 2 args
CallByName(obj=a1092c, method='DropObject', calltype=1 , comArgs=0)
VT_EMPTY returned : return value from COM function was empty
Customer:  ABF  American Business Futures  Milwaukee  WI  (414) 555-4787

C:\ScriptBASIC\examples>
Title: Re: COM
Post by: JRS on November 30, 2020, 08:20:33 AM
Charles,

You mentioned 64 bit COM. Are you also looking at .NET for O2?
Title: Re: COM
Post by: JRS on November 30, 2020, 08:56:05 AM
Quote from: Jose Roca
This is why I prefer to code by hand, without compromises, and process messages in the window callback procedure instead of using event functions. But to each his own. I always have been old fashioned.

I would agree if I could live forever.  :D

I believe if Jose would have created a VB6 to COM / WinAPI converter he would had a greater success than investing in PowerBasic.

My clients keep me grounded with their budgets.
Title: Re: COM
Post by: Charles Pegge on December 05, 2020, 03:05:44 PM
My proposed Idispatch implementation:

Code: [Select]
  ---------------------------------------------------------------------
  function ConnectByDispatch(wchar *name, IDispatch **interface) as int
  =====================================================================
  GUID          ObjectGuid
  HRESULT       hr
  LPUNKNOWN     pUnkOuter
  '
  if unic(name)=123 '{'
    err=CLSIDFromString(strptr name, ObjectGuid) 'passing a guid string '{...}'
  else
    err=CLSIDFromProgID(strptr name, ObjectGuid) 'passing a progid string 'sapi.voice'
  endif
  if not err
    err=CoCreateInstance ObjectGuid, null, context, IID_IDispatch, @interface
  endif
  'if err then print "Connection error code: " err
  return err
  end function

  -----------------------------
  macro ConnectByName(name,obj)
  =============================
  IDispatch *obj
  ConnectByDispatch(name, obj)
  end macro

  -------------------------
  def CreateInterfaceByName
  =========================
  IDispatch *%2
  ConnectByDispatch(%1, %2)
  end def


  % METHOD 1
  % GET    2
  % LET    4
  % LET    8



  -----------------------------------------------------------------------------------------------------------------
  function CallByNameV(IDispatch *vi, word flag=1, wchar *name, VARIANT *va=null, int c=0, VARIANT *vr=null) as int
  =================================================================================================================
  sys sl=strptr name
  uint id
  err = vi.GetIDsOfNames IID_NULL, sl, 1, LOCALE_USER_DEFAULT, id
  '
  if not err
    'enum vbCallType{ VbMethod = 1, VbGet = 2, VbLet = 4, VbSet = 8 };
    if c>1
      'reverse order assignment
      int i,j
      VARIANT vb
      j=c+1
      for i=1 to c
        j--
        if i>=j then exit for
        'swap
        vb=va[j]
        va[j]=va[i]
        va[i]=vb
      next
    endif
    '
    DISPPARAMS pa
    @pa.rgvarg=@va
    pa.cargs=c
    if flag>2 '4 8
      int pp=-3 'DISPID_PROPERTYPUT '0xfffffffd
      @pa.rgdispidNamedArgs=@pp
      pa.cNamedArgs=1     
    endif
    err=vi.Invoke( id, IID_NULL, LOCALE_USER_DEFAULT, flag, pa, vr, null, null)
  endif
  '
  return err
  end function

  -------------------------------------------------------------------------------------------------------
  function CallByNameS(IDispatch *vi, word flag, BSTR name, BSTR *sa=null, int c=0, BSTR *sr=null) as int
  =======================================================================================================
  int i
  VARIANT va[8], vr 'up to 8 params
  indexbase 1
  '
  if @sr
   vr.vt=VT_BSTR
   ?vr.bstrVal=?sr 'direct coupling
  endif
  for i=1 to c
   va[i].vt=VT_BSTR
   ?va[i].bstrVal=?sa[i] 'direct coupling
  next
  '
  err=CallByNameV(vi, flag, name, va, c, vr) 'VARIANT morph
  '
  for i=1 to c
    frees sa[i]
  next
  if @sr
    if vr.vt<>VT_BSTR
      VariantChangeType(vr,vr,0,VT_BSTR)
    endif
    ?sr=?vr.bstrval 'caller will free this string
  endif
  return err
  end function


  macro CallByName(vi,nf,sa,sr) CallByNameS(vi,1,nf,sa,countof,sr)

  macro GetByName(vi,nf,sr) CallByNameS(vi,2,nf,null,0,sr)

  macro LetByName(vi,nf,sa) CallByNameS(vi,4,nf,sa, 1)

  macro SetByName(vi,nf,sa) CallByNameS(vi,8,nf,sa, 1)

Title: Re: COM
Post by: JRS on December 05, 2020, 03:17:44 PM
Looks great Charles. Are you getting close to releasing something I can play with? (test)
Title: Re: COM
Post by: JRS on December 07, 2020, 02:01:02 AM
Charles,

I was thinking about what you said about DWORDs. Could the DWORD reference mean the argument is its pointer not its value?

I *addrptr

If I'm on the right path, shouldn't p work?

p addrptr





Title: Re: COM
Post by: JRS on December 21, 2020, 04:51:09 AM
Charles,

Will you be releasing OLE automation cheer for Christmas? 🎄🎁

I will be delivering new faster websites for Christmas with a fiber bow.

Hosting these forums local is like inviting everyone over to my house. 🤗

Title: Re: COM
Post by: JRS on January 18, 2021, 09:02:44 AM
Charles,

Where does things stand on your COM/OLE automation library? Will it be available with the self compile release?
Title: Re: COM
Post by: Charles Pegge on January 20, 2021, 05:25:46 AM
Yes, John. I am doing a round of tests before releasing 0.3.0. The compiler differences are fairly minimal.

Title: Re: COM
Post by: JRS on January 20, 2021, 09:49:31 AM
Looking forward to the next O2 release!
Title: Re: COM
Post by: Nicola on January 22, 2021, 08:31:49 AM
Yes, we are all anxiously awaiting. 8)
Title: Re: COM
Post by: JRS on January 22, 2021, 07:52:19 PM
Having the compiler in O2 and self compiling will open the door for others to contribute. A new phase in O2 development.