New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide a higher-level abstraction over IDispatch
#229
Comments
The You're right -- it's a very low level way of making a COM call, and could benefit from some abstraction. I don't know that I'm going to have time to do this any time soon, but keeping the issue open and retitling it so that others can offer help. |
IDispatch
i had found the way to tts (text to speech) using sapi with code below by calling import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
const CLSID_SpVoice = '{96749377-3391-11D2-9EE3-00C04F797396}';
const IID_ISpVoice = '{6C44DF74-72B9-4992-A1EC-EF996E0422D4}';
final ptr = calloc<COMObject>()..ref.genNew();
int _speak(
Pointer<Utf16> pwcs,
int dwFlags,
int pulStreamNumber,
) =>
Pointer<
NativeFunction<
Int32 Function(
Pointer obj,
Pointer<Utf16> pwcs,
Uint32 dwFlags,
Uint32 pulStreamNumber,
)>>.fromAddress(ptr.ref.vtable.elementAt(20).value)
.asFunction<
int Function(
Pointer obj,
Pointer<Utf16> pwcs,
int dwFlags,
int pulStreamNumber,
)>()(ptr.ref.lpVtbl, pwcs, dwFlags, pulStreamNumber);
int windowsSpeak(String s) {
// Initialize COM
var hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) {
throw "WindowsException(hr)";
}
hr = CoCreateInstance(
GUIDFromString(CLSID_SpVoice),
nullptr,
CLSCTX_ALL,
GUIDFromString(IID_ISpVoice),
ptr,
);
var i = _speak(s.toNativeUtf16(), 0, NULL);
if (FAILED(hr)) throw "失败";
CoUninitialize();
return i;
}
Future<int> windowsSpeakAsync(String s) {
return compute(windowsSpeak, s);
}
const COINIT_APARTMENTTHREADED = 0x2;
const NULL = 0;
/// @nodoc
const CLSCTX_INPROC_SERVER = 0x1;
/// @nodoc
const CLSCTX_INPROC_HANDLER = 0x2;
/// @nodoc
const CLSCTX_LOCAL_SERVER = 0x4;
/// @nodoc
const CLSCTX_INPROC_SERVER16 = 0x8;
/// @nodoc
const CLSCTX_REMOTE_SERVER = 0x10;
/// @nodoc
const CLSCTX_ALL = CLSCTX_INPROC_SERVER |
CLSCTX_INPROC_HANDLER |
CLSCTX_LOCAL_SERVER |
CLSCTX_REMOTE_SERVER;
// *** COM STRUCTS ***
// typedef struct _GUID {
// unsigned long Data1;
// unsigned short Data2;
// unsigned short Data3;
// unsigned char Data4[ 8 ];
// } GUID;
/// Represents a globally unique identifier (GUID).
///
/// {@category Struct}
class GUID extends Struct {
@Uint32()
int Data1;
@Uint16()
int Data2;
@Uint16()
int Data3;
@Uint64()
int Data4;
factory GUID.allocate() => calloc<GUID>().ref
..Data1 = 0
..Data2 = 0
..Data3 = 0
..Data4 = 0;
/// Create GUID from common {FDD39AD0-238F-46AF-ADB4-6C85480369C7} format
factory GUID.fromString(String guidString) {
assert(guidString.length == 38);
final guid = calloc<GUID>().ref;
guid.Data1 = int.parse(guidString.substring(1, 9), radix: 16);
guid.Data2 = int.parse(guidString.substring(10, 14), radix: 16);
guid.Data3 = int.parse(guidString.substring(15, 19), radix: 16);
// final component is pushed on the stack in reverse order per x64
// calling convention. This is a funky workaround until FFI supports
// passing structs by value.
final rawString = guidString.substring(35, 37) +
guidString.substring(33, 35) +
guidString.substring(31, 33) +
guidString.substring(29, 31) +
guidString.substring(27, 29) +
guidString.substring(25, 27) +
guidString.substring(22, 24) +
guidString.substring(20, 22);
// We need to split this to avoid overflowing a signed int.parse()
guid.Data4 = (int.parse(rawString.substring(0, 4), radix: 16) << 48) +
int.parse(rawString.substring(4, 16), radix: 16);
return guid;
}
/// Print GUID in common {FDD39AD0-238F-46AF-ADB4-6C85480369C7} format
@override
String toString() {
final comp1 = (Data4 & 0xFF).toRadixString(16).padLeft(2, '0') +
((Data4 & 0xFF00) >> 8).toRadixString(16).padLeft(2, '0');
// This is hacky as all get-out :)
final comp2 = ((Data4 & 0xFF0000) >> 16).toRadixString(16).padLeft(2, '0') +
((Data4 & 0xFF000000) >> 24).toRadixString(16).padLeft(2, '0') +
((Data4 & 0xFF00000000) >> 32).toRadixString(16).padLeft(2, '0') +
((Data4 & 0xFF0000000000) >> 40).toRadixString(16).padLeft(2, '0') +
((Data4 & 0xFF000000000000) >> 48).toRadixString(16).padLeft(2, '0') +
(BigInt.from(Data4 & 0xFF00000000000000).toUnsigned(64) >> 56)
.toRadixString(16)
.padLeft(2, '0');
return '{${Data1.toRadixString(16).padLeft(8, '0').toUpperCase()}-'
'${Data2.toRadixString(16).padLeft(4, '0').toUpperCase()}-'
'${Data3.toRadixString(16).padLeft(4, '0').toUpperCase()}-'
'${comp1.toUpperCase()}-'
'${comp2.toUpperCase()}}';
}
/// Create GUID from common {FDD39AD0-238F-46AF-ADB4-6C85480369C7} format
void setGUID(String guidString) {
assert(guidString.length == 38);
Data1 = int.parse(guidString.substring(1, 9), radix: 16);
Data2 = int.parse(guidString.substring(10, 14), radix: 16);
Data3 = int.parse(guidString.substring(15, 19), radix: 16);
// Final component is pushed on the stack in reverse order per x64
// calling convention.
final rawString = guidString.substring(35, 37) +
guidString.substring(33, 35) +
guidString.substring(31, 33) +
guidString.substring(29, 31) +
guidString.substring(27, 29) +
guidString.substring(25, 27) +
guidString.substring(22, 24) +
guidString.substring(20, 22);
// We need to split this to avoid overflowing a signed int.parse()
Data4 = (int.parse(rawString.substring(0, 4), radix: 16) << 48) +
int.parse(rawString.substring(4, 16), radix: 16);
}
}
Pointer<GUID> GUIDFromString(String guid) => calloc<GUID>()..ref.setGUID(guid);
/// A representation of a generic COM object. All Dart COM objects inherit from
/// this class.
///
/// {@category com}
class COMObject extends Struct {
Pointer<IntPtr> lpVtbl;
Pointer<IntPtr> get vtable => Pointer.fromAddress(lpVtbl.value);
genNew() {
lpVtbl = calloc<IntPtr>();
}
}
final _ole32 = DynamicLibrary.open('ole32.dll');
final CoInitializeEx = _ole32.lookupFunction<
Int32 Function(Pointer<Void> pvReserved, Uint32 dwCoInit),
int Function(Pointer<Void> pvReserved, int dwCoInit)>('CoInitializeEx');
/// Creates a single uninitialized object of the class associated with a
/// specified CLSID. Call CoCreateInstance when you want to create only one
/// object on the local system. To create a single object on a remote
/// system, call the CoCreateInstanceEx function. To create multiple
/// objects based on a single CLSID, call the CoGetClassObject function.
///
/// ```c
/// HRESULT CoCreateInstance(
/// REFCLSID rclsid,
/// LPUNKNOWN pUnkOuter,
/// DWORD dwClsContext,
/// REFIID riid,
/// LPVOID *ppv
/// );
/// ```
/// {@category ole32}
final CoCreateInstance = _ole32.lookupFunction<
Int32 Function(Pointer<GUID> rclsid, Pointer<IntPtr> pUnkOuter, Uint32 dwClsContext,
Pointer<GUID> riid, Pointer<COMObject> ppv),
int Function(Pointer<GUID> rclsid, Pointer<IntPtr> pUnkOuter, int dwClsContext,
Pointer<GUID> riid, Pointer<COMObject> ppv)>('CoCreateInstance');
/// Closes the COM library on the current thread, unloads all DLLs loaded
/// by the thread, frees any other resources that the thread maintains, and
/// forces all RPC connections on the thread to close.
///
/// ```c
/// void CoUninitialize();
/// ```
/// {@category ole32}
final CoUninitialize =
_ole32.lookupFunction<Void Function(), void Function()>('CoUninitialize');
// #define FAILED(hr) (((HRESULT)(hr)) < 0)
bool FAILED(int result) => result < 0;
|
okay, i found it is the same order from // from sapi51.h
// MIDL_INTERFACE("6C44DF74-72B9-4992-A1EC-EF996E0422D4")
// ISpVoice : public ISpEventSource
// {
// public:
// virtual HRESULT STDMETHODCALLTYPE SetOutput(
// /* [in] */ IUnknown *pUnkOutput,
// /* [in] */ BOOL fAllowFormatChanges) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetOutputObjectToken(
// /* [out][annotation] */
// _Outptr_ ISpObjectToken **ppObjectToken) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetOutputStream(
// /* [out] */ ISpStreamFormat **ppStream) = 0;
// virtual HRESULT STDMETHODCALLTYPE Pause( void) = 0;
// virtual HRESULT STDMETHODCALLTYPE Resume( void) = 0;
// virtual HRESULT STDMETHODCALLTYPE SetVoice(
// /* [in] */ ISpObjectToken *pToken) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetVoice(
// /* [out][annotation] */
// _Outptr_ ISpObjectToken **ppToken) = 0;
// virtual HRESULT STDMETHODCALLTYPE Speak(
// /* [string][in][annotation] */
// _In_opt_ LPCWSTR pwcs,
// /* [in] */ DWORD dwFlags,
// /* [out][annotation] */
// _Out_opt_ ULONG *pulStreamNumber) = 0;
// virtual HRESULT STDMETHODCALLTYPE SpeakStream(
// /* [in] */ IStream *pStream,
// /* [in] */ DWORD dwFlags,
// /* [out][annotation] */
// _Out_opt_ ULONG *pulStreamNumber) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetStatus(
// /* [out] */ SPVOICESTATUS *pStatus,
// /* [out][annotation] */
// _Outptr_ LPWSTR *ppszLastBookmark) = 0;
// virtual HRESULT STDMETHODCALLTYPE Skip(
// /* [string][in] */ LPCWSTR pItemType,
// /* [in] */ long lNumItems,
// /* [out] */ ULONG *pulNumSkipped) = 0;
// virtual HRESULT STDMETHODCALLTYPE SetPriority(
// /* [in] */ SPVPRIORITY ePriority) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetPriority(
// /* [out] */ SPVPRIORITY *pePriority) = 0;
// virtual HRESULT STDMETHODCALLTYPE SetAlertBoundary(
// /* [in] */ SPEVENTENUM eBoundary) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetAlertBoundary(
// /* [out] */ SPEVENTENUM *peBoundary) = 0;
// virtual HRESULT STDMETHODCALLTYPE SetRate(
// /* [in] */ long RateAdjust) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetRate(
// /* [out] */ long *pRateAdjust) = 0;
// virtual HRESULT STDMETHODCALLTYPE SetVolume(
// /* [in] */ USHORT usVolume) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetVolume(
// /* [out] */ USHORT *pusVolume) = 0;
// virtual HRESULT STDMETHODCALLTYPE WaitUntilDone(
// /* [in] */ ULONG msTimeout) = 0;
// virtual HRESULT STDMETHODCALLTYPE SetSyncSpeakTimeout(
// /* [in] */ ULONG msTimeout) = 0;
// virtual HRESULT STDMETHODCALLTYPE GetSyncSpeakTimeout(
// /* [out] */ ULONG *pmsTimeout) = 0;
// virtual /* [local] */ HANDLE STDMETHODCALLTYPE SpeakCompleteEvent( void) = 0;
// virtual /* [local] */ HRESULT STDMETHODCALLTYPE IsUISupported(
// /* [in] */ LPCWSTR pszTypeOfUI,
// /* [in] */ void *pvExtraData,
// /* [in] */ ULONG cbExtraData,
// /* [out] */ BOOL *pfSupported) = 0;
// virtual /* [local] */ HRESULT STDMETHODCALLTYPE DisplayUI(
// /* [in] */ HWND hwndParent,
// /* [in] */ LPCWSTR pszTitle,
// /* [in] */ LPCWSTR pszTypeOfUI,
// /* [in] */ void *pvExtraData,
// /* [in] */ ULONG cbExtraData) = 0;
// };
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
class ISapi extends IUnknown {
// vtable begins at 13, is 30 entries long.
ISapi(Pointer<COMObject> ptr) : super(ptr);
int Pause() => Pointer<
NativeFunction<
Int32 Function(
Pointer obj,
)>>.fromAddress(ptr.ref.vtable.elementAt(16).value)
.asFunction<
int Function(
Pointer obj,
)>()(ptr.ref.lpVtbl);
int Resume() => Pointer<
NativeFunction<
Int32 Function(
Pointer obj,
)>>.fromAddress(ptr.ref.vtable.elementAt(17).value)
.asFunction<
int Function(
Pointer obj,
)>()(ptr.ref.lpVtbl);
int Speak(
Pointer<Utf16> pwcs,
int dwFlags,
int pulStreamNumber,
) =>
Pointer<
NativeFunction<
Int32 Function(
Pointer obj,
Pointer<Utf16> pwcs,
Uint32 dwFlags,
Uint32 pulStreamNumber,
)>>.fromAddress(ptr.ref.vtable.elementAt(20).value)
.asFunction<
int Function(
Pointer obj,
Pointer<Utf16> pwcs,
int dwFlags,
int pulStreamNumber,
)>()(ptr.ref.lpVtbl, pwcs, dwFlags, pulStreamNumber);
}
const CLSID_SpVoice = '{96749377-3391-11D2-9EE3-00C04F797396}';
const IID_ISpVoice = '{6C44DF74-72B9-4992-A1EC-EF996E0422D4}';
class Sapi {
final ISapi iSapi;
Sapi(this.iSapi);
factory Sapi.createInstance() {
final ptr = COMObject.createFromID(CLSID_SpVoice, IID_ISpVoice);
final iSapi = ISapi(ptr);
return Sapi(iSapi);
}
final SVSFlagsAsync = 1;
final SVSFPurgeBeforeSpeak = 2;
int speak(String s) {
final pwcs = s.toNativeUtf16();
try {
final hr = iSapi.Speak(pwcs, SVSFlagsAsync | SVSFPurgeBeforeSpeak, NULL);
if (FAILED(hr)) {
throw WindowsException(hr);
} else {
final hr = iSapi.Resume();
if (FAILED(hr)) {
throw WindowsException(hr);
} else {
return hr;
}
}
} finally {
free(pwcs);
}
}
int pause() {
try {
final hr = iSapi.Pause();
if (FAILED(hr)) {
throw WindowsException(hr);
} else {
return hr;
}
} finally {}
}
int resume() {
try {
final hr = iSapi.Resume();
if (FAILED(hr)) {
throw WindowsException(hr);
} else {
return hr;
}
} finally {}
}
void dispose() {
free(iSapi.ptr);
CoUninitialize();
}
}
|
@mabDc I've added the speech client APIs directly to win32 (#379), so now you don't have to handcraft these COM interfaces. This should greatly simplify your code. Here's a very straightforward example: import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
const textToSpeak =
'Dart is a portable, high-performance language from Google.';
void main() {
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
final speechEngine = SpVoice.createInstance();
final pText = textToSpeak.toNativeUtf16();
speechEngine.Speak(pText, SPEAKFLAGS.SPF_IS_NOT_XML, nullptr);
free(pText);
CoUninitialize();
}
|
nice. thanks. |
What have you done! COM is supposed to be all painful and headache-inducing and you should have to look at a piece of code for half an hour before figuring out what it does. It's not supposed to be mostly readable. |
Thank you for the kind comments, @Keithcat1! The MSDN documentation is kinda messed up, but it looks like the third parameter is the stream number (for when you have multiple simultaneous streams): https://docs.microsoft.com/en-us/previous-versions/office/developer/speech-technologies/jj149384(v=msdn.10) |
@Keithcat1 u need look at sapi51.h for some information as i ve mentioned MIDL_INTERFACE("6C44DF74-72B9-4992-A1EC-EF996E0422D4")
ISpVoice : public ISpEventSource
{
public:
virtual HRESULT STDMETHODCALLTYPE SetOutput(
/* [in] */ IUnknown *pUnkOutput,
/* [in] */ BOOL fAllowFormatChanges) = 0;
virtual HRESULT STDMETHODCALLTYPE GetOutputObjectToken(
/* [out][annotation] */
_Outptr_ ISpObjectToken **ppObjectToken) = 0;
virtual HRESULT STDMETHODCALLTYPE GetOutputStream(
/* [out] */ ISpStreamFormat **ppStream) = 0;
virtual HRESULT STDMETHODCALLTYPE Pause( void) = 0;
virtual HRESULT STDMETHODCALLTYPE Resume( void) = 0;
virtual HRESULT STDMETHODCALLTYPE SetVoice(
/* [in] */ ISpObjectToken *pToken) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVoice(
/* [out][annotation] */
_Outptr_ ISpObjectToken **ppToken) = 0;
virtual HRESULT STDMETHODCALLTYPE Speak(
/* [string][in][annotation] */
_In_opt_ LPCWSTR pwcs,
/* [in] */ DWORD dwFlags,
/* [out][annotation] */
_Out_opt_ ULONG *pulStreamNumber) = 0;
virtual HRESULT STDMETHODCALLTYPE SpeakStream(
/* [in] */ IStream *pStream,
/* [in] */ DWORD dwFlags,
/* [out][annotation] */
_Out_opt_ ULONG *pulStreamNumber) = 0;
virtual HRESULT STDMETHODCALLTYPE GetStatus(
/* [out] */ SPVOICESTATUS *pStatus,
/* [out][annotation] */
_Outptr_ LPWSTR *ppszLastBookmark) = 0;
virtual HRESULT STDMETHODCALLTYPE Skip(
/* [string][in] */ LPCWSTR pItemType,
/* [in] */ long lNumItems,
/* [out] */ ULONG *pulNumSkipped) = 0;
virtual HRESULT STDMETHODCALLTYPE SetPriority(
/* [in] */ SPVPRIORITY ePriority) = 0;
virtual HRESULT STDMETHODCALLTYPE GetPriority(
/* [out] */ SPVPRIORITY *pePriority) = 0;
virtual HRESULT STDMETHODCALLTYPE SetAlertBoundary(
/* [in] */ SPEVENTENUM eBoundary) = 0;
virtual HRESULT STDMETHODCALLTYPE GetAlertBoundary(
/* [out] */ SPEVENTENUM *peBoundary) = 0;
virtual HRESULT STDMETHODCALLTYPE SetRate(
/* [in] */ long RateAdjust) = 0;
virtual HRESULT STDMETHODCALLTYPE GetRate(
/* [out] */ long *pRateAdjust) = 0;
virtual HRESULT STDMETHODCALLTYPE SetVolume(
/* [in] */ USHORT usVolume) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVolume(
/* [out] */ USHORT *pusVolume) = 0;
virtual HRESULT STDMETHODCALLTYPE WaitUntilDone(
/* [in] */ ULONG msTimeout) = 0;
virtual HRESULT STDMETHODCALLTYPE SetSyncSpeakTimeout(
/* [in] */ ULONG msTimeout) = 0;
virtual HRESULT STDMETHODCALLTYPE GetSyncSpeakTimeout(
/* [out] */ ULONG *pmsTimeout) = 0;
virtual /* [local] */ HANDLE STDMETHODCALLTYPE SpeakCompleteEvent( void) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE IsUISupported(
/* [in] */ LPCWSTR pszTypeOfUI,
/* [in] */ void *pvExtraData,
/* [in] */ ULONG cbExtraData,
/* [out] */ BOOL *pfSupported) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE DisplayUI(
/* [in] */ HWND hwndParent,
/* [in] */ LPCWSTR pszTitle,
/* [in] */ LPCWSTR pszTypeOfUI,
/* [in] */ void *pvExtraData,
/* [in] */ ULONG cbExtraData) = 0;
}; |
How can I get an error message string from a HRESULT error code? |
I've added If you |
Didn't know that. I'll throw a WindowsException if the HRESULT returned from a call to a SAPI function is not 0. |
One day I had a problem, wanting to use SAPI in my application via COM. Now I have two problems.
It would be nice if there was an extension on IDispatch which had some functions that accepted a List or something as arguments and automatically converted them to COM types and called the specified method or set the specified property.
I saw the example/idispatch.dart wrapper class, but that doesn't seem to compile anymore and calling a method is still pretty complicated.
The text was updated successfully, but these errors were encountered: