Author Topic: COM  (Read 6821 times)

0 Members and 1 Guest are viewing this topic.

JRS

  • Guest
Re: COM
« Reply #45 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.  :-[
« Last Edit: November 17, 2020, 07:51:51 PM by John »

Charles Pegge

  • Guest
Re: COM
« Reply #46 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.

JRS

  • Guest
Re: COM
« Reply #47 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.

« Last Edit: November 17, 2020, 10:29:14 AM by John »

JRS

  • Guest
Re: COM
« Reply #48 on: November 17, 2020, 12:25:08 PM »
If you have VB6 it comes with a typelib viewer.

JRS

  • Guest
Re: COM
« Reply #49 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.
« Last Edit: November 17, 2020, 07:48:36 PM by John »

Charles Pegge

  • Guest
Re: COM
« Reply #50 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

JRS

  • Guest
Re: COM
« Reply #51 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.
« Last Edit: November 20, 2020, 08:48:44 PM by John »

JRS

  • Guest
Re: COM
« Reply #52 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.
« Last Edit: November 22, 2020, 11:15:37 AM by John »

JRS

  • Guest
Re: COM
« Reply #53 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.

Charles Pegge

  • Guest
Re: COM
« Reply #54 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.

JRS

  • Guest
Re: COM
« Reply #55 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.  
« Last Edit: November 29, 2020, 11:28:50 AM by John »

JRS

  • Guest
Re: COM
« Reply #56 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>
« Last Edit: November 29, 2020, 04:04:51 PM by John »

JRS

  • Guest
Re: COM
« Reply #57 on: November 30, 2020, 08:20:33 AM »
Charles,

You mentioned 64 bit COM. Are you also looking at .NET for O2?

JRS

  • Guest
Re: COM
« Reply #58 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.
« Last Edit: November 30, 2020, 09:45:11 AM by John »

Charles Pegge

  • Guest
Re: COM
« Reply #59 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)