这个篇章结束,我们能自己写一个*.dle插件,这个插件的最后结果很简单,弹出一个对话框,如下图:
从图中的对话框内容可以看出,我们这个对话框是在DoExport()中被Max引擎给调用到了。文章中,我们会从一个空的工程开始一点一点加入代码,直到最后上面的结果,整个过程能让我们实践一个插件的开发过程,当然,只是万里长征的第一步。
整个工程最后的文件清单如下:
1,dllmain.h, dllmain.cpp
2,exporterdesc.h, exporterdesc.cpp
3,resource.h
4,OgreExporter.h, OgreExporter.cpp
5,exporter.def
4里面用到Ogre开头的名称,是因为我们后续可以导出成Ogre识别的XML格式,这个格式可以在Ogre DTD文档中看到,对大家都比较方便。最后我们也可以用Ogre知道我们导出的有没有问题。
现在就让我们开始:
准备、用VC新建win32 project,创建一个DLL, 记住勾选empty project,我们会在写插件的过程中不停的手工加入需要的文件。
1、在新项目中添加new item,类型选择.cpp,加入第一个文件,dllmain.h,内容如下:
#ifndef DLLMAIN_H #define DLLMAIN_H #include <windows.h> #include "resource.h" #include "max.h" extern HINSTANCE hInstance; extern TCHAR *GetString(int id); #endif
2、新加入dllmain.cpp,相关的代码如下,建议这个文件可以作为以后的模板文件,Copy-Paste代码的方式在这里完全是有意义的。
#include "dllmain.h" #include "exporterdesc.h" HINSTANCE hInstance; static ExporterDesc gExporterDescInst; BOOL WINAPI DllMain (HINSTANCE hInst, ULONG ulReason, LPVOID lpvReserved) { hInstance = hInst; switch(ulReason) { case DLL_PROCESS_ATTACH: if (FAILED(CoInitialize(NULL))) return FALSE; break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; } return(TRUE); } //------------------------------------------------ __declspec( dllexport ) const TCHAR *LibDescription() { return _T("ogre exporter"); } __declspec( dllexport ) int LibNumberClasses() { return 1; } __declspec( dllexport ) ClassDesc *LibClassDesc(int i) { switch(i) { case 0: return &gExporterDescInst; break; default: return 0; break; } } __declspec( dllexport ) ULONG LibVersion() { return VERSION_3DSMAX; } __declspec( dllexport ) ULONG CanAutoDefer() { return 1; } //------------------------------------------------ TCHAR *GetString(int id) { static TCHAR buf[256]; if (hInstance) return LoadString(hInstance, id, buf, sizeof(buf)) ? buf : NULL; return NULL; }
需要特别注意的是,LibClassDesc()这个DLL入口实现,可以返回一个ClassDesc的指针,这里通常我们会在插件编写中让它返回一个全局的静态变量。
3、新加入文件,这次是.DEF文件。
LIBRARY "exporter" EXPORTS LibDescription @1 LibNumberClasses @2 LibClassDesc @3 LibVersion @4 SECTIONS .data READ WRITE
这个文件定义了整个插件能导出的方法名称。
4、dllmain中引出了一个exporterdesc.h,所以我们新加入文件,名称即exporterdesc.h,文件中内容如下:
#include "iparamb2.h" class ExporterDesc : public ClassDesc2 { public: ExporterDesc(); virtual ~ExporterDesc(); int IsPublic(); void * Create(BOOL loading = FALSE); const TCHAR * ClassName(); SClass_ID SuperClassID(); Class_ID ClassID(); const TCHAR* Category(); };
这个类必须继承自ClassDesc2(或者ClassDesc,ClassDesc2是前者的继承类),并实现上述的接口。在Max启动的过程中,扫描到我们的插件的时候,DllMain()是重要的入口,因为是动态链接库强制的。而这里的Create(),则是Max引擎强制的,会被Max引擎用来创建插件主体的一个实例。
5、实现上面类的是exporterdesc.cpp中,我们要实现这些方法,如下:
#include "dllmain.h" #include "exporterdesc.h" #include "OgreExporter.h" ExporterDesc::ExporterDesc() { } ExporterDesc::~ExporterDesc() { } int ExporterDesc::IsPublic() { return true; } const TCHAR* ExporterDesc::Category() { return GetString(IDS_STRCATEGORY); } Class_ID ExporterDesc::ClassID() { return Class_ID(0x73bd1636, 0x22f05f49); } const TCHAR* ExporterDesc::ClassName() { return GetString(IDS_STRCLASSNAME); } void* ExporterDesc::Create(BOOL loading) { OgreExporter* pExporter = new OgreExporter(hInstance); return pExporter; } SClass_ID ExporterDesc::SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
我们的Create()方法创建了一个OgreExporter实例。所以,我们需要来加入OgreMaxExport这个类。这个类也是导出插件的真正主体。前面一切的一切都是因为Max引擎或者说是SDK需要我们那么做,为了让Max引擎能跟我们的插件做配合才做的工作。而这个被创建的OgreExporter才是我们真正的主角。
6、新加入OgreExporter.h文件,内容如下:
#ifndef OGREEXPORTER_H #define OGREEXPORTER_H #include <windows.h> #include "resource.h" #include "max.h" #include "impexp.h" #include "dllmain.h" class OgreExporter : public SceneExport { public: OgreExporter(HINSTANCE hInst) : m_hInstance(hInst) { } virtual ~OgreExporter (); public: virtual int ExtCount(); virtual const MCHAR* Ext(int n); virtual const MCHAR* LongDesc(); virtual const MCHAR* ShortDesc(); virtual const MCHAR* AuthorName(); virtual const MCHAR* CopyrightMessage(); virtual const MCHAR* OtherMessage1(); virtual const MCHAR* OtherMessage2(); virtual unsigned int Version(); virtual void ShowAbout(HWND hWnd); virtual int DoExport(const MCHAR* name,ExpInterface* ei,Interface* i,BOOL suppressPrompts=FALSE, DWORD options=0); private: HINSTANCE m_hInstance; }; #endif
对于导出插件来讲(3DS Max中,导出插件是其中一个类型),我们需要继承SceneExport,
并实现基类的几个函数,列表如下:
virtual int ExtCount();在SDK中,可以看到这些方法SDK有标准[Pure Virtual],也就是“纯虚函数”,所以,有这个标注的方法就必须自己来实现实体。
7,实现的文件是新加入的文件,OgreExpoter.cpp。
#include "OgreExporter.h" OgreExporter::~OgreExporter() { } int OgreExporter::DoExport(const MCHAR *name, ExpInterface *ei, Interface *i, BOOL suppressPrompts, DWORD options) { MessageBox (NULL, "DoExport called by Max engine", "Max plug-in", MB_OK); return 1; } const MCHAR* OgreExporter::AuthorName() { return GetString(IDS_STRAUTHORNAME); } const MCHAR* OgreExporter::CopyrightMessage() { return GetString(IDS_STRCOPYRIGHT); } const MCHAR* OgreExporter::Ext(int n) { switch (n) { case 0: return _T("xml"); break; default: return _T(""); } } int OgreExporter::ExtCount() { return 1; } const MCHAR* OgreExporter::LongDesc() { return GetString(IDS_STRLONGDESC); } const MCHAR* OgreExporter::OtherMessage1() { return GetString(IDS_STROTHER1); } const MCHAR* OgreExporter::OtherMessage2() { return GetString(IDS_STROTHER2); } const MCHAR* OgreExporter::ShortDesc() { return GetString(IDS_STRSHORTDESC); } void OgreExporter::ShowAbout(HWND hWnd) { } unsigned int OgreExporter::Version() { return 100; }
如我在前面的文章中(http://blog.csdn.net/tinyhum/article/details/6918540)提过的,上述的Ext方法会被Max引擎迭代用来找到该插件支持的后缀,DoExport则是真正的主体,在用户选好导出的文件名字并按下保存后就会被Max引擎呼叫到,想做什么事情,主要在这里做文章。
弹出的对话框我们是直接调用Win32的MessageBox()来进行的,你也可以在VC中自己制作好Dialog模板来进行调用。我们的GetString()利用了VC的resource view来进行字符串调用。
上述7个文件,还不能编译成功整个工程,如果要编译过,你必须在工程中加入resource,并把GetString()用到的那些ID全部实现。
编译的过程中,项目的属性页上,头文件包含目录必须有max sdk的include路径;链接目录上,必须有max sdk的lib路径;输入的库文件中,需要有comctl32.lib core.lib geom.lib gfx.lib helpsys.lib maxutil.lib mesh.lib paramblk2.lib这些库,特别是最后一个库是我们目前一定需要的(ClassDesc2存在的库)。