Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations John Tel on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Help on using IExtractImage 1

Status
Not open for further replies.

phinoppix

Programmer
Jul 24, 2002
437
US
Hi,


It seems MSDN does not have sufficient documentation on IExtractImage (shell extension for generating custom thumbnail). So far, I already have the public class that will be registered as the thumbnail extension for a particular file extension (eg. ".abc"). This COM class implements IExtractImage and IPersistFile. So far, when I open Explorer and view thumbnails of .abc files, it runs as expected. But when I do a refresh, the thumbnails are gone, just white background. Also, when I right click or double click an .abc file, Explorer crashes.

This is the snippet of the extension class:
Code:
	[ComVisible(true)]
	[Guid("D1C74EF1-C150-4c9c-BA73-453B8B1C0D90")]
	[ClassInterface(ClassInterfaceType.None)]
	public class Class1 : IExtractImage, IPersistFile
	{
                /* Section ommitted for brevity */
		string tmpImgFile = "";
		Size imgSize = Size.Empty;

		public long GetLocation(out StringBuilder pszPathBuffer, 
			int cch, ref int pdwPriority, ref SIZE prgSize, 
			int dwRecClrDepth, ref int pdwFlags)
		{
			OutputDebugString("GetLocation");
			pszPathBuffer = new StringBuilder();
			//pszPathBuffer.Append(tmpImgFile);
			imgSize = new Size(prgSize.cx, prgSize.cy);

			// Windows XP (or even earlier) sets a null pointer for pdwPriority when selecting a file.
			// MSDN does not explain any reasons.
			// Vista, however, ignores this parameter.

			if ((pdwFlags & IEIFLAG_ASYNC) > 0) return E_PENDING;
			return NOERROR;
		}

		public int Extract(out IntPtr phBmpThumbnail)
		{
			OutputDebugString("Extract");
			Bitmap tmpBmp = new Bitmap(imgSize.Width, imgSize.Height);
			using (Graphics dcBmp = Graphics.FromImage(tmpBmp))
			{
				dcBmp.Clear(Color.Wheat);
				using (Pen p = new Pen(Color.Black, 1.0f))
				{
					dcBmp.DrawLine(p, 0, 0, imgSize.Width, imgSize.Height);
				}
			}

			phBmpThumbnail = tmpBmp.GetHbitmap();
			return NOERROR;
		}
Another thing, the MSDN states that if the COM is compiled as free-threaded, the IRunnableTask interface must be implemented also. Question: How do I know my COM assembly is free-threaded? I can see that the assembly's threading model is registered as "Both", is this different from being a free-threaded? Why do the thumbnails disappear every time I hit refresh? And, if it is, what is the IID of IRunnableTask (couldn't find it from the SDK) ?

I hope someone can help me on this.

TIA [wink]
 
You are doing all that just to get a thumbnail?

Bitmap bmp = Bitmap.FromFile(@"C:\File.jpg");

Bitmap thumb = bmp.GetThumbnail();

That will work too....
 
Thanks for the response. What I have is a shell extension for Windows Explorer. As I mentioned, when user wants to view the files (in Explorer) which may contain either ASCII or binary data, this shell extension will **parse the data** (the files being custom data) and build the thumbnail image on the fly. The snippet I provided is just to test if the shell extension, when properly registered, really works with Windows Explorer.

Generating the thumbnail is the easy part. My problem is making the shell extension work properly. At least it does the first time the folder is viewed. But when I refresh, the thumbnails are no longer generated. Much worse, when I try to double-click the file to open, or just right-click, Explorer crashes.
 
Forgot to close this thread.

I remembered how GC would move IntPtr rendering them useless. So I have to work with unsafe pointers so I can properly return the bitmap object. Also I didn't noticed the Extract() parameter is a pointer to a handle, and am passing the handle improperly. In fact, has to dereference the pointer var and set the bitmap handle, which was quite tricky, but has a work around on C#.

The Extract() has IntPtr parameter. To dereference, I needed to get the unsafe pointer via IntPtr.ToPointer() w/c returns a void*. But since I cannot dereference a void*, I have to cast it to int* first, set the bitmap handle, then cast it back to void*. The only caveat w/ this solution is that int* would pertain to a pointer to a 32-bit integer. On a 64-bit OS, this is an issue (since the managed Int32 is really just 32-bits). The only feasible solution I can think is to get the OS version, then conditionally apply a proper integral cast if 32 or 64-bit).

The thumbnails are working now and explorer no longer crashes. But I'm now just trying to figure out why Explorer freezes for quite a while when I open it with WinLogo+E; other shortcuts don't have problems though.

Thanks
 
Im having exactly the same problems as you, and im quite at a loss fumbling with unsafe pointers (one of the main reasons i use C# is I *dont* have to use them). Could you please post your corrected Extract method?

TIA
Joao Correia
 
Hi jcorreia123,

Basically I have...
Code:
public unsafe int Extract(IntPtr phBmpThumbnail)
{
    // Assuming you already have hBmp as the handle to your Bitmap object...

    if (IntPtr.Size = 4)
    {
        int* pBuffer = (int*)phBmpThumbnail.ToPointer();
        *pBuffer = hBmp.ToInt32();

        [COLOR=green]// IMO, casting back to (void*) is not necessary.
        // I guess just for formality, that pBuffer points
        // to an object, not an int.  :)[/color]
        phBmpThumbNail = new IntPtr((void*)pBuffer);
    }
    else // 8-bytes, or 64-bit
    {
         long* pBuffer = (long*)phBmpThumbnail.ToPointer();
        *pBuffer = hBmp.ToInt64();
        phBmpThumbNail = new IntPtr((void*)pBuffer);
    }

    return NOERROR;
}
IntPtr.ToPointer() returns a void*, but as stated on the SDK, dereferencing a void* is not allowed, that's why the cast to a pointer to an integer type. The if block is necessary to test for processor type if 32 or 64 bit.

Btw, the reason why Explorer freezes for a moment when opening a new instance is because (in my case) I was returning IEIFLAG_ASYNC at GetLocation and yet I did not implement IRunnableTask (see MSDN for IExtractImage::GetLocation). Returned NOERROR instead, and now runs just fine.

Hope this helps [wink]
 
Hello phinoppix

Thanks for the reply. I had actually got this to work during the weekend, but in a different way:
Code:
        public void GetLocation(int pszPathBuffer, int cch, ref int pdwPriority, ref SIZE prgSize, int dwRecClrDepth, ref ThumbnailFlags pdwFlags)
        {
            char[] Path;
            szSize.Width = prgSize.cx;
            szSize.Height = prgSize.cy;
            iColorDepth = dwRecClrDepth;

            tfFlags = pdwFlags;
            Path = strPath.ToCharArray();
            pdwFlags = tfFlags | ThumbnailFlags.Cache | ThumbnailFlags.Async;
            pdwPriority = 0;

            if ((Path.Length < cch))
            {
                cch = Path.Length;
            }

            Marshal.Copy(Path, 0, new IntPtr(pszPathBuffer), cch);
        }

        public int Extract()
        {

            Bitmap output = GetThumbnailFromFile();

            return output.GetHbitmap().ToInt32();
        }

GetThumbnailFromFile() does just that, returns a thumbnail from a file and is irrelevant to this issue.
Now, this Works-For-Me(tm) BUT (and there is always a but...) IRunnableTask is all but ignored. I have tried to implement it according to MSDN IRunnableTask information, but to no avail. My interfaces are defined like so:

Code:
    [ComImportAttribute(), GuidAttribute("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
    public interface IExtractImage
    {
        void GetLocation(int pszPathBuffer, int cch, ref int pdwPriority, ref SIZE prgSize, int dwRecClrDepth, ref ThumbnailFlags pdwFlags);
        int Extract();
    }

    [ComImportAttribute(), GuidAttribute("953BB1EE-93B4-11d1-98A3-00C04FB687DA"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
    public interface IExtractImage2
    {
        void GetLocation(int pszPathBuffer, int cch, ref int pdwPriority, ref SIZE prgSize, int dwRecClrDepth, ref ThumbnailFlags pdwFlags);

        int Extract();

        long GetDateStamp();
    }

    [ComImportAttribute(), GuidAttribute("85788D00-6807-11D0-B810-00C04FD706EC"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
    interface IRunnableTask
    {
        int IsRunning();
        uint Kill(bool fUnused);
        uint Resume();
        uint Run();
        uint Suspend();
    }

Now, according to an MSDN article i can't seem to find again, you have to both set the Async flag AND return E_PENDING from GetLocation() to make the shell ask for the thumbnail using multithreads. The thing is, as soon as i change my interface to

Code:
    [ComImportAttribute(), GuidAttribute("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
    public interface IExtractImage
    {
        long GetLocation(int pszPathBuffer, int cch, ref int pdwPriority, ref SIZE prgSize, int dwRecClrDepth, ref ThumbnailFlags pdwFlags);
        int Extract();
    }

and make GetLocation(...) return E_PENDING (the value of wich can be found using google), explorer just crashes as soon as GetLocation(...) returns. I worked around this by creating a background thread in GetLocation and setting it to do the extraction work and then Thread.Join() it in Extract(), but its not very effective - in fact i'm not even sure its working as i intend it.

So, to sum it up - the first code i posted Works-For-Me(tm), but is not using Async. With the changes to use Async, it crashes explorer as soon as GetLocation(...) returns. It seems to me it's an inproperly implemented interface (IExtractImage) has the wrong signature (its not a long and it should be something else) but i have made some attempts and couldnt get it to work. Also, this process, using IExtractImage with IRunnableTask is extremely poorly documented in MSDN or elsewhere.

This code works in Vista and XP, and i believe Vista automagically uses multithreads ignoring Async flags and return values anyway.

Thanks,
Joao Correia
 
Hi jcorreia123,

I haven't tried implementing IRunnableTask since the shell extension I made is just for POC, and I have to work on another project.

Some comments about your implementation, I'm not sure if you just had a typo, but there are some errors on your interfaces. You're missing the parameter for Extract() and return value for GetLocation.
Code:
	[ComImport]
	[Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")]
	[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	public interface IExtractImage
	{
		[PreserveSig]
		unsafe long GetLocation(
            [Out]
            IntPtr pszPathBuffer,
			int cch,
			ref int pdwPriority,
			ref SIZE prgSize,
			int dwRecClrDepth,
			ref int pdwFlags);

		[PreserveSig]
		unsafe int Extract([Out]IntPtr phBmpThumbnail);
	}


	[ComImport]
	[Guid("953BB1EE-93B4-11d1-98A3-00C04FB687DA")]
	[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	public interface IExtractImage2 : IExtractImage
	{
		int GetDateStamp([In, Out]ref System.Runtime.InteropServices.ComTypes.FILETIME pDateStamp);
	}
I'm using unsafe here for some particular reasons, but you can just ignore them since you're not using unsafe code. Still, since you mentioned Works-For-Me(tm), I guess .NET interop does some magic on this.

Code:
        public int Extract()
        {

            Bitmap output = GetThumbnailFromFile();

            return output.GetHbitmap().ToInt32();
        }
On your Extract() code, I noticed you are returning the bmp handle, instead of passing it via the buffer. If you look at MSDN, Extract() returns a status code (int) if the Extraction failed or not (NOERROR). It also has a parameter, which is a buffer for the bmp handle, or more specifically, a pointer to the bmp handle.

Also, from MSDN,
Bitmap.GetHbitmap -> Creates a GDI bitmap object from this Bitmap.
As far as i understand, this means GetHbitmap() is as good as creating another bitmap object, ending up with 2 bmps: one, internally created from the Bitmap constructor ( using GdipCreateBitmapFromScan0() ), and one as a return value. It's pretty interesting that GetHbitmap() actually calls the GDI+ function GdipCreateHBITMAPFromBitmap() behind the scenes.
So, why not create a GDI bitmap object beforehand using Win32 CreateBitmap()? After all, the Graphics class has an overload that takes in a GDI bmp handle. :)

I'll see if I can implement the asynchronous version this weekend, hopefully I can coz like you said, the documentation is really scarce. If you finish it first, I hope you won't mind posting your solution .

[wink]
 
Hello again

At first i thought that too (and it might just be one of those mistakes that work). I looked through some VB samples for this and they had IExtractImage implemented like that - no parameter on Extract and no return code in GetLocation - and it worked. I know the specs say otherwise, but it *does* Work-For-Me as pasted.

Connected to this is the return of an int in Extract. Instead of using the out parameter (like the specs say) i used what was in those samples (see vb shell extensions)

I took the easy way and just mapped things from VB to C# straight out.

I was using the GetHBitmap as a means to get to the .ToInt32() which gave me the IntPtr of the bitmap i needed. I will try to use your suggestion, as it does look better.


Again, thanks for the suggestions and i will post here if i finally get it working.
Joao
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top