knut
knut

Reputation: 4745

Not able to instantiate PDF browser control from AcroPDF.dll using COM and .NET interop

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!

Upvotes: 1

Views: 12887

Answers (2)

knut
knut

Reputation: 4745

These are the steps to use the Adobe PDF Reader control:

  1. Create a new Windows Forms Application: File → New Project… → Windows Forms Application → OK
  2. Add a reference to the Adobe Acrobat 7.0 Browser Control Type Library 1.0: Project → Add Reference… → COM → Adobe Acrobat 7.0 Browser Control Type Library 1.0 → OK
  3. Add the Adobe PDF Reader control to the Toolbox: Tools → Choose ToolBox Items… → COM Components → Adobe PDF Reader → OK
  4. Drag an Adobe PDF Reader control from your toolbox into the Form

I'm not sure why but I have to run Microsoft Visual C# 2008 Express Edition with administrative privileges to get this working. With a limited user I get this message in the designer:

Error HRESULT E_FAIL has been returned from a call to a COM component.

Note that after adding the Adobe PDF Reader control to your toolbox a new .NET interop assembly has been created with the name AxInterop.AcroPDFLib.dll. A reference to this new assembly has been added to your project references.

API reference documentation for the Adobe PDF Reader control is located here: http://icio.us/ajukkr

This forum thread provides some more useful information: http://forums.adobe.com/thread/438362

Upvotes: 3

Simon P Stevens
Simon P Stevens

Reputation: 27499

.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.]

Upvotes: 4

Related Questions