maXcomX 3 Posted March 21, 2023 (edited) Every time I start up an app on OnFormCreate CoInitializeEx(Nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE); returns with 1 (S_FALSE) // The COM library is already initialized on this thread Does creating a form automatically initialize COM? and OnFormCreate CoInitializeEx(nil, COINIT_MULTITHREADED); returns with -2147417850 ($80010106 or 0x80010106) // can't find the description of this return value On Delphi 10.4, Win 11 latest update. What could be a possible issue? Edited March 21, 2023 by maXcomX Share this post Link to post
Remy Lebeau 1396 Posted March 21, 2023 (edited) 12 minutes ago, maXcomX said: Every time I start up an app on initialization CoInitializeEx(Nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE); returns with 1. That is S_FALSE. Quote CoInitializeEx(nil, COINIT_MULTITHREADED); returns with -2147417850 That is RPC_E_CHANGED_MODE. Quote What could be a possible issue? Per the documentation: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex Quote Return value This function can return the standard return values E_INVALIDARG, E_OUTOFMEMORY, and E_UNEXPECTED, as well as the following values. Return code Description S_OK The COM library was initialized successfully on this thread. S_FALSE The COM library is already initialized on this thread. RPC_E_CHANGED_MODE A previous call to CoInitializeEx specified the concurrency model for this thread as multithread apartment (MTA). This could also indicate that a change from neutral-threaded apartment to single-threaded apartment has occurred. Remarks CoInitializeEx must be called at least once, and is usually called only once, for each thread that uses the COM library. Multiple calls to CoInitializeEx by the same thread are allowed as long as they pass the same concurrency flag, but subsequent valid calls return S_FALSE. To close the COM library gracefully on a thread, each successful call to CoInitialize or CoInitializeEx, including any call that returns S_FALSE, must be balanced by a corresponding call to CoUninitialize. You need to initialize the COM library on a thread before you call any of the library functions except CoGetMalloc, to get a pointer to the standard allocator, and the memory allocation functions. Otherwise, the COM function will return CO_E_NOTINITIALIZED. After the concurrency model for a thread is set, it cannot be changed. A call to CoInitialize on an apartment that was previously initialized as multithreaded will fail and return RPC_E_CHANGED_MODE. This means that you are calling CoInitializeEx() on a thread that has already called CoInitialize/Ex() successfully, where S_FALSE means you are trying to set the same concurrency model that has already been assigned to the thread, whereas RPC_E_CHANGED_MODE means you are specifying a concurrency model that is not compatible with the thread's current concurrency model. For instance, are you calling CoInitializeEx() in the main UI thread? The RTL's System.Win.ComObj unit initializes COM in the main UI thread during program startup, using the global CoInitFlags variable to decide whether to use CoInitialize() or CoInitializeEx(). Edited March 21, 2023 by Remy Lebeau Share this post Link to post
maXcomX 3 Posted March 21, 2023 (edited) Oops, I edited the question for some more clarification. Sorry Remy. Edited March 21, 2023 by maXcomX Share this post Link to post
maXcomX 3 Posted March 21, 2023 (edited) I did read the info from ms docs you mentioned, thank you. But I can't understand what this have to do with starting a clean Delphi app calling OnCreateForm give this results while there is nothing initiated before. For example: Creating a new vcl app with just one form and declaring CoInitializeEx on OnFormCreate and CoUninitialize() on FormCloseQuery. When debugging CoInitializeEx produces the results I mentioned at the start of this thread. Edited March 21, 2023 by maXcomX Clarifying Share this post Link to post
maXcomX 3 Posted March 22, 2023 Here a sample to reproduce the issue. TestCoIntitialize.zip Share this post Link to post
programmerdelphi2k 237 Posted March 22, 2023 (edited) look at: VCL -> System.Win.ComObj.pas, line 2600 (RAD 11.3)... and "finalization" section Quote InitProc := @InitComObj; ... --> call "procedure InitComObj;" procedure InitComObj; begin if InitComObjCalled then Exit; if SaveInitProc <> nil then TProcedure(SaveInitProc); if (CoInitFlags <> -1) and Assigned(System.Win.ComObj.CoInitializeEx) then begin NeedToUninitialize := Succeeded(System.Win.ComObj.CoInitializeEx(nil, CoInitFlags)); IsMultiThread := IsMultiThread or ((CoInitFlags and COINIT_APARTMENTTHREADED) <> 0) or (CoInitFlags = COINIT_MULTITHREADED); // this flag has value zero end else NeedToUninitialize := Succeeded(CoInitialize(nil)); InitComObjCalled := True; end; Edited March 22, 2023 by programmerdelphi2k 1 Share this post Link to post
programmerdelphi2k 237 Posted March 22, 2023 now try this "without any" other code in your form sample (just "empty project": procedure TForm1.Button1Click(Sender: TObject); begin if Assigned(InitProc) then ShowMessage('InitProc assigned') else ShowMessage('InitProc NOT assigned'); end; Share this post Link to post
maXcomX 3 Posted March 22, 2023 Thank you, but this is not an expected behaviour of this function, does it? For example if you run this code in C++ all goes well. Delphi issue? Share this post Link to post
programmerdelphi2k 237 Posted March 22, 2023 I think that Remy it's more xperient in "deep MSWin"... and maybe can help better than me sorry if I cannot Share this post Link to post
maXcomX 3 Posted March 22, 2023 Also, there is a big issue when trying to use WASAPI when CoInitializeEx fails. Methods like ActivateAudioInterfaceAsync will fail too with unpredictable results. Share this post Link to post
maXcomX 3 Posted March 22, 2023 4 minutes ago, programmerdelphi2k said: I think that Remy it's more xperient in "deep MSWin"... and maybe can help better than me sorry if I cannot Any way: Thanks a lot for your efforts to solve this issue! Share this post Link to post
programmerdelphi2k 237 Posted March 22, 2023 I dont know almost nothing about COM usage, but if you check if "initproc" before any usage... it was assigned, dont help you? if Assigned(InitProc) then Share this post Link to post
maXcomX 3 Posted March 22, 2023 1 hour ago, Remy Lebeau said: That is S_FALSE. That is RPC_E_CHANGED_MODE. Per the documentation: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex This means that you are calling CoInitializeEx() on a thread that has already called CoInitialize/Ex() successfully, where S_FALSE means you are trying to set the same concurrency model that has already been assigned to the thread, whereas RPC_E_CHANGED_MODE means you are specifying a concurrency model that is not compatible with the thread's current concurrency model. For instance, are you calling CoInitializeEx() in the main UI thread? The RTL's System.Win.ComObj unit initializes COM in the main UI thread during program startup, using the global CoInitFlags variable to decide whether to use CoInitialize() or CoInitializeEx(). Thank you Remy, but I've been through all those things. That's why I ended here 😉 Share this post Link to post
maXcomX 3 Posted March 22, 2023 28 minutes ago, programmerdelphi2k said: I dont know almost nothing about COM usage, but if you check if "initproc" before any usage... it was assigned, dont help you? if Assigned(InitProc) then It does what it's expected to do, but it will not explain why the function is resulting the results I mentioned. Share this post Link to post
Remy Lebeau 1396 Posted March 22, 2023 (edited) Per https://stackoverflow.com/a/20019023/65863: Quote In the RTL/VCL source, COM is initialized in the following ways: By a call to OleInitialize made from Forms.TApplication.Create. So this call will be made for all VCL forms applications, but not, for example, for service applications. By a call to CoInitialize or CoInitializeEx in ComObj.InitComObj. This is registered as an InitProc in the initialization section of the ComObj unit. In turn, the call to Application.Initialize in your project .dpr file's code will invoke ComObj.InitComObj. In many and various other locations around the RTL/VCL. Including, but not limited to, Datasnap, ComServ, Soap, System.Win.Sensors, Winapi.DirectShow9. Some of these areas of code are more recent than Delphi 7. Now, of these various COM initializations, the ones that count are 1 and 2. In any standard VCL forms application, both of these will run at startup in the main thread. Item 1 runs first and so gets to initialize COM first. That's the initialization that counts. Item 2 runs after and returns S_FALSE meaning that COM was already initialized. And also: https://learn.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-oleinitialize Quote OleInitialize calls CoInitializeEx internally to initialize the COM library on the current apartment. Because OLE operations are not thread-safe, OleInitialize specifies the concurrency model as single-thread apartment. So, there you go. If your project uses the ComObj unit, it will call CoInitialize/Ex() when Vcl.Forms.TApplication.Initialize() is called, which won't matter since the constructor of Vcl.Forms.TApplication will have already called OleInitialize() beforehand, which calls CoInitializeEx(): constructor TApplication.Create(AOwner: TComponent); var ... begin inherited Create(AOwner); if not IsLibrary then FNeedToUninitialize := Succeeded(OleInitialize(nil)); ... end; Edited March 22, 2023 by Remy Lebeau 1 Share this post Link to post
Remy Lebeau 1396 Posted March 22, 2023 15 hours ago, maXcomX said: Also, there is a big issue when trying to use WASAPI when CoInitializeEx fails. Methods like ActivateAudioInterfaceAsync will fail too with unpredictable results. Can you be more specific? Share this post Link to post
maXcomX 3 Posted March 22, 2023 (edited) Yes I can. I'm working on a new example that replaces the mmio methods, because they are or will be deprecated (like mmioOpen) And that make all other mmio related methods quite useless. So far I managed to translate a CPP example to it's Delphi equivalent. However Microsoft made a lot of samples based on a DOSBox interface. To be more specific, the DOSbox sample runs well on MS Visual Studio (returning the adequate result of CoInitializeEx() , but not within Delphi 10.4. Here Delphi source including the CPP source: LoopBackCapture_2.zip The Delphi sample uses MFPack Edited March 22, 2023 by maXcomX Share this post Link to post
maXcomX 3 Posted March 23, 2023 I must have overlooked something. The place to call ConitializeEx() should not be called in OnFormCreate, as Remy commented by posting this link. The MS sample is a DOSbox sample, I forgot that when creating a form in Delphi ConitializeEx() has already being called. Thank you Remy. Share this post Link to post
maXcomX 3 Posted March 24, 2023 (edited) I must have overlooked something. When initializing an UI application, any earlier Conitialize() as default, should be set to ConitializeEx() when necessarily. So, if Assigned(InitProc) is true, CoUnitialize() should be called to change the thread model and set to ConitializeEx() to make callbacks working. Also the parameter COINIT_MULTITHREADED that only should be used by DOSbox and other windowless apps, should be COINIT_APARTMENTTHREADED for UI apps. Am I correct? Edited March 24, 2023 by maXcomX Clarifying Share this post Link to post
Remy Lebeau 1396 Posted March 25, 2023 1 hour ago, maXcomX said: When initializing an UI application, any earlier Conitialize() as default, should be set to ConitializeEx() when necessarily. Unfortunately, as I already pointed out earlier, the VCL's TApplication constructor calls OleInitialize() before the ComObj unit has a chance to call CoInitialize/Ex(), and OleInitialize() internally calls CoInitialize(nil), so if you require CoInitializeEx() with any mode other than COINIT_APARTMENTTHREADED then you will have to do your COM work in a separate worker thread instead of in the UI thread. 1 hour ago, maXcomX said: So, if Assigned(InitProc) is true, CoUnitialize() should be called The ComObj unit is not the only unit that uses InitProc. InitProc is a chained list of initialization procedures, so you can't use Assigned(InitProc) alone as an indicator of whether you should call CoInitializeEx() or not. If you need CoInitializeEx() called, then just call it yourself unconditionally, and just be sure to pay attention to whether it fails or not. Returning S_FALSE is NOT a failure condition! 1 hour ago, maXcomX said: to change the thread model and set to ConitializeEx() to make callbacks working. A thread cannot change its COM threading mode once it has been set. That is what the RPC_E_CHANGED_MODE error code tells you. That IS a failure condition! If you need to use a different COM threading model than the calling thread has already been set to, you will have to move your COM work to another thread that is able to be set to the desired COM model. 1 hour ago, maXcomX said: Also the parameter COINIT_MULTITHREADED that only should be used by DOSbox and other windowless apps, should be COINIT_APARTMENTTHREADED for UI apps. Am I correct? Typically, but not necessarily always. It really depends on each app's particular requirements. 1 Share this post Link to post
maXcomX 3 Posted March 28, 2023 (edited) Ok, thank you again for this useful info. I stumbled into this while writing a basic audio loopback application, where I experienced a strange behavior of function ActivateAudioInterfaceAsync. My first thought was it had something todo with ConitializeEx(). But reading the documentation again, the interface should be implemented on an agile object (aggregating a free-threaded marshaler. I think I need to implement the TAggregatedObject, but not sure if that is correct and how to implement this. I tried this: TActivateAudioInterfaceCompletionHandler = class(TInterfacedObject, IActivateAudioInterfaceCompletionHandler) public function ActivateCompleted(activateOperation: IActivateAudioInterfaceAsyncOperation): HRESULT; stdcall; end; TLoopbackCapture = class(TAggregatedObject, IActivateAudioInterfaceCompletionHandler) // etc // constructor constructor TLoopbackCapture.Create(oObj: IUnknown); var hr: HResult; begin inherited Create(IActivateAudioInterfaceCompletionHandler(oObj)); // etc // From the mainform creating tLoopBackCapture is called var OuterObj: TActivateAudioInterfaceCompletionHandler; oLoopbackCapture: TLoopbackCapture; procedure TfrmMain.FormCreate(Sender: TObject); begin OuterObj := TActivateAudioInterfaceCompletionHandler.Create(); oLoopbackCapture := TLoopbackCapture.Create(IActivateAudioInterfaceCompletionHandler(OuterObj)); end; But this approach doesn't work either. When calling ActivateAudioInterfaceAsync, function TAggregatedObject.QueryInterface(const IID: TGUID; out Obj): HResult; begin Result := IInterface(FController).QueryInterface(IID, Obj); << FController = nil end; No Interface ($80004002), IID = {'94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90'}, well actually that's the identifier of the IAgile interface.. Followed by EAccessError. So, I think implementing TAggregatedObject is not the way, but implementing IAgile should do it? It's a real headbanger.. 😞 Edited March 28, 2023 by maXcomX Typo Share this post Link to post
Remy Lebeau 1396 Posted March 29, 2023 5 hours ago, maXcomX said: But reading the documentation again, the interface should be implemented on an agile object (aggregating a free-threaded marshaler. Aggregating the FTM means your COM object would need to call the CoCreateFreeThreadedMarshaler() function and save the IUnknown interface it gives you, and then have your object's QueryInterface() method return that interface when it is asked for IID_IMarshal. However, nothing in the ActivateAudioInterfaceAsync() documentation mentions aggregating the FTM at all. All it says is that your completionHandler object needs to implement the IAgileObject interface, nothing more. IAgileObject is an indicator that lets the system know that the object is free-threaded and thus can be called across apartment boundaries without marshaling. 5 hours ago, maXcomX said: I think I need to implement the TAggregatedObject, but not sure if that is correct and how to implement this. You DO NOT need to implement TAggregatedObject in this situation. 5 hours ago, maXcomX said: No Interface ($80004002), IID = {'94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90'}, well actually that's the identifier of the IAgile interface.. TAggregatedObject and IAgileObject are completely different and related things. 5 hours ago, maXcomX said: Followed by EAccessError. Right, because you did not hook up the TAggregatedObject.Controller to point at another COM object. As you can see in the code snippet you posted, TAggregatedObject.QueryInterface() simply delegates to another COM object. 5 hours ago, maXcomX said: So, I think implementing TAggregatedObject is not the way Correct. 5 hours ago, maXcomX said: but implementing IAgile should do it? Yes. You would need something like this: TActivateAudioInterfaceCompletionHandler = class(TInterfacedObject, IActivateAudioInterfaceCompletionHandler, IAgileObject) public function ActivateCompleted(activateOperation: IActivateAudioInterfaceAsyncOperation): HRESULT; stdcall; end; function TActivateAudioInterfaceCompletionHandler.ActivateCompleted(activateOperation: IActivateAudioInterfaceAsyncOperation): HRESULT; stdcall; begin // do something... end; ... ActivateAudioInterfaceAsync( ..., TActivateAudioInterfaceCompletionHandler.Create as IActivateAudioInterfaceCompletionHandler, ... ); Share this post Link to post
maXcomX 3 Posted March 30, 2023 (edited) Thanks, Remy. IAgileObject works as expected. On 3/29/2023 at 2:40 AM, Remy Lebeau said: However, nothing in the ActivateAudioInterfaceAsync() documentation mentions aggregating the FTM at all. All it says is that your completionHandler object needs to implement the IAgileObject interface, nothing more. IAgileObject is an indicator that lets the system know that the object is free-threaded and thus can be called across apartment boundaries without marshaling. This text confused me: E_ILLEGAL_METHOD_CALL On versions of Windows previous to Windows 10, this error may result if the function is called from an incorrect COM apartment, or if the passed IActivateAudioInterfaceCompletionHandler is not implemented on an agile object (aggregating a free-threaded marshaler). So on first thought, I would need an aggregated interface instead of an IAgileObject interface. And because IAgileObject is not defined in any Delphi version, TAggregatedObject bubbled up.. Being confused has nothing to do with Confucius I would say... For the audience I translated IAgileObject to Delphi (which should be declared in ObjIdl, part of Delphi ActiveX?) // Interface IAgileObject // ====================== // The IAgileObject interface is a marker interface that indicates that an object // is free threaded and can be called from any apartment. // Unlike what happens when aggregating the Free Threaded Marshaler (FTM), // implementing the IAgileObject interface doesn't affect what happens when marshaling a call. // Instead, the IAgileObject interface is recognized by the Global Interface Table (GIT). // When an object that implements the IAgileObject interface is placed in the GIT and // localized to another apartment, the object is called directly in the new apartment, rather than marshaling. {$HPPEMIT 'DECLARE_DINTERFACE_TYPE(IAgileObject);'} {$EXTERNALSYM IAgileObject} IAgileObject = interface(IUnknown) ['{94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90}'] // public end; IID_IAgileObject = IAgileObject; {$EXTERNALSYM IID_IAgileObject} Again: Thank you for your help and explanations on this subject. Edited March 30, 2023 by maXcomX Typo Share this post Link to post