[Closed] [SDK]Exporter Plug-in Questions– Cleanup?
I’m close to getting my ROFF exporter completed… I just need to create the writeBinary() and writeASCII() methods.
I've been exercising my exporter and all seems to go well... I've written my own custom progressbar dialog (following an example of another plugin's source code)-- rather than using the one built into the interface methods. My ProgressBar is a modeless dialog created with the CreateDialog() method. I've built in a Cancel button (user can also press Esc key)... and in my code if the user cancels export (gotta be quick because it happens so fast... blink and you'll miss it... hah!) I pop up a MessageBox asking if they are sure they want to abort the export process... this all works fine... returning IMPEXP_CANCEL in the "DoExport" method... but after some minutes later with Max just sitting idle, or if I try to reset the scene or exit. I get an Application Error and crash. My guess is there is some clean up I should be doing like deleting pointers, etc.
Again, being a beginner coder... I'm not sure what all I am responsible for cleaning up during an export abort-- looking at other examples in the samples folder I can't see that they really do anything.
Any guidance would be greatly appreciated.
Below are snippets from my code that handle the ProgressBar and aborting:
// The following statements are declared after my includes/defines in ROFFexport.cpp and before the class definition:
static INT_PTR CALLBACK AboutBoxDlgProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
static INT_PTR CALLBACK ROFFexportOptionsDlgProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
static INT_PTR CALLBACK ProgressDlgProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
static ROFFexportOptions g_ExportOptions;
static HWND hWndProgressDlg = NULL; // handle of the progress dialog
ProgressBar Dialog Processing:
static INT_PTR CALLBACK ProgressDlgProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch ( message )
{
case WM_INITDIALOG:
CenterWindow( hWnd, GetParent(hWnd) );
g_ExportOptions.bUserAbortExport = FALSE;
SendDlgItemMessage ( hWnd , IDC_PROGRESSBAR, PBM_SETRANGE, 0, MAKELPARAM(0, 100) );
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_ABORT_EXPORT:
g_ExportOptions.bUserAbortExport = TRUE;
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
...and in my DoExport function, inside the main For Loop that does the exporting for all of the nodes I have the following logic to catch the abort:
// If user presses cancel in progress bar or Esc Key abort export
if ( g_ExportOptions.bUserAbortExport )
{
int answer = MessageBox ( NULL, "Abort scene export?","Confirm Abort", MB_YESNO | MB_ICONQUESTION );
switch (answer)
{
case IDYES:
DestroyWindow(hWndProgressDlg);
hWndProgressDlg = NULL;
// Do parameter clean up???
return IMPEXP_CANCEL;
case IDNO:
g_ExportOptions.bUserAbortExport = FALSE;
break;
}
}
In an abort scenario (or just normal operations for that matter before returning TRUE at end of DoExport method) what am I responsible for cleaning up? Do I need to delete the ROFFexport class pointers that were instantiated???
the static object instances will be around for as long as the dll is loaded ie if not deferred loaded it will be as long as max is running. Otherwise a general rule of thumb on clean up if you use new operator then chances are you are responsible for clean up with delete, though there are some functions in the sdk that return pointers that require you to cleanup they are not that common, most return pointers to the data in memory which is not your responsibility to delete. You can use AutoPtr which is especially designed for this kind of situation (when you don’t know when or where clean up is needed).
As for your specific hang, again even if it is labouring the point the debugger is your friend and It will save you heaps of time and keep you sane.
Perhaps I need to add a WM_DESTROY case to ProgressDlgProc ???
case WM_DESTROY:
// Perform cleanup tasks???
PostQuitMessage(0);
break;
I don’t use “new” anywhere… but I do have the vector containers for position and rotation data and catalog of notes by frame… and the NoteKeyTab for the notekeys and INodeTab for the list of nodes to be exported… these parameters are all private members of class ROFFexport. Do I need to clear() and/or delete them?
Ah, the debugger… still endeavoring (will likely drive me insane figuring it out– hah!). Thanks.
Edit: Hmm… so this class instance:
static ROFFexportOptions g_ExportOptions
stays around as a global??? It has some pointers inside it to the scene nodes/properties during the export process which may be invalidated, yes? Perhaps I should delete this g_ExportOptions from globals (how?). Also class ROFFExport had a private member pointer “m_pExportOptions” that pointed to the address of g_ExportOptions… :shrug:
vector<Point3> myvec;
is not something you need to worry about, the internal vector code will clean up it’s allocation, and the myvec variable will vanish when the stack is rolled up on function exit.
again the Tabs are the same as vectors should clean up themselves. Again if you used the new operator in there creation you would be responsible for the clean up.
INodeTab nodes;
for(i=0;i < numNodes;i++)
{
INode* node = ip->GetINode(i);
node.Appent(1,&node);
}
some made up code example which requires no clean up from you Any attempt to delete the nodes in the tab would be catastrophic ! And the Tab will deal with it allocation for the array of pointers.
on the other hand
vector<Point3*> ppointers;
for(int i = 0; i < numPoints;++i)
ppointers[i] = new Point3(GetVert(i));
would need to be cleaned up with
for(int i = 0; i < numPoints;++i)
delete ppointers[i];
no other action is needed with the vector.
alternatively
Point3* ppointer[] = new Point3*[numPoints];
for(int i = 0; i < numPoints;++i)
ppointers[i] = new Point3(GetVert(i));
would be cleaned up something like
for(int i = 0; i < numPoints;++i)
delete ppointers[i] ;
delete [] ppointers;
the exact syntax may be incorrect as I’m making it up as i type but hopefuly it makes some sense
stays around as a global??? It has some pointers inside it to the scene nodes/properties during the export process which may be invalidated, yes? Perhaps I should delete this g_ExportOptions from globals (how?). Also class ROFFExport had a private member pointer “m_pExportOptions” that pointed to the address of g_ExportOptions…
yep it will remain for as long as the dll is loaded. They may well be invalid but as long as you reinitialize correctly when the export runs there’s no problem. And addressing the static data is not a problem as it not going anywhere, the clue is in the name ;). If it bothers you don’t use a static create a new instance in the export class construtor and a delete it in the destructor.
I’ll try it in the ROFFexport constructor/destructor. But I thought it had to be declared as static to use in the static dialog callbacks?
Edit: What about adding a case for WM_DESTROY ???
one of many ways you could handle it…
class myGameExport : public ....
{
public:
myExportOptions* options;
myGameExport()
{
options = GetOptionsFromFile(); // we assume this creates a new instance which we will need to delete
}
~myGameExport()
{
if(options)
delete options;
options = NULL;
}
.....
};
INT_PTR CALLBACK myGameExportOptionsDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND CBhwnd;
static myGameExport* exp = NULL;
switch(message)
{
case WM_INITDIALOG:
exp = reinterpret_cast<myGameExport*>(lParam);
myExportOptions* options = exp->options;
....
btw theres not a lot wrong with the static method, edit for the belt and braces approach ! ;)
i’ve no idea about the WM_DESTROY. I found quite a few exporters hang on cancel. And I would probably not use any “non standard” dialog progress display anyway.
another way could be
[left]class myGameExport : public ....
{
public:
myExportOptions options;
myGameExport() { GetOptionsFromFile(options); }
GetOptionsFromFile(myExportOptions& options);
.....
};
INT_PTR CALLBACK myGameExportOptionsDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND CBhwnd;
static myGameExport* exp = NULL;
switch(message)
{
case WM_INITDIALOG:
exp = reinterpret_cast<myGameExport*>(lParam);
myExportOptions* options = &exp->options;
//or...
myExportOptions& options = exp->options;
....[/left]
then max will clean up when it deletes the instance of the exporter class
if you’re a hardcore c++ style victim then the public members is a huge no no you’ll have to add public getters and setters so the call back can have access.
Thank you very much for the examples… for now I think I will leave it as I have it globally defined:
static ROFFexportOptions g_ExportOptions;
…and have a private member pointer in ROFFexport class ( ROFFexportOptions* m_pExportOptions; ) that points to &g_ExportOptions in its constructor like so:
ROFFexport::ROFFexport()
{
m_pBinFile = NULL; //Initialize pointer to binary output file
m_pTxtFile = NULL; //Initialize pointer to ascii output file
m_pExportOptions = &g_ExportOptions;
}
…seems to work good enough. I’d rather have the options be in this ROFFexportOptions class instance– rather than reading/writing config files.