0

I'm using system image list in my application, as will be explained below. Calling this from the main thread works perfectly, however, if when I try to do this from a different thread, it causes error for a reason that I don't really understand.

The system image list interface is:

 <ComImportAttribute(), GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
Private Interface iImageList
    <PreserveSig> _
    Function Add(ByVal hbmImage As IntPtr, ByVal hbmMask As IntPtr, ByRef pi As Integer) As Integer

'here comes the rest of standard functions for this interface

End Interface

The Windows API definition is:

  <DllImport("shell32.dll", EntryPoint:="#727")> _
Private Shared Function SHGetImageList(ByVal iImageList As Integer, ByRef riid As Guid, ByRef ppv As IImageList) As Integer
End Function

And finally, to get\create the image list, I am executing the following:

Dim ID As New Guid("46EB5926-582E-4017-9FDF-E8998DAA0950")
Dim Intrf As iImageList=Nothing
Dim extraLargeIcons = &H2
Dim Ret As Integer = SHGetImageList(CInt(Fix(extraLargeIcons)), ID, Intrf )

This last call to create the list casing the following error if called from a non-main thread (simply calling it from standard .Net Background worker):

Unable to cast COM object of type 'System.__ComObject' to interface type 'iImageList'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{46EB5926-582E-4017-9FDF-E8998DAA0950}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

I would like to understand why this happening and why iImageList interface become suddenly not supported when created in a different thread (it works perfectly from main thread), and if there is a way to resolve this.

P.S.

I did read similar questions, but I am not passing information across threads (I am trying to create and use Imagelist from one thread), also I did not understood how to resolve this. Using STA\MTA-Thread attribute gave nothing. Finally, I should say that I know little about COM business.

TMS
  • 129
  • 7
  • What do you mean, you are not passing information across threads? You are too passing a COM interface pointers across threads - how else are you "calling it from a different thread"?. When called from a "wrong" thread, an interface requires marshaling, but this particular interface is non-marshalable. – Igor Tandetnik Jun 04 '17 at 00:45
  • @Igor, could you expand on that? As far as I can see, SHGetImageList doesn't take a COM interface pointer as input, instead it generates one. Shouldn't it be able to generate such a pointer regardless of what thread it is running in? – Harry Johnston Jun 04 '17 at 03:18
  • 1
    I guess I misunderstood. I thought you were saying that you called `SHGetImageList` in one thread, but then called through the resulting interface pointer in another. I'm pretty sure `SHGetImageList` must be called from an STA thread, but it shouldn't matter *which* STA thread; it should work from a worker thread as long as that thread joins STA. – Igor Tandetnik Jun 04 '17 at 03:53
  • @Igor, perhaps there's an undocumented limitation, e.g., it needs to be a thread that has an existing message queue, or has created a window, or something along those lines? – Harry Johnston Jun 04 '17 at 04:03
  • 3
    Why are you importing `SHGetImageList()` by ordinal? MSDN doesn't document any specific ordinal number to use. – andlabs Jun 04 '17 at 04:06
  • @HarryJohnston [This program works](http://rextester.com/QLBMY38994). It creates a worker thread that does nothing but initialize STA and obtain an `IImageList` pointer. Actually, upon closer inspection, [MTA thread works, too](http://rextester.com/QIRL18474). I'm not sure what the OP is doing wrong - I can't seem to get `SHGetImageList` to fail. – Igor Tandetnik Jun 04 '17 at 04:17
  • TMS, could you please provide a [mcve]. – Harry Johnston Jun 04 '17 at 04:47
  • @Igor, yeah, looking more closely at the error message I get the impression this is some sort of .NET/COM interop thing - the call to ShGetImageList appears to have successfully returned a COM object, but .NET couldn't process it. Darned if I know why that would depend on the thread. Way over my head, I fear. :-) – Harry Johnston Jun 04 '17 at 04:52
  • 3
    This function (like many SHxxx functions) probably creates COM objects undercover. It's possible that these objects are cached (since there is an associated `FileIconInit` function that can "reinitialize" things). If that happens it's possible that they are called on a different type of apartment, and this is typically what provokes E_NOINTERFACE. When using shell functions, I think it's better to make *all threads STA* (have a look at SHGetFileInfo remarks as an example). – Simon Mourier Jun 04 '17 at 05:39
  • Thx for all commenters, The exception happens when I am calling SHGetImageList from the standard .Net Background worker, nothing fancy. @andlabs can you please explaine more? I believe I did same what pinvoke.net states – TMS Jun 04 '17 at 10:58
  • 5
    The non-obvious detail is that icons can be generated by [a shell extension](https://msdn.microsoft.com/en-us/library/windows/desktop/cc144122(v=vs.85).aspx). Such extensions get a very nice guarantee from the plugin architecture, they don't have to be thread-safe. Problem is, the thread that BackgroundWorker uses is always in the MTA, the short way to say "provides no help for components that are not thread-safe". There is no proxy/stub for IImageList. And the fallback isn't there, doesn't implement IMarshal either, which is what the error message really means. – Hans Passant Jun 04 '17 at 11:54
  • Yes, pinvoke.net is importing by ordinal rather than by name -_- I wonder if simply removing `EntryPoint:="#727"` would be sufficient to ensure forward compatibility should Microsoft ever change the ordinals. This wouldn't fix the specific error here, though. – andlabs Jun 04 '17 at 12:57
  • @HansPassant I just tried something like: `Th = New Threading.Thread(AddressOf SHGetImageList) Th.SetApartmentState(Threading.ApartmentState.STA) Th.Start()` with this output. – TMS Jun 04 '17 at 13:26
  • 5
    Maybe that worked, but you still cannot use the returned interface. It is not thread-safe. **All** of the code has to run on an STA thread, not just SHGetImageList() but also the code that uses the interface. And you have to honor the STA contract, calling Application.Run() is not optional. An example of an STA thread [is here](https://stackoverflow.com/a/21684059/17034). Or just stop fighting the machine, this function was made to run on your UI thread so use it from there. – Hans Passant Jun 04 '17 at 13:48

0 Answers0