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

acrobatactivexccominterop

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!

Best Answer

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

Related Topic