Skip to main content

Notice

Please note that most of the software linked on this forum is likely to be safe to use. If you are unsure, feel free to ask in the relevant topics, or send a private message to an administrator or moderator. To help curb the problems of false positives, or in the event that you do find actual malware, you can contribute through the article linked here.
Topic: Mixing Foobar2000 with CLR (.NET) (Read 6050 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Mixing Foobar2000 with CLR (.NET)

I’ve just about run out of ideas with this problem. Say I have an “empty” Foobar plugin with the following code (literally this is it):
Code: [Select]
#include "foobar2000.h"
DECLARE_COMPONENT_VERSION("dotnet test", "1.0", "body massage!");

When you compile it, it works perfectly in native mode. So, then let me turn on .NET support (/clr basically). component_client also needs /clr since it appears to contain the initialization of the component itself. The plugin compiles and works in Foobar just fine with /clr.

But, there is an odd problem. Foobar’s drag and drop support becomes broken and the SysTreeView32 in 'Add Folder…' has vanished. It’s obvious the plugin hasn’t done anything explicit to cause these problems.

These issues don't exist when making a CLR DLL for 0.8.3 in .NET 1.1. It's a new problem using 0.9 and .NET 2.0.

So what could be causing this? I’m really stumped, it’s a very strange conflict. If anyone has an idea or even a hypothesis, it would be great to hear since I’m out of ideas.

Mixing Foobar2000 with CLR (.NET)

Reply #1
I checked what is happening on my side, seems like the call to OleInitialize when the UI is initialising fails with RPC_E_CHANGED_MODE.

Seems like .NET or something is initialising COM on the main thread, with the COINIT_MULTITHREADED concurrency model, before the UI initialises. There's some indication on MSDN that this will cause SHBrowseForFolder to fail too.

[edit] Maybe its /CLRTHREADATTRIBUTE ?
.


Mixing Foobar2000 with CLR (.NET)

Reply #3
Quote
Perhaps this can be fixed using System.STAThreadAttribute.
[a href="index.php?act=findpost&pid=375937"][{POST_SNAPBACK}][/a]

I would say it shouldn't initialise the COM with STA either, that will still cause Ole to fail to initialise (I think). Depends on what exactly that will do really. But the seemingly equivalent "CLR Thread Attribute" setting allows None as a value. Its also under Linker/Advanced in project properties. Although, for "None" it says "Lets the Common Language Runtime (CLR) set the default threading attribute.", so I guess it will end up as MTA again. It does also say "Setting the thread attribute is only valid when building an .exe".

[edit] Ok, I was wrong, OleInitialize does succeed if COM was initialised as STA (COINIT_APARTMENTTHREADED). So that should work here too.
.

Mixing Foobar2000 with CLR (.NET)

Reply #4
Dumb hack that works for the simplest case, I'll have to do more testing with a larger component like foo_prettypop later. Thanks for the ideas guys, wouldn't have figured it out without you.

Code: [Select]
#pragma unmanaged
BOOLEAN WINAPI TestEntry(IN HINSTANCE hDllHandle, IN DWORD     nReason,  IN LPVOID  Reserved ) {
    CoInitializeEx(0, COINIT_APARTMENTTHREADED);
    return true;
}

Set entry point of DLL to TestEntry (in Linker options)

Mixing Foobar2000 with CLR (.NET)

Reply #5
Why would it not work? STA is the default when a native application calls CoIntialize and the only alternative to MTA when using CoInitializeEx, even though in COM lingo those concurrency models are called apartment-threaded and multithreaded. See also: COINIT enumeration.

STA/apartment-threading is also required by foo_comserver2; I have no intentions to manually marshall all those calls to the foobar2000 main thread when COM can do it for me.

 

Mixing Foobar2000 with CLR (.NET)

Reply #6
Quote
Why would it not work? STA is the default when a native application calls CoIntialize and the only alternative to MTA when using CoInitializeEx, even though in COM lingo those concurrency models are called apartment-threaded and multithreaded. See also: COINIT enumeration.

STA/apartment-threading is also required by foo_comserver2; I have no intentions to manually marshall all those calls to the foobar2000 main thread when COM can do it for me.
[a href="index.php?act=findpost&pid=375953"][{POST_SNAPBACK}][/a]
(Is this to me?)
Noo.. I know all that (well except about what foo_comserver does). I just thought generally, OleInitialize wouldn't work if COM was already initialised, even if it was initialised as apartment threaded. But I was wrong

If apartment threading is required (seems like it), then maybe the core could initialise COM or OLE itself (doesn't seem like it does).

[edit]For example, you can actually do this sequence of events to crash fb2k:
foobar2000 /nogui
foobar2000 /command:"Add directory..."
ESC when it eventually comes up
.

Mixing Foobar2000 with CLR (.NET)

Reply #7
Quote
I just thought generally, OleInitialize wouldn't work if COM was already initialised, even if it was initialised as apartment threaded. But I was wrong [a href="index.php?act=findpost&pid=375991"][{POST_SNAPBACK}][/a]
Ah, so that was all you were concerned about. Evidently, it works as long as you don't try to change the concurrency model. I guess my explanation could still be useful to readers of this thread with less knowledge on COM.

I really thought the /nogui switch had been removed.

Mixing Foobar2000 with CLR (.NET)

Reply #8
The above solution (hack?) appears to have some side effect somewhere. For instance, WMA playback in a completely separate DLL doesn’t like this. WM playback requires CoInitialize() in a single threaded apartment. The CoInitialize call before playing WM succeeds, but for some reason the WM reader can never open a file. In fact, the WM library never sends any message that a failure has occurred (it just sits trying to open the file it seems).

Mixing Foobar2000 with CLR (.NET)

Reply #9
From MSDN:
Quote
Because there is no way to control the order in which in-process servers are loaded or unloaded, do not call CoInitialize, CoInitializeEx, or CoUninitialize from the DllMain function.

Which I think is basically what your hack is, although Im not too clued up on these things.

Also note:
Quote
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.
Which it doesn't look like you are doing.

Did you have no luck in making .NET use a STA instead of a MTA? If you're still stuck, the MSDN forums can be quite helpful.
.

Mixing Foobar2000 with CLR (.NET)

Reply #10
Quote
Did you have no luck in making .NET use a STA instead of a MTA?
[a href="index.php?act=findpost&pid=376351"][{POST_SNAPBACK}][/a]
I'm pretty sure you can't for a CLR DLL other than this. It seems like a CLR DLL has both an unmanaged and managed entry point. The managed entry point loads .NET runtime (setting MTA since that's the default). By calling CoInit in the "first" entry point (the unmanaged one) as a STA, when .NET starts to load once it reaches the managed entry point it knows it is in a STA and works with that be default.

Since the CLR requires COM, CoInit is going to be called on the main thread at some point, want it or not. (edit:) That was the orginal problem, .NET gets started in that invisible managed DLL entry point that sets MTA and then the Foobar main thread tries to do STA later one.

As far as the example code, that was simply an example. On detach you would call CoUninitialize of course.

Mixing Foobar2000 with CLR (.NET)

Reply #11
Here seems to be a solution that doesn't do a DllMain (but really is quite the same idea). This let's .NET 2.0 load, the UI load correctly, and Windows Media also works. So I'm thinking this is 'it'. You have to edit component client.
Code: [Select]
#pragma unmanaged
static foobar2000_client_impl g_client;

extern "C"
{
    __declspec(dllexport) foobar2000_client * _cdecl foobar2000_get_interface(foobar2000_api * p_api,HINSTANCE hIns)
    {
 CoInitializeEx(0, COINIT_APARTMENTTHREADED);
 g_hIns = hIns;
 g_api = p_api;

 return &g_client;
    }
}

Apparently the managed start up code gets called the first time a managed function is called. This inits COM before the first managed function call. When the first managed function call is made, the CLR will use the current COM STA state.

I think this might be called a proper solution given the current state of things. But there really isn't a way to CoUninit. If the plugin were to call CoUninit there would be no guarantee the UI code wasn't still doing something that needs COM.

Mixing Foobar2000 with CLR (.NET)

Reply #12
Interesting read.