2
    function GetFileIcon(const filename:string): HICON;
var
  shfi: TShFileInfo;
begin
  try
    FillChar(shfi, SizeOf(TShFileInfo), 0);
    ShGetFileInfo(PChar(filename), 0, shfi, SizeOf(TShFileInfo), SHGFI_ICON or SHGFI_LARGEICON);
    Result := shfi.hIcon;
  except
    Result := 0;
  end;
end;

Using delphi xe2, on win 7 64bits, this function will often return 0 when called inside a Tthread, but is always working fine when called from main thread. It looks like a shell initialization problem, because after a while it will work in the Thread as well. I found a similar question in stack overflow (Calling SHGetFileInfo in thread to avoid UI freeze) but it is for c++ language so I did not sort it out.

  • Update: It seems ShGetFileInfo is not threadsafe. When there are multiple threads calling it simultaneously, it fails. See David Hefferman's answer below. Also using CoInitializeEx instead of Coinitialize does not help with multiple threads. You have to serilize access using a TCriticalSection.
Community
  • 1
  • 1
user1238784
  • 2,250
  • 3
  • 22
  • 41
  • 2
    I bet you forgot to initialize COM. – TLama Feb 19 '14 at 16:09
  • 2
    [`This way`](http://pastebin.com/iQs2YYTb). – TLama Feb 19 '14 at 16:14
  • 1
    CoInitializeEx(), from the ActiveX unit. – Ken White Feb 19 '14 at 16:14
  • Thanks I googled for the difference between coinitialize and CoInitializeEx, but cannot really find anything. Is there any reason to pick one or the other. – user1238784 Feb 19 '14 at 16:21
  • 2
    What hope can you possibly have of solving programming problems when you ignore error codes? Also, regarding the difference between `CoInitialize` and `CoInitializeEx`, the first Google result for "CoInitialize vs CoInitializeEx" is a link to the MSDN documentation, and Google even excerpts the relevant portion on the result page: "New applications should call CoInitializeEx instead of CoInitialize." The fifth result for me is a link to [a Stack Overflow question asking *exactly* what you want to know](http://stackoverflow.com/q/19882174/33732). – Rob Kennedy Feb 19 '14 at 17:12
  • I am not ignoring error codes, I know that it was not a problem of error code, that's different – user1238784 Feb 19 '14 at 17:28
  • You are ignoring error codes. The code in the question is clear. – David Heffernan Feb 19 '14 at 17:41
  • yes but the problem is not due to ignoring the code – user1238784 Feb 19 '14 at 17:47
  • @user1238784 No, as it happens. But that's always the first thing we get exercised about. And in your real code you must check for errors. – David Heffernan Feb 19 '14 at 21:19

1 Answers1

4

From the documentation:

You must initialize Component Object Model (COM) with CoInitialize or OleInitialize prior to calling SHGetFileInfo.

In a GUI app, the COM is initialized in the main thread. But from other threads that does not happen automatically. You will need to do it explicitly.

Beyond that you are not handling errors correctly. Remember that Windows API functions do not raise exceptions. So your exception handler is pointless and should be removed. Instead you need to check the return value of your call to SHGetFileInfo, as described in the documentation.

Beyond that your code works, as this program demonstrates:

{$APPTYPE CONSOLE}

uses
  Classes, Windows, ActiveX, ShellAPI;

var
  hThread: THandle;
  ThreadId: Cardinal;

function ThreadFunc(Parameter: Pointer): Integer;
var
  shfi: TSHFileInfo;
begin
  CoInitialize(nil);
  Try
    if ShGetFileInfo('C:\windows\explorer.exe', 0, shfi, SizeOf(shfi), SHGFI_ICON or SHGFI_LARGEICON)=0 then
    begin
      Writeln('ShGetFileInfo Failed');
      Result := 1;
      exit;
    end;
    Writeln(shfi.hIcon);
  Finally
    CoUninitialize;
  End;
  Result := 0;
end;

begin
  hThread := BeginThread(nil, 0, ThreadFunc, nil, 0, ThreadId);
  WaitForSingleObject(hThread, INFINITE);
  CloseHandle(hThread);
  Readln;
end.

I expect that any failure you observe is actually related to the particular file that you are trying to inspect.


Update: It seems ShGetFileInfo is not threadsafe. When there are multiple threads calling it simultaneously, it fails. I believe that you will need to serialize the calls to ShGetFileInfo with a lock. For instance, TCriticalSection.

The following program, based on the SSCCE you provided in the comments, demonstrates this:

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  SyncObjs,
  Windows,
  ActiveX,
  ShellAPI;

var
  hThreads: TWOHandleArray;
  ThreadId: Cardinal;
  Lock: TCriticalSection;

function ThreadFunc(Parameter: Pointer): Integer;
var
  shfi: TSHFileInfo;
  randomnumber: integer;
  fname: string;
begin
  CoInitialize(nil);
  Try
    fname := 'c:\desktop\file'+IntToStr(Integer(Parameter))+'.exe';

    Lock.Acquire;
    try
      if ShGetFileInfo(pchar(fname), 0, shfi, SizeOf(shfi), SHGFI_ICON or SHGFI_LARGEICON)=0 then
      begin
        Writeln('ShGetFileInfo Failed');
        Result := 1;
        exit;
      end;
      Writeln(shfi.hIcon);
    finally
      Lock.Release;
    end;
  Finally
    CoUninitialize;
  End;
  Result := 0;
end;

var
i: integer;
begin
  Lock := TCriticalSection.Create;
  for i := 0 to 9 do
    hThreads[i] := BeginThread(nil, 0, ThreadFunc, Pointer(i), 0, ThreadId);

  WaitForMultipleObjects(10, @hThreads,true, INFINITE);

  Readln;
end.

Remove the critical section, and the calls to ShGetFileInfo succeed, but return 0 for the icon handle. With the critical section, valid icon handles are returned.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • thanks, I now found out this other duplicate post about same issue: http://stackoverflow.com/questions/11394007/threaded-loading-of-icons-in-delphi?rq=1 – user1238784 Feb 19 '14 at 16:25
  • I tried to call coInitialize inside the Execute method of the thread before loading the icon but still does not work?? – user1238784 Feb 19 '14 at 16:42
  • I cannot see your code so I've no idea whether you did it right or not. You also do not check the return value of `SHGetFileInfo`. – David Heffernan Feb 19 '14 at 16:45
  • well the code is quite simple: In the Execute, I call: Coinitialize(nil) try //here call my class method to load icon finally coUninitialize; end; – user1238784 Feb 19 '14 at 16:47
  • I've answered a lot of questions here where people made mistakes with seemingly simple things. Forgive me please for being a little sceptical. Anyway, you really ought to fix your error checking. – David Heffernan Feb 19 '14 at 16:49
  • look I am writing into these tiny lines, the error checking looks fine to me. The error checking is not the issue, the thing is I tried your suggestion and did not work – user1238784 Feb 19 '14 at 16:53
  • The error checking is wrong. Read the final paragraph of my answer. Make sure you update to the latest version of the answer. Read also the documentation of the function that you are calling. Note that it describes error reporting through the return value which your code ignores. – David Heffernan Feb 19 '14 at 16:54
  • Ok the error check was wrong, still that does not fix my problem – user1238784 Feb 19 '14 at 16:56
  • Looks like your problem is not with your code, but with the file that you are working with. Run the example program in my answer to see that your code works. – David Heffernan Feb 19 '14 at 17:01
  • My application creates multiple threads, all I am trying to load are the icons of some folder (so it's not a file related problem) the first time I launch them it will show only one folder. I launch them second time and all icons are shown. I just tried my code with the explorer icon and I get same behavior. – user1238784 Feb 19 '14 at 17:07
  • Does it make any difference with your code if I am creating and launching the threads from within another Thread (not main) ? – user1238784 Feb 19 '14 at 17:14
  • What happens when you run my code? What is the output? – David Heffernan Feb 19 '14 at 17:15
  • your code worked. output is a number different from zero. But I now commented out the coInitialize and CoUninitialize in your code and it works anyway. So it does not make any difference – user1238784 Feb 19 '14 at 17:21
  • Well, the documentation is clear. You have to initialize COM. You might get away with it for some files, and some combinations of parameters, but not all. Or do you think that MSDN is wrong? And you weren't checking errors. So, everything in my answer is accurate. The fact that your code, with the corrections I outlined, performs as expected indicates that the problem is in code that is not present in the question. I have answered the question that you asked. Your task now is to work out what is special in the code that you did not show us. – David Heffernan Feb 19 '14 at 17:28
  • Perhaps it's a simple sharing violation. Multiple threads trying to access the same file or folder. Or perhaps it really is specific to the file or folder. Did you check for any patterns with the ones that fail. Anyway, I hope you can see that there's nothing more that anyone can do with the information your provided other than guess. – David Heffernan Feb 19 '14 at 17:29
  • you may have given me a right hint but that did not solve the problem. I am just trying toi understand why it does not – user1238784 Feb 19 '14 at 17:34
  • a sharing violation could be the reason yes, but I am trying to load the icons for the subfolders of the d:\ folder. Also the d:\ folder is a shared folder on a network. Each thread loads a different subfolder. Is it possible to have sharing violation in thiscase? – user1238784 Feb 19 '14 at 17:36
  • You are getting confused between the answer to the question that you asked and the solution to the problem you face. They are not the same thing. Clearly we cannot, without relying on a lucky guess, solve your problem. Because I have demonstrated that the question is missing the crucial details. So, until we can reproduce your problem, we cannot solve it. All we can do is answer the question that you asked. – David Heffernan Feb 19 '14 at 17:40
  • well the answer to the question was to know the reason why it did not work that code and still does not. – user1238784 Feb 19 '14 at 17:41
  • I tried to load other files in other folders. When I hit the first time it does not work, second and subsequent times always works. Maybe I can send you the sample program – user1238784 Feb 19 '14 at 17:43
  • I beg to differ. The code in my answer does work. You've confirmed that. Your problem is with code that we cannot see. How can you expect us to say anything about that code? And no, I don't want you to send me your program. What we need is an SSCCE. But that should be a new question now. I think this one is done. – David Heffernan Feb 19 '14 at 17:44
  • Bause the problem is not the code you cannot see, it is in the api call. Once I have called the com initialize the thing should work but it does not. It does not depend from any my code. Ando the fact that it works the second time and subsequents tell me it's a microsoft issue – user1238784 Feb 19 '14 at 18:29
  • So explain how the code in my answer works and the exact same code in your program fails? Surely it is environmental. We just need an sscce. – David Heffernan Feb 19 '14 at 18:36
  • I found out that putting my program on another pc with win 7 32bits works all the time. Your code was a single thread, mine is multiple threads – user1238784 Feb 19 '14 at 18:40
  • Again I beg to differ. Read the question again. The question says it works fine in the main thread, but fails in a different thread. Again you are confusing the solution to your problem with the answer to the question you asked. Next step is an sscce. Little point doing anything else until you have made that. – David Heffernan Feb 19 '14 at 18:44
  • Well that was my guess that the reason could be because it is in a different thread, but here there is also a windows problem. The code I should post is too much, because I use a class to load the icon and the class can load any type of picture file, it's very complex. Also the threads are created inside another thread. It's impossible to post the code. But if the principle is that each call to the api must be preceeded and followed by the com initialization and unitialization, that I am doing correctly. – user1238784 Feb 19 '14 at 18:48
  • That's not the principle. You init COM when the thread starts, and finalise when it ends. You do need to make an sscce. That requires you to cut the code down. Read the page at http://sscce.org – David Heffernan Feb 19 '14 at 18:51
  • I modified your code to be multithreaded and it did not work. It returns all zero except one – user1238784 Feb 19 '14 at 20:05
  • Again, I cannot see that code. Is each thread requesting information for the same file? – David Heffernan Feb 19 '14 at 20:25
  • where can i post the code. And yes it's requesting icon of same file – user1238784 Feb 19 '14 at 20:28
  • Well, what do you expect will happen when multiple threads attempt to access the same file. It's a sharing violation. Where can you post the code? In a new question, once you have cut it down to a reasonable size. That will take a little bit of effort from you. – David Heffernan Feb 19 '14 at 20:31
  • if it was a sharing violation why is it always the last thread to output the non zero value – user1238784 Feb 19 '14 at 20:40
  • I don't know. Do you have any ideas? Instead of spending all this time in comments, you could have made an SSCCE by now. – David Heffernan Feb 19 '14 at 20:44
  • Well you could help me modifying your code so that it is multithreaded and loads different files icons? That would be my SSCCE. I believe that it will not work. – user1238784 Feb 19 '14 at 20:46
  • I'm busy. Sorry. I think you should make the SSCCE. – David Heffernan Feb 19 '14 at 20:49
  • 1
    That's a good effort for an SSCCE. We can work with that. You should have done that a couple of hours ago. ;-) – David Heffernan Feb 19 '14 at 20:53
  • Thanks, I see your update, but why do you put a critical section. Why do i need that to call a windows api? Oh now I see the call is not threadsafe. So I discovered something right!! – user1238784 Feb 19 '14 at 21:07
  • anyway this looks like a major limitation. I use multiple threads to load pictures and they work just fine. it does not seem even real that I need to serialize a api call... – user1238784 Feb 19 '14 at 21:13
  • 1
    It seems pretty conclusive that's the case though. Probably the code in `ShGetFileInfo` uses global variables!! – David Heffernan Feb 19 '14 at 21:13
  • ok I'll see what I can do to serialize..thanks for all your help for now. – user1238784 Feb 19 '14 at 21:18
  • Just wanted to say, today I had the chance to apply the necessary serialization to my code and it works very well..Thanks agian for all your help, David. – user1238784 Feb 21 '14 at 13:25