Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
Yeah usually you would be right, a form is indeed a class, but for the purpose of inter-process communication using the RunningObjectTable you can only use forms. Apparently there are some subtle differences between a form and a class in this regard. I remember there was a discussion about this in an older thread (can't be bothered to search right now) and there was someone who figured out the nitty-gritty details and came up with a dirty hack that allowed classes to be used in this manner as well.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
How do you start such a TB "server"? Do you have some best practices to share? My idea would be to try to find it by its window text, and if not found, start it. I am not sure how "crash safe" my approach is. Or if it would perhaps even be necessary to kill the server if something goes wrong.. If anybody has any experience with this and tipps, please let me know.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
@tmighty2: Kudos for coming up with cSpVoice wrapper. This would have been my next suggestion -- a class which accepts a callback object and an index so it can forward original SpeechLib.SpVoice events on functions of the callback by prefixing their parameters with the index.
This is what VBx does with control arrays -- it generates both the control array class and a hidden wrapper class which calls back to the control array class so it can raise events with indexes.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
Originally Posted by fafalone
Community edition is free but puts a splash screen on initial loads of 64bit exes/dlls, however no restrictions on language features or project types besides llvm-optimized binaries (partially complete). 'VB-ish language' makes it sound like b4a or powerbasic or some other incompatiible dialect; to be clear, its the VB6 language exactly as a compatibility base; it's like VB1-5 vs 6; not another language vs vb6. New syntax and language features are opt in. It runs existing VB6 code without modification in most cases (minus a few bugs, minor unimplemented features, and vb6 internals hacks easily replaced with simpler techniques). For 64bit it uses the MS Office VBA7 syntax with LongPtr/PtrSafe.
Sounds like a good possible solution for the OP. I recently tried TB to port one of my custom ActiveX controls and was quite impressed - the entire code ran without modification. As soon as TB has matured more, I will probably use more.....
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
How could I get the Form?
I have tried anything I could imagine.
In the tb form I have tried Function, Property and GetMe (in order to return the form itself as on object).
For example:
Code:
Private m_oCallback As Object
Private m_oCallbackMe As Object
Public Sub ShutDown()
Unload Me
End Sub
Public Property Get AboutYou2() As String
AboutYou2 = "About me 2"
End Property
Public Function AboutYou1() As String
AboutYou1 = "about me 1"
End Function
Public Sub ShowYourself()
Me.Visible = True
End Sub
Public Sub HideYourself()
Me.Visible = False
End Sub
Public Function GetMe(oCallback As Object) As Object
Set m_oCallbackMe = oCallback
Set GetMe = Me
End Function
And on the VB6 side in a Form:
Code:
Set m_oProxy64 = GetObject("myidentifier")
If m_oProxy64 Is Nothing Then
Debug.Assert False
End If
Set m_ProxyForm = m_oProxy64.GetMe(Me)
But I was not able to talk to the Form.
Perhaps I am missing something.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
"m_oProxy64"is the TB form, provided you used the "Me" keyword in the "PutObject" function. You can even access other controls on the form: m_oProxy64.Command1.Caption = "New Caption".
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
Originally Posted by VanGoghGaming
Yeah usually you would be right, a form is indeed a class, but for the purpose of inter-process communication using the RunningObjectTable you can only use forms. Apparently there are some subtle differences between a form and a class in this regard. I remember there was a discussion about this in an older thread (can't be bothered to search right now) and there was someone who figured out the nitty-gritty details and came up with a dirty hack that allowed classes to be used in this manner as well.
The discussion was about VB6 forms and private VB6 classes not being able to be placed in ROT for no apparent reason (needed a "cure" in current VBProject structs). There is no such restriction in TB so regular classes get put in ROT too.
Here is a somewhat full sample using TB and VB6: Moniker2.zip
Just make sure to build x64 target of Moniker.twinproj before running the VB6 project. It will search and auto-start (hidden) Moniker_win64.exe in exact location. It will make sure to shutdown (hidden) proxy process on form unload.
Here is the complete VB6 source code
Code:
Option Explicit
Private m_oProxy As Object
Private m_oWrapper As Object
Private Function GetMyProxy() As Object
Const MONIKER_WIN64_EXE As String = "..\TB\Build\Moniker_win64.exe"
On Error GoTo EH
If m_oProxy Is Nothing Then
Set m_oProxy = GetObject("MyApp.MyProxy")
End If
Set GetMyProxy = m_oProxy
Exit Function
EH:
Shell App.Path & "\" & MONIKER_WIN64_EXE & " /hidden"
Set m_oProxy = GetObject("MyApp.MyProxy")
Set GetMyProxy = m_oProxy
End Function
Private Sub Form_Click()
On Error GoTo EH
If m_oWrapper Is Nothing Then
Set m_oWrapper = GetMyProxy.CreateSpVoice(Me)
End If
m_oWrapper.SpVoice.Speak "this is a test", 1
Exit Sub
EH:
MsgBox Err.Description, vbCritical
Set m_oWrapper = Nothing
Set m_oProxy = Nothing
End Sub
Private Sub Form_Unload(Cancel As Integer)
If Not m_oProxy Is Nothing Then
m_oProxy.Shutdown
End If
End Sub
Public Sub SpVoice_StartStream(ByVal StreamNumber As Long, ByVal StreamPosition As Variant)
Print "StartStream, StreamNumber=" & StreamNumber & ", StreamPosition=" & StreamPosition
End Sub
Public Sub SpVoice_Word(ByVal StreamNumber As Long, ByVal StreamPosition As Variant, ByVal CharacterPosition As Long, ByVal Length As Long)
Print "StartStream, Word=" & StreamNumber & ", StreamPosition=" & StreamPosition & ", CharacterPosition=" & CharacterPosition & ", Length=" & Length
End Sub
Another better approach would be to pass "MyApp.MyProxy" moniker filename on command-line so that a UUID can be generated by client code and used as "communication channel name". If you look at chome.exe processes' command-lines these are littered with such on-the-fly generated UUIDs.
Edit: Just updated Moniker2.zip above with this idea about random UUID and included x64 TB executable.
Since "Shell" returns immediately and doesn't wait for the external program to actually finish loading, isn't there a possibility the subsequent "GetObject" will fail?
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
> Since "Shell" returns immediately and doesn't wait for the external program to actually finish loading, isn't there a possibility the subsequent "GetObject" will fail?
That's what I though and was prepared to use ShellExecute with SEE_MASK_NOCLOSEPROCESS and WaitForInputIdle on returned hProcess i.e. the usual dance with a lot of API declares but it works with built-in Shell just fine here which is not something I expected to get away so easily.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
Thank you to The trick for providing detailed step by step. I just found this note that says Microsoft has stopped reading from HKEY_CLASSES_ROOT\AppID since Windows 2003, and uses the one in HKLM only at HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID. Perhaps that's why the OP was having issues.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
If you need several voices with their events at the same time, would be recommend 1 "Moniker_win64.exe" for all voices or 1 "Moniker_win64.exe" for each voice?
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
That’s an interesting question. With multiple out-of-process servers you can get per process multi-tasking “for free” but the COM object in particular already uses threading to multi-task in any single process so the benefits of spawning multiple EXEs in dubious or non-existant.
Do you face any blocking issues with single spawn approach?
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
I terminate my app often using the stop button in the VB6 IDE.
This leads to several moniker exes being left open.
Is there any recommend way to check if the process that the callback belongs to is still alive?
I could think of using a timer to check if window title (the process that the callback object belongs to could open an invisible window with a UUID as the caption) is found.
Or trying to find the process from time to time.
Is there any recommended which of checking if the process is still alive? To be clear: The TB exe should check if the other process (in my case the VB6 app) is still running. If not, the TB exe should close.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
You should approach this issue the other way around, if a TB exe is still running then you should reuse it instead of opening a new one (as I suggested in post #45).
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
When running in IDE you can use a fixed GUID for the moniker name so that any outstanding/running TB server will respond to GetObject(FIXED_MONIKER) no matter if IDE was restarted in the mean time.
Note that on stopping debug session all references to external COM objects are cleared so proxy/stub wrappers in TB process are cleared ok in this case too.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
The solution (at least as by suggestion of chatgpt and Google gemini) is to use a Heartbeat which will be sent by the calling VB6 application to the TB app from time to time to indicate that it it still alive.
I have attached my TB to illustrate that just in case somebody requires to see that.
TB is really easy to work with and works fine so far. I really appreciate getting to know it due to this problem that I had. I use it paying regular monthly payment.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
I suspected that this would be the error that I experience on some customers computer, but the error occurs here:
Code:
Private m_sProxyName_Aka_UUID$
Private m_oProxy64 As Object
I start it like this:
Dim sCmd$
sCmd="D:\Dev\Projects\tws64\TB\Build\twsMoniker64_win64.exe 2e1316f8-a36a-4734-9db6-cc41a7696dd2"
Dim d As Double
d = Shell(sCmd)
Set m_oProxy64 = GetObject(m_sProxyName_Aka_UUID)
And it throws an error in that last line "GetObject" with "Automation error. Invalid syntax" , err.number: -2147221020
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
"Invalid syntax" is raised when the moniker name is not recognized. Probably should retry GetObject call (with a timeout) untill Shell completes spawning the worker process and server object is registered in ROT.
> In your latest demo that you posted here, are you not using CreateObject anymore?
This is just a sample cheap and useful method you can use to "spawn" new server objects inside the x64 process. Can't remember if it's there for any other purpose.
cheers,
</wqw>
Last edited by wqweto; Oct 12th, 2024 at 03:42 PM.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
I tried it 10 times with a pause of 1 second between each attempt, so 10 seconds altogether. That should be enough, right?
It works fine on my computer but fails on a clients computer running Windows 11 Home, Version 10.0.22631 Build 22631.
Do you have any recommendation what I should check next?
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
I have attached the Twinbasic project.
Please ignore the childish over-explaining variable names. I had to make some sense of it, and doing that helps me.
Edit: I have now put vb6 and tb into a single folder and attached it as spvoiceproxy2.zip.
Last edited by tmighty2; Oct 12th, 2024 at 07:10 PM.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
Originally Posted by qvb6
VB6 could talk to a 64-Bit COM DLL, but not standard 64-Bit DLL. The API version of CreateObject() allows creating out-of-process objects using DLLHOST.exe. You can talk to 32 or 64 Bit COM DLL's that way. The OS takes care of moving data back and forth between VB6 and the 64-Bit COM DLL. Please see CreateObject64() in post #6 in this thread and my subsequent posts.
In my case, I made a 64-Bit COM DLL using C++(Not SAPI related), and talked to it from VB6. Windows runs the 64-Bit version of DLLHOST.exe to host the 64-Bit DLL.
Another option for you is to turn your DLL into 64-Bit COM EXE(Same as ActiveX EXE). This doesn't require adding registry entries like the 64-Bit DLL option.
The ActiveX exe also needs to be written to the registry and requires administrator privileges to register. Run.
registeractiveobject,make exe as rot object,This can be called by other processes without administrator privileges.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
I am not familiar with this error at all and currently stuck. Would it help to shrink the project? I am not sure if it's understandable.
My code works fine on my computer, and honestly I got no clue what else to test.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
One thing I noticed when opening SpVoiceMonikerTest.vbp is that Microsoft Speech Object Library reference is MISSING on my machine.
Apparently you are referencing some incompatible (newer or older) version on the DLL. Once I fixed this the project seems to work in the IDE (while compiled SpVoiceMonikerTest.exe is something old, not sure what to test here).
Could it be your clients PCs have different Microsoft Speech installed? I have no previous experience with this library, first time seeing it, so cannot tell if this is normal or if you should use late-bound calls similar to dealing with MS Office incompatibilities.
Anyway, cannot come up with anything obviously wrong with your code on first glace.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
It is a file that should be present on all Windows machines from Windows 10 upwards as it is used as a standard feature:
Users who can not see and therefore use a computer voice to have the text on the screen spoken to them, use computer voices, and they are controlled via SAPI.
SAPI.dll must exist as both 32 bit and 64 bit version on the computer.
Users can not replace this SAPI.dll easily. It is not a file that users are expected to update themselves.
Do you have any idea how to solve them when you say that on your machine it was missing?
May I ask you where your was located and which version, etc.?
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
ps: I posted the reference that I used in TwinBasic to chatgpt, and it said that the library that I'm using, identified by the GUID "C866CA3A-32F7-11D2-9602-00C04F8EE628", is part of Windows' built-in speech capabilities, specifically tied to the Speech API.(SAPI).
This is what I see when I double click the reference in TwinBasic:
Code:
[ LibraryId ("C866CA3A-32F7-11D2-9602-00C04F8EE628") ]
[ Version (5.4) ]
[ Description ("Microsoft Speech Object Library") ]
Library SpeechLib
' Original type library: C:\WINDOWS\System32\Speech\Common\sapi.dll
' NOTE: Offsets and lengths calculated for current Win64 target.
I checked the customer's registry, and it is found:
I see that the version of sapi.dll of the customer's computer is newer than mine, but SAPI exists since many years, and I find it highly unlikely that a change to it was introduced which in turn would cause an incompatibility.
Last edited by tmighty2; Oct 13th, 2024 at 01:10 PM.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
When I import the customers sapi.dll instead of mine and check the TwinBasic References then, apart from the file location, the 2 files match.
I believe it's a TwinBasic problem.
Edit: Oh you are talking about the VB6 project!!
Last edited by tmighty2; Oct 13th, 2024 at 02:48 PM.
Re: Compatibility of 32-bit VB6 Applications with 64-bit SAPI
I had used it in a small test app, and it worked.
I use just the same file in my production app, and it can not even get the proxy.
My production app is signed and has a manifest.
When I run my app as an admin, it works.
It will immediately get the proxy without any 2nd or third attempt..
Why????????????? The proxy is in my app's path.
My app is signed and manifested. The proxy is not.
The proxy and my app are in Program Files (x86)\<appname>.
Why does my app need to have admin rights to interact with the proxy?
When I put the small test app (which is not signed and not manifested) into the same Program Files (x86)\<appname> directory, it does NOT require to be admin to be executed.
Edit: Hmmm, I see... since my app has uiAccess manifest, it can interact with the ui which is priviledged. Allowing my app to control a proxy which does not have the same privileges could be seen as bad as the proxy does something in its name, but in fact it's my app which does the possibly bad thing with its privileges. That is why the proxy must also have the manifest, I guess.
Edit2: I have given it the same manifest (except * for processorarchitecture instead of x86) and codesigned the moniker.
Now it can't be started using shell and throws invalid procedure or argument error when I use the usual Shell command to run it.
Last edited by tmighty2; Oct 13th, 2024 at 06:22 PM.