Discussion:
Here's how to use MFC in a Acrobat 8 plugin - Part 1
(too old to reply)
J***@adobeforums.com
2007-01-05 08:06:26 UTC
Permalink
Since there is apparently no way to determine the currently selected pages in the Pages Navigation panel (see Leonard Rosenthol, "Determining currently selected page(s)?" #1, 3 Jan 2007 3:59 am </cgi-bin/webx?14@@.3bc2ba82/0>) I had to create my own Page Ranges dialog box. There also doesn't seem to be any explicit documentation on how to use MFC in a Acrobat 8 plug-in, so I'll explain what I did to implement this dialog using Visual Studio 2005.

The easiest way to begin is to use the Acrobat 8 Plug-in Wizard mentioned on page 98 of the Guide to SDK Samples. You should read Developing Plug-ins and Applications (in particular Chapter 3, "Creating Plug-in and PDF Library Applications") but for some mysterious reason, that guide doesn't mention the wizard???

The reason you should use the Wizard is that it has a checkbox for "Plug-in uses MFC"!

Unfortunately I had already started my plug-in by following the instructions in the Developing Plug-ins doc. That recommends that you start off by copying the BasicPlugin sample which doesn't include any MFC support. So I used the Wizard to generate sample code with MFC support enabled and disabled. I then diffed the files to see what was required and retrofitted my Visual Studio solution to match.

It turns out there is really very little difference between a MFC-enabled plug-in and a normal plug-in.

The wizard will create stdafx.cpp and stdafx.h which you normally use for precompiling headers, but it doesn't bother to actually turn on their use.

In any case you need these two files because anywhere you include the Acrobat plugin header file, you need to include stdafx.h before it:



#include "stdafx.h"
#ifndef MAC_PLATFORM
#include "PIHeaders.h"
#endif

If you don't the compiler will complain that you can't include windows.h in a MFC dll (and PIHeaders.h includes windows.h if it hasn't been already).

Then, in your project properties, "Configuration Properties | General | Use of MFC"
should be set to "Use MFC in a Shared DLL" rather than the BasicPlugin's "Use Standard Windows Libraries".

In the "C++ | Preprocessor" section add _AFXDLL to the list of Preprocessor definitions.

That's basically it as far as project setup goes and like I said, if you start your plug-in by using the Acrobat 8 Plug-in Wizard it'll do all this for you. Unfortunately the wizard only creates a Debug configuration and not both a Debug and Release configuration which is standard on Visual Studio built projects. (I built a standard MFC dll and took a look at its Release configuration settings to figure out how to correctly generate a Release version of my plug-in).

It should now be a snap to get an MFC dialog to display from an Acrobat 8 plug-in right? That's what I thought until I actually tried it.

(continued in part 2)
J***@adobeforums.com
2007-01-05 08:12:17 UTC
Permalink
Here's how to use MFC in a Acrobat 8 plugin - Part 2

I created a dialog box as usual using the Dialog Editor and generated a c++ class from it. (For some reason the .h file for the Visual Studio generated dialog class didn't include resource.h so I had to add that manually??).

Ignoring what Developing Plug-ins and Applications says on page 43 in the section "Using modal dialog boxes". I then just tried doing:



PageRangeDlg pd;
INT_PTR nRet = pd.DoModal();

which works fine in a normal MFC app.

When I tried this in an Acrobat 8 plug-in however I got an assertion failure. Debugging shows me stopped at:



_AFXWIN_INLINE HINSTANCE AFXAPI AfxGetResourceHandle()
{ ASSERT(afxCurrentResourceHandle != NULL);
return afxCurrentResourceHandle; }

It's something about not being able to load resources. Time to google! Luckily agaceff posted a message to the planetpdf.com forum about a similar problem and mentioned AFX_MANAGE_STATE. I dug into the MFC docs to figure out what that's all about and here's what I found in the MFC "Exported DLL Function Entry Points" section:

If you have an exported function, such as one that launches a dialog box
in your DLL, you need to add the following code to the beginning of the
function:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

This swaps the current module state with the state returned from AfxGetStaticModuleState
until the end of the current scope.

Problems with resources in DLLs will occur if the AFX_MANAGE_STATE macro
is not used. By default, MFC uses the resource handle of the main application
to load the resource template. This template is actually stored in the
DLL. The root cause is that MFC's module state information has not been
switched by the AFX_MANAGE_STATE macro. The resource handle is recovered
from MFC's module state. Not switching the module state causes the wrong
resource handle to be used.

AFX_MANAGE_STATE does not need to be put into every function in the DLL.
For example, InitInstance can be called by the MFC code in the application
without AFX_MANAGE_STATE because MFC automatically shifts the module state
before InitInstance and then switches it back after InitInstance returns.
The same is true for all message-map handlers. Regular DLLs actually have
a special master window procedure that automatically switches the module
state before routing any message.

I don't claim to completely understand what that's saying but doing the following made my dialog box come up fine:



AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
PageRangeDlg pd;
INT_PTR nRet = pd.DoModal();

(While composing this post I tried to duplicate the error by commenting out the AFX_MANAGE_STATE line and my dialog box still came up fine! But testing on a plugin newly created by the Acrobat 8 Plug-in Wizard showed the assertion error so I still think AFX_MANAGE_STATE is necessary??)

(continued in part 3)
J***@adobeforums.com
2007-01-05 08:13:01 UTC
Permalink
Here's how to use MFC in a Acrobat 8 plugin - Part 3

Just to be on the safe side I followed the instructions on page 43 of Developing Plug-Ins and Applications and my current code looks like this:



AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
...
HWND capturehWnd, hParent;
capturehWnd = GetCapture();
if (capturehWnd != NULL)
ReleaseCapture();
hParent = WinAppGetModalParent(AVAppGetActiveDoc());

CWnd cWnd;
cWnd.Attach(hParent);
PageRangeDlg pd(currentPage, currentPage, nPages, &cWnd);
cWnd.Detach(); // since bad things might happen to hParent when cWnd
disappears?

INT_PTR nRet = pd.DoModal();
if (capturehWnd != NULL)
SetCapture(capturehWnd);

However if I try to follow the instructions on page 44 and do the following in PageRangeDlg::OnInitDialog() I get an assertion exception when the dialog box is closed???



AVWindow avWindow;
avWindow = AVWindowNewFromPlatformThing(AVWLmodal, 0, NULL, gExtensionID,
m_hWnd);
AVAppBeginModal(avWindow);

For now I'm just ignoring those instructions and my dialog box seems to work fine. Perhaps I'll start a separate topic to find out about that issue.

Now that the Acrobat 8 SDK is free, other newbies like me will be trying it out. I hope this long post helps start a discussion on how to use MFC to show dialogs from Acrobat 8 plug-ins :)
L***@adobeforums.com
2007-01-05 10:06:40 UTC
Permalink
Thanks for the comprehensive write up!!

I've forwarded it to our SDK folks.

Leonard
J***@adobeforums.com
2007-01-05 17:06:59 UTC
Permalink
Ooops. I forgot to mention three other files the Acrobat 8 Plug-in Wizard makes for MFC plugins:



plugin.clw
pluginapp.cpp
pluginapp.h

The first .clw file is (as near as I can tell) obsolete in Visual Studio 2005. It is just a settings text file used for older versions of the MFC ClassWizard (at least I don't have any .clw files in my plugin directory and I used the ClassWizard to create a class for my PageRangeDlg dialog box.)

The other two files define the CMFCApp class. I'm a bit surprised my plug-in seemed to worked fine without these! In "Regular DLLs Dynamically Linked to MFC" the Visual C++ doc states:

A regular DLL, dynamically linked to MFC has the following requirements:

* These DLLs are compiled with _AFXDLL defined, just like an executable
that is dynamically linked to the MFC DLL. But _USRDLL is also defined,
just like a regular DLL that is statically linked to MFC.
* This type of DLL must instantiate a CWinApp-derived class.
* This type of DLL uses the DllMain provided by MFC. Place all DLL-specific
initialization code in the InitInstance member function and termination
code in ExitInstance as in a normal MFC application.



Because this kind of DLL uses the dynamic-link library version of MFC,
you must explicitly set the current module state to the one for the DLL.
To do this, use the AFX_MANAGE_STATE macro at the beginning of every function
exported from the DLL.

Regular DLLs must have a CWinApp-derived class and a single object of
that application class, as does an MFC application. However, the CWinApp
object of the DLL does not have a main message pump, as does the CWinApp
object of an application.

Note that the CWinApp::Run mechanism does not apply to a DLL, because
the application owns the main message pump. If your DLL brings up modeless
dialogs or has a main frame window of its own, your application's main
message pump must call a DLL-exported routine that calls CWinApp::PreTranslateMessage.

I looked at my plugin project's settings again after reading that and noticed that I didn't have _USRDLL set (the Wizard generated project doesn't set it either?). I now define _USRDLL in both my Debug and Release configurations. Unlike what I claimed in Part 1, it seems you don't have to define _AFXDLL once you say "Use MFC in a Shared DLL" (_AFXDLL then becomes one of the "inherited values" along with _WINDLL).

I managed without a CWinApp-derived class in my plug-in for a day and a half and things worked okay but I feel better knowing pluginapp.cpp & pluginapp.h are in there now.

So far I've only created a modal dialog box. Looks like creating a modeless one in a plug-in might have some issues.

Some of this information is really only for people who have to add MFC support to an existing plug-in. If you use the Plug-in Wizard to create your initial project you're probably okay.
J***@adobeforums.com
2007-01-05 17:09:46 UTC
Permalink
BTW, I guess I should also mention that the project that the Acrobat 8 Plug-in Wizard makes isn't particularly ideal. For one thing it doesn't consistently use the name of your new project when generating output files. This is particularly annoying since the actual plug-in name will always be template.api!

To fix this using Visual Studio 2005: After you generate your new plugin with the Wizard, select the project in the Solution Explorer pane, right click and choose Unload Project. The project will then become "(unavailable)". Right click it again and choose "Edit YourProjectName.vcproj". This lets you directly edit the .vcproj file which is just a text file with XML in it.

Change all occurrences of PITemplate & Template to YourProjectName. Save and close the .vcproject window. Right click on your project again and choose Reload Project.

(Or you could just close Visual Studio 2005 and edit YourProjectName.vcprog with any text editor)

The other problem is the previously discussed omission of a Release Configuration in the project settings. Maybe the Wizard writers can fix this in the next release of the SDK?
J***@adobeforums.com
2007-01-05 19:30:38 UTC
Permalink
I think I figured out the cause of the assertion that happens when I follow the instructions on page 44 of Developing Plug-Ins and Applications in the "Using modal dialog boxes" section. The assertion is when I close my modal dialog box and is in CWnd::DestroyWindow():



// Should have been detached by OnNcDestroy
ASSERT(pMap->LookupPermanent(hWndOrig) == NULL);

It looked to me like something wasn't being released correctly. So after putting breakpoints in CWnd::DestroyWindow() and stepping thru what happened a few times I decided to ignore just part of the instructions on page 44 :)

Here's what my dialog class now does:



BOOL PageRangeDlg::OnInitDialog()
{
CDialog::OnInitDialog();

avWindow_ = AVWindowNewFromPlatformThing(AVWLmodal, 0, NULL, gExtensionID,
m_hWnd);
AVAppBeginModal(avWindow_);
...
return TRUE; // return TRUE unless you set the focus to a control
}

void PageRangeDlg::OnDestroy()
{
CDialog::OnDestroy();

// TODO: Add your message handler code here
AVAppEndModal();
AVWindowDestroy(avWindow_);
}

where I added AVWindow avWindow_ as a private member variable to my dialog class. Page 44 explicitly states:

If you are using MFC to put up your dialog box, do not call AVWindowDestroy
in the WM_DESTROY message (MFC will cause Acrobat or Adobe Reader to destroy
the AVWindow automatically).

But from my experience it seems that you still have to call AVWindowDestroy() even if you are using MFC?
Loading...