views:

715

answers:

2

When I try to instantiate a PDF browser control like this in C#:

AcroPDFLib.AcroPDFClass acrobat = new AcroPDFLib.AcroPDFClass();

I get a COMException with this message:

Creating an instance of the COM component with CLSID {CA8A9780-280D-11CF-A24D-444553540000} from the IClassFactory failed due to the following error: 80004005.

I have made a reference to AcroPDF.dll which has the component name Adobe Acrobat 7.0 Browser Control Type Library 1.0.

When I run Visual C# 2008 Express Edition as administrator I get another error message:

Unable to cast COM object of type 'AcroPDFLib.AcroPDFClass' to interface type 'AcroPDFLib.IAcroAXDocShim'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{3B813CE7-7C10-4F84-AD06-9DF76D97A9AA}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

This happens at the next line when I try to use the object:

acrobat.LoadFile("book.pdf");

I can't figure out what is wrong. Help most appreciated!

+1  A: 

.net COM interop doesn't route all COM messages directly back to the caller. If you called COM from an STA, it won't understand how you app can handle re-entrance. This means that failure messages that could just be handled with a retry, end up causing exceptions.

Try implementing IMessageFilter interface. This will allow COM to understand how to pass messages back to your app. In particular, implement the RetryRejectedCall and check if the failure flags and possibly return a timeout value (something like 1000ms) to allow COM to retry after a brief pause.

It's a COM type, so this is the code you'll need to define the interface:

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000016-0000-0000-C000-000000000046")]
public interface IMessageFilter
{
    [PreserveSig]
    int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);

    [PreserveSig]
    int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);

    [PreserveSig]
    int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
}

And this is an example of how you would implement it:

public class MyMessageFilter : IMessageFilter
{
    int IMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller,int dwTickCount, IntPtr lpInterfaceInfo)
    {
        // 0 means that it's handled.
        return 0;
    }

    int IMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
    {
        // The return value is the delay (in ms) before retrying.
        return 1000;
    }

    int IMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
    {
        // 1 hear means that the message is still not processed and to just continue waiting.
        return 1;
    }
}

Once you've implemented a message filter you will need to register it using CoRegisterMessageFilter. This is a per-thread registration, so be aware of what thread you are calling it on. The PInvoke signiture is:

[DllImport("ole32.dll")]
static extern int CoRegisterMessageFilter(IMessageFilter lpMessageFilter, out IMessageFilter lplpMessageFilter);

Even if this doesn't work, at the very least, if you log all the messages in the filter you should hopefully get some more information about what is going wrong. Look at the values of the parameters being passed into the message filter. If you look them up they will relate to error/state codes.

[Be aware, the IMessageFilter I'm referring to here is different from the System.Windows.Forms.IMessageFilter, so make sure you don't accidentally use the winforms one.]

Simon P Stevens
Is the IMessageFilter a C++ interface? If so i need to mention that I'm not a C++ programmer, and that it may be over my head to implement this.
knut
@knut, yes, IMessageFilter is a COM interface. I've added all the code you'll need. Let me know if you get stuck.
Simon P Stevens
I think I've followed your steps. I've put a break point at every handler. No break point gets hit. I've pasted my code here: http://codepaste.net/b9ody9. Have I done anything wrong?
knut
@knut: That looks fine to me. Perhaps try Debug.WriteLine(...) rather than breakpoints. I've had problems with breakpoints in message filter classes before. I'm surprised nothing gets called at all, that would suggest the exception is occurring even before the call gets into COM. Hang on, I'm going to try this out. I'll post an update in a few minutes.
Simon P Stevens
@knut: Ok, I've tried this out. I downloaded acrobat reader 9.3 from [this website](http://get.adobe.com/uk/reader/). And I used your code exactly and it worked fine. The object was created with no exceptions. No messages were sent, so I removed the message filter and it still worked fine. Perhaps you should try re-installing acrobat?
Simon P Stevens
@knut: I've uploaded my working source and binaries for you to try out if you want - [AcroTest.zip](http://dl.dropbox.com/u/314831/AcroTest.zip)
Simon P Stevens
I've updated my Acrobat Reader to version 9.3. That did not help. Then I ran Visual C# 2008 Express edition as administrator. As admin I get a more helpful error message when I try to use the created object. I will update my question with this new information.
knut
@simon-p-stevens Thank you for your effort and great support! I have found a solution to the problem. Maybe you could comment on it. Again, thanks for helping!
knut
A: 
knut
I'm curious about this new interop assembly that gets created. I would be thankful if somebody could explain why this is necessary. Does the control have to be hosted inside some container? Is it possible to use the control in a scripting scenario? This JScript fails like the example in the original question: WScript.CreateObject("AcroPDFLib.AcroPDF")
knut