3DS MAX SDK开发学习记录

3DS MAX SDK开发学习记录 - 程序逻辑

 目 录
  1.1 基本思想
  1.2 系统管理插件的接口部分
  1.3 用户启用插件生成物体时的接口部分

1.1、基本思想
  3DS MAX SDK是一组库及相应的头文件,其中包含了3DS MAX的大部分核心代码,利用库中的函数就可与系统内核通信。3DS
MAX的插件(Plugins)就是一种动态链接库,每个插件都包含有若干个类及对象和公用函数。有一组公用函数及几个类的对象是提供给系统挂接和管理插件用的,另外一些类则实现插件本身的功能(如造型或修改)。

  插件中的每种活动都是由相应如的类对象来完成的,如系统获取插件中定义的类的描述、插件对象创建时的鼠标交互响应、用户对插件对象参数的修改等都有相应的类,系统会调用指定的公用函数取得这些对象的地址,并在适当的时候激活它们。这就是3DS
MAX的回调机制。

1.2、系统管理插件的接口部分

  

  // 定义插件对象的32位类ID。
  #define SPHERE_C_CLASSID1 8239283498
  #define SPHERE_C_CLASSID2 8239283498

  // 插件描述类对象
  SphereClassDesc sphereDesc;
  ClassDesc* GetSphereDesc() { return
&sphereDesc;}

  // 系统管理所需方法
  // 这几个方法都是dllexport型的
  LibVersion() {return VERSION_3DSMAX;}
  LibNumberClasses() {return 1;}
  LibClassDesc() {return GetSphereDesc();}
  LibDescription() {return _T("Sphere Object. Call
1-800-plig-in");}

  // 插件描述类,成员为供系统管理用的方法
  class SphereClassDesc: public ClassDesc
  {
  public:
    int      IsPublic()   {return 1;} // true可由用户选取,
                         // false由其它插件调用不与用户打交道。
    void *     Create(BOOL loading=FALSE)  {return new
SphereObject;}
    const TCHAR * ClassName()  { return _T("Sphere_c");}
    SClass_ID   SuperClassID(){ return GEOMOBJECT_CLASS_ID;}
    Class_ID    ClassID()   { return
CLASS_ID(SPHERE_C_CLASSID1,SPHERE_C_CLASS_ID2); }
    const TCHAR*  Category()   { return _T("How To");}
  }

1.3、用户启用插件生成物体时的接口部分

  

  // 创建时期的用户鼠标交互接口
  // 由一个CreateMouseCallBack衍生类的对象定义
  class SphereObjCreateCallBack: public CreateMouseCallBack
  {
    IPoint2 sp0;
    SphereObject *ob;
    Point3 p0;
  public:
  // 开发者定义的创建时期的用户交互方法
    int proc(ViewExp *vpt, int msg, int point, int flags, IPoints2
m, Matrix3 &mat);
    void SetObj(SphereObject *obj) { ob=obj;}
  }

  // 创建时对用户鼠标交互活动的响应代码
  int SphereObjCreateCallBack::proc(ViewExp *vpt,
      int msg, int point, int flags, IPoints2 m, Matrix3
&mat)
  {
    float r;
    Point3 p1,center;
    if(msg==MOUSE_POINT || msg==MOUSE_MOVE)
    {
      switch (point)
      {
        case 0:... break;
        case 1:... break;
      }
    else if(msg==MOUSE_ABORT) { return CREATE_ABORT; }
    return TRUE;
  }

  // 定义创物体创建时的回调类对象。
  static SphereObjCreateCallBack sphereCreateCB;

  // 在插件所创建的物体的类中定义获取创建回调函数地址的方法。
  CreateMouseCallBack* SphereObject::GetCreateMouseCallBack()
  {
    sphereCreateCB.SetObj(this);
    return &sphereCreateCB;
  }

  // 创建时期对用户修改物体参数的响应
  // 在用户编辑实体参数(创建时或修改时)系统将调用BeginEditParams(),该方法负责为面添加并注册滚转页
  // 在编辑完成时系统将会调用EndEditParams()。

  //BeginEditParams用来在用户进入参数编辑框时
  void SphereObject::BeginEditParams(IObjParam*ip,ULONG
flags,Animatable *prev)
  {
    SimpleObject::BeginEditParams(ip,flags,prev);
    this->ip=ip;
    if(pmapCreate&&pmapParam)
     {
      pmapCreate->SetParamBlock(this);
      pmapTypeIn->SetParamBlock(this);
      pmapParam->SetParamBlock(pblock);
    }
    else
    {
      if (flags&GEGIN_EDIT_CREATE)
      {
        pmapCreate=CreateCPParamMap(
            descCreate,
            CREATEDESC_LENGTH,
            this,
            ip,
            hInstance,
            MAKEINTRESOURCE(IDD_SPHEREPARAM1),
            _T("Creation Method"),
            0 );
        pmapTypeIn=CreateCPParamMap(
            descTypeIn,
            TYPEINDESC_LENGTH,
            this,
            ip,
            hInstace,
            MAKEINTRESOURCE(IDD_SPHEREPARAM3),
            _T("Keyboard Entry"),
            APPENDROOL_CLOSED );
      }
      pmapParam=CreateCPParamMap(
         descParam,
         PARAMDESC_LENGTH,
         pblock,
         ip,
         hInstace,
         MAKEINTRESOURCE(IDD_SPHEREPARAM2),
         _T("arameters"),
         0 );
    }
    if(pmapTypeIn)
    {
      pmapTypeIn->SetUserDlgProc(new
SphereTypeInDlgProc(this));
    }
  }

  void SphereObject::EndEditParams(IObjParam *ip,ULONG
flags,Animatable *next)
  {
    SimpleObject::EndEditParams(ip,flags,next);
    this->ip=NULL;
    if(flags&END_EDIT_REMOVEUI)
    {
      if(pmapCreate) DestroyCPParamMap(pmapCreate);
      if(pmapTypeIn)DestroyCPParamMap(pmapTypeIn);
      DestroyCPParamMap(pmapParam);
      pmapParam=NULL;
      pmapTypeIn=NULL;
      pmapCreate=NULL;
    }
    pblock->GetValue(PB_SEGS,ip->GetTime(),dlgSegments,FOREVER);

    pblock->GetValue(PB_SMOOTH,ip->GetTime(),dlgSmooth,FOREVER);

  }

  class SphereTypeInDlgProc: public ParamMapUserDlgProc
  {
  public:
    SphereObject *so;
    SphereTypeInDlgProc(SphereObject *s){so=s;}
    BOOL DlgProc(TimeValue t,IParamMap*map,HWIND hWnd,UINT
msg,WPARAM wParam,LPARAM lParam);
    void DeleteThis(){delete this;}
  };

  BOOL SphereTypeInDlgProc:lgProc(TimeValue,IParamMap *map,HWIND
hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
  {
    switch(msg)
    {
      case WM_COMMAND:
        switch(LOWORD(wParam))
        {
          case IDC_TI_CREATE:
            {
              if(so->crtRadius==0.0) return
TRUE;
              if(so->TestAFlag(A_OBJ_CREATING))

              {
                so->pblock->SetValue(PB_RADIUS,0,so->crtRadius);

              }
              Matrix3 tm(l);
              tm.SetTrans(so->crtPos);
              so->ip->NonMouseCreate(tm);

              return TRUE;
            }
            break;
        }
        return FALUSE;
    }

  目 录  2.1 基本的介绍  2.2 插件必须的函数  2.3 类描述器方法成员   3DS MAX
SDK插件是以DLL形式存在的。通常我们用Microsoft Visual C++来开发。建立一个新的工程的描述见“Creating
A New Plugin Project”开发者可以将这些DLL插件存放在任何地方,但是要想法子让3DS
MAX知道到哪里去找这些文件。这部分是在“Plug-In Directory Search
Mechanism”里讨论的。插件开发者可以为应用加上在线帮助,并使用望可在Max Help菜单里访问。细节见“Plug-In
Help
System”。有一个标准的位置供开发者保存插件所需的任何配置文件。这些可能是.ini文件,二进制配置文件或任何需要的文件。详见“Plug-In
Configuration System”。2.1、基本的介绍  2.1.1
标准DLL函数  所有的插件DLL都必须实现一套标准的函数:  DLLMain()  LibDescription()  LibNumberClasses()  LibClassDesc()  LibVersion()  这些允许3DS
MAX访问、维护在DLL内在插件并与之协同工作。这些函数的详情见“DLL, LIbrary Functions, and Class
Descriptors”。  2.1.2 重入与线程安全的插件  3DS
MAX插件必须是可重入与线程安全的。详在高级主题的“Thread Safe Plug-Ins”章节里。2.2、插件必须的函数  3DS
MAX进行DLL装入、分类、管理插件。包括DLL例程和类描述类。“Class
Descriptors”提供插件类的信息,用以实现LibClassDesc()函数。  2.2.1 DLL
functions  DllMain(HINSTANCE hinstDLL, ULONG fdwReason, LPVOID
lpvReserved)
  当DLL被装入时由windows调用。该函数也会在时间关键性操作期间被多次调用,如渲染。所以开发者在该函数内要小心谨慎。注意以下的示意代码中,在DLL第一次调用以后只有很少的语句被执行。该函数应该返回TRUE。  int
controlsInit = FALSE;  BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG
fdwReason,LPVOID lpvReserved)  {    // Hang on to this DLL's
instance handle.    hInstance = hinstDLL;    if (!
controlsInit) {      controlsInit = TRUE;      // Initialize MAX's
custom controls      InitCustomControls(hInstance);      //
Initialize Win95
controls      InitCommonControls();    }    return(TRUE);  }  2.2.2
LibNumberClasses()  当3DS
MAX启动之后,它找到并装入这些DLLs。然后,它需要有个方法判断DLL中的插件类数目。开发者应当在本函数中提供,例如:  __declspec(dllexport)
int LibNumberClasses() { return 1; }    返回值即插件类的个数。  2.2.3
LibClassDesc(i)  插件必须向系统提供一个方法以获取插件定义的类的描述器(Class
Descriptors)。类描述器向系统提供DLL中的插件类的信息。本函数使系统可以访问类描述器,返回值应该是指向第i个类描述器的指针。(一个DLL中可以有许多个类描述器)。例如:  __declspec(dllexport)
ClassDesc *LibClassDesc(int i)  {     switch(i) {      case 0:
return &MeltCD;      case 1: return
&CrumpleCD;      default: return
0;    }  }  这里是有关必须被LibClassDesc(i)返回的类描述器的资料。类描述器(Class
Descriptors)向系统提供DLL中插件类的信息。类描述的一个方法负责分配插件类的新实例(Create)。开发者通过从ClassDesc衍生一个子类并实现若干方法成员来建立类描述器。下面是个简单的类描述器及其静态实例的例子。  class
MeltClassDesc : public ClassDesc  {  public:    int      IsPublic()
{ return TRUE; }    void *    Create(BOOL loading=FALSE) { return
new MeltMod(); }    const TCHAR * ClassName() { return _T("Melt");
}    SClass_ID   SuperClassID() { return OSM_CLASS_ID;
}    Class_ID   ClassID() { return Class_ID(0xA1C8E1D1,
0xE7AA2BE5); }    const TCHAR* Category() { return _T("");
}  };  static MeltClassDesc MeltCD;  2.2.4
LibDescription()  当包含入口(过程物体、修改器或控制器等)的MAX文件被装入且系统还没有访问它时(如DLL无效),一个消息被发给用户。当DLL不可用时,系统要求每个DLL返回一个字符串向用户说明情况。例如,假定用户有一个融化修改器,他将此融化修改器应用到场景中的某个节点上,并保存此文件。当他将这个文件给一个没有这个融化DLL的朋友,这个朋友打开这个文件时,系统将发出一条消息说明文件中的一个入口所依赖的DLL找不到,这个消息可能是“融化修改器。想要请打电话025-1234PLUG-INS”。  DLL必须实现LibDescription()才能向系统提供这个字符串。该函数返回值即为当找不到该DLL时要显示的文字。该字符串也将显示在Summary
Info/Plug-In
Info...对话框中。一旦DLL中的一个插件已经在场景中使用过,系统就会把这个字符串保存到max文件里(以便在DLL丢失时显示)。  注意:即使DLL缺席时场景仍然会被打开。3DS
MAX保留任何DLL丢失的节点(entities),这样如果文件被修改并保存然后再在有DLL的系统打开修改后的文件,这些节点仍然存在并链接进场景。不能访问其DLL的入口称为封闭入口(orphaned
entities)。  封闭入口将作为其超类(SupperClass)的通用代表导入。该代表将在场景中显示最少的信息。举个实例:如果入口是个修改器,它将在修改器清单中显示自己的名字,但不会显示任何参数。如果没有对象类型信息,那么将在场景中显示为虚物体(dummy)。它们可以被移动、旋转、缩放、链接、组合、删除……任何与节点相关的操作。丢失的控制器只提供不变的默认值,这些值是不可调的。  参考“Read
Only
Plug-Ins”部分。通过允许插件工作在只读模式,用户可以自由分发DLL,其他人除非通过了基于硬件锁ID的认证可以运行它,否则使用将受到限制直到购买自己的拷贝。以下是该函数实现的一个例子:  __declspec(
dllexport ) const TCHAR *LibDescription()  {    return _T("Melt
Modifier. Call 1-800-PLUG-INS to obtain a copy");  }  2.2.5
LibVersion()  开发者必须实现一个函数以便系统处理不同版本的3DS
MAX插件DLL。因为MAX体系与插件的关系如此之紧密,系统有时候需要阻止插件的老版本被调用。要使MAX能够完成它,DLL必须实现一个名为LibVersion()的函数。这个函数只简单的返回一个预定义的常量,这个常量表明在插件编译时系统的版本。未来版本的MAX可能更新该常量,而老的DLL总是返回以前的值。该函数使得系统可以检查任意一个DLL是否已经被装入,如果是这样则显示一条消息。  __declspec(
dllexport ) ULONG LibVersion() { return VERSION_3DSMAX;
}  注意:开发者可以用下面的全局函数获取该值。  DWORD
Get3DSMAXVersion();  返回正在运行的MAX版本被编译时“\MAXSDK\INCLUDE\PLUGAPI.H”文件中包含的VERSION_3DSMAX宏定义的状态。  总结  插件必须实现这五个函数:DLLMain()、LibNumberClasses()、LibClassDesc(i)、LibDescription()、LibVersion()。这些函数允许系统取得DLL中插件的信息。2.3、六个类描述器方法成员  下面我们来讲讲六个类描述器方法成员:IsPublic()、Create()、ClassName()、SuperClassID()、ClassID()及Category()。  2.3.1
IsPublic()  该方法返回一个布尔值。如果插件可以被用户选取和指派,正是常见的情况,返回TRUE。某些插件可能是同一DLL的实现的其它插件私有专用的,并不出现在清单供用户选择。这些插件将返回FALSE。  2.3.2
Create(BOOL loading =
FALSE)  MAX在需要得到一个指向插件类的新实例的时候调用该方法。例如,如果MAX从磁盘打开一个包含前边用过的插件的文件,它将调用插件的Create()方法。插件负责分配一个插件类的新实例。在上边的例子的是用一个“new”操作简单实现的。  Create()的可选参数是一个标识表明要创建的类是否将从一个磁盘文件中装入。如果该标识为TRUE,插件可以不必做任何初始化工作,因为装入进程将会处理它。见“Loading
and
Saving”章节。  当系统需要删除一个插件类的实例时,它会调用Animatable的DeleteThis()方法。插件开发者必须实现该方法。因为开发使用new操作分配内存,它也应该用delete操作释放之。如开发者可以如下实现DeleteThis():  void
DeleteThis() { delete this; }  进一步的细节参考“Memory
Allocation”章节。  2.3.3
ClassName()  该方法返回类的名字。这个名字将出现在MAX用户界面的插件按钮上。该方法也在调试时显示类的名字。  2.3.4
SuperClassID()  该方法返回系统预定义的常量,该常量表示插件类是从哪个类衍生来的。例如,弯曲修改器返回OSM_CLASS_ID。这个超类ID被所有对象空间修改器使用。其它的超类ID例子有:CAMERA_CLASS_ID、LIGHT_CLASS_ID、SHAPE_CLASS_ID、HELPER_CLASS_ID、SYSTEM_CLASS_ID。完整的清单见“List
of Super Class IDs”。  2.3.5 ClassID()  该方法必须为对象返回一个唯一性ID。3DS MAX
SDK中包含一个生成这种ClassID的程序。使用该程序为你的插件创建ClassID是非常重要的。如果如果你使用任何一个例子程序的源代码来建立自己的插件,必须改变已经存在Class_ID。如果不这样,将会出现冲突。如果两个ClassID冲突,系统将加载它找到的第一个(并将在试图加载第二个时显示存在Class_ID冲突)。  一个Class_ID包含两个无符号的32位整数。建构函数为每一个赋值,如Class_ID(0xA1C864D1,
0xE7AA2BE5)。参见“Class Class
ID”。  注意在MAX使用的插件样本代码将类ID第二个32位整数设为0,只有与MAX一起发售的内建插件才可以这样做。所有的插件开发者都应该同时使用两个32位整数。还有,确保你使用SDK提供程序建立类ID,这可确保两个插件类之间不会冲突。要生成一个随机的Class_ID并可选的将其拷贝至剪帖板中,单击DLL
Function and Class部分的“Generate a Class_ID”。  2.3.6
Category()  在建立面板底部下拉选单选择类别。如果设成已经存在的类别(i.e. "Standard Primitives",
"article Systems",
etc),插件将会出现在那个类别里。开发者不应该加到MAX提供的类别里(见下边的注释)。如果类别还不存在,则将被创建。如果插件不需要出现在清单中,它可以简单的返回一个null字符串_T("")。Category()也被按钮设置对话框中用于插件分类。  重要说明:MAX体系在Create分支面板里有每类12个插件的限制。为预防每个类别有太多插件的问题,开发者应该总是为自己的插件建立一个新的类别而不是使用一个MAX标准插件已经使用的类别。注意早于1.2版本的MAX在每个类别有多于12个按钮时会崩溃。
  管理过程物体创建过程并编辑其参数  这部分讨论以下方法:  GetCreateMouseCallBack()  proc()  BeginEditParams()  EndEditParams()  GetValue()  SetValue  NumSubs()  SubAnim()  SubAnimName()  当MAX用户将要建立一个新的过程物体时,系统会调用插件的一个方法接管创建阶段的用户输入活动。插件可以任意实现其用户界面,但必须向MAX提供一个途径与用户交互过程联系。插件要实现GetCreateMouseCallBack()函数以向MAX提供该途径。这个函数返回一个指向CreateMouseCallBack衍生类实例的指针。这个类有一个proc()方法,程序员在这个函数里定义物体创建阶段的用户交互活动。系统实际上需要一个函数指针,这个函数是由插件实现可以被系统调用。参考sphere_c.cpp源代码。  插件必须实现一些方法以处理用在命令面板中的输入。插件要负责实现从Animatable类继承来的两个方法,这两个方法用来处理用户在命令面板中的输入:BeginEditParams()和EndEditParams()。Begin在用户可以编辑实体参数时由系统调用(物体创建或修改已经存在的实体时都可能调用),它负责向面板里添加卷展栏并将其注册到系统中。加入卷展栏函数带有一个Dialog
Proc参数。这个对话处理过程控制用户与对话框控件的交互活动。过程球体的例子使用了参数映射机制来简化开发者与管理用户界面控制相关工作任务。参数映射被用于管理UI交互活动。见参数映射。  End方法则在用户结束编辑一个物体的参数时被调用。系统将传递一个标识给EndEditParams()以指示卷展栏是否应该删除。如果为TURE,插件必须注销卷展栏,并将其从面板中删掉。在某些情况,物体的卷展栏应该保留在命令面板里。例如如果用户已经完成一个过程球体的建立,它的EndEditParams()方法被调用。然而用户可能希望建立另一个球,这样开发者不可以立刻移走卷展栏。这样用户界面不会因为删除后立刻加回来而闪烁。  参数映射  这部分对于理解例子及简化开发相当有用,所以列入基本内容。参数映射用于最小化插件管理用户界面参数所需的编程工作。一个简单插件,如过程球体,拥有由类似微调控件(to
be continued...)。

一步步开发一个通用型的3ds max模型导出插件(1)

一步步开发一个通用型的3ds max模型导出插件(1)

     
3ds
max是游戏开发当中使用的主流建模工具。美术人员在max建好的模型要放置到游戏的场景中去,有两种不同的选择:一是选择自由开放的max导出插件来导出模型(如3ds、obj等文件格式);二是使用自主开发的3ds
max插件来导出模型。一般的开放的模型格式往往不能满足特定游戏对3D模型的全部需求,这样开发自已导出插件就变得迫切和必须。

     
本文的目标是介绍以开发出满足以下需求的max插件:

     
1.导出基本的网格信息,即:顶点信息(顶点位置、法线、纹理坐标),三角形信息

    
2.导出基本的材质信息,包括标准的材质(diffuse,specular,emissive,ambient),纹理贴图信息(diffuse
map\opacity map\bump map等),uv变换矩阵、渲染状态(如 two-side等)等

    
3.导出 骨骼数据(cs bone和skin bone)

    
3.导出target camera 和 dummy 信息

    
4.导出 cs蒙皮和skin蒙皮并导出cs和skin的骨骼动画

    
5.导出基于max 节点的关键帧transform动画(translate,rotation,scale)

    
6.导出基本的材质颜色动画、alpha动画和uv变换动画

    
7.导出mesh的morph动画

    
8.支持自定义优化器对导出数据进行优化、冗余数据检测剔除

    
9.支持自定义的目标文件格式输出器,支持输出成不同的文件格式

 

                                                     (一)3ds
max插件基础

    
开发3ds max 插件,首先你应该掌握基本的c++编程、msvc 和计算机图形学,其次你应该通读一遍3ds max sdk
help和3ds max script help ,再次你应该多少会用一点3ds
max,这样有便于你理解开发过程中会遇到的很多概念术语。

    
3ds max提供了功能强大的max sdk和max script
,使用这些API或script使你几乎能开发出所有的max组件,同时max从6.0以后特别为游戏开发提供了IGame接口用来导出模型数据,这使游戏中常用数据的导出变得简单。在这篇文章里我不会采用max
script或IGame API来导出模型数据,而是仍然使用传统的max sdk来开发导出插件。

    
下面是一些max中基本的概念:

   
 默认的,3ds max的使用的是X向右,Y轴向内,Z轴向上的右手坐标系
。这点和opengl或directx中所使用的坐标系都有所不同。

    
3ds
max使用场景图的方式来管理所有场景中的物体,Node是组成场景图的最小单位,一个场景中只存在一个根节点(RootNode),它由它的孩子节点和子孙节点组成了一颗树。每个叶子节点会有一个对象(Object)和一组材质(material可能会有多个subMaterial)

    
3ds
max的许多核心编辑功能都是由修改器(modifier)来完成的,这些modifer按先后顺序应用到一个Node的对象(Object)上,形成一个modifier
stack .

    max插件都是windows的dll文件,但在max下会根据其功能不同而要求生成不同的扩展名,我们做成的导出插件的扩展名是dle.

   
max要求每个插件都包含以下的导出函数:

    

    
DllMain是每个windows dll的入口函数.

BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved)

     

   
LibInitialize在这个DLL被加载的时候被调用,你可以在里面做一些必要的初始化工作。   

int LibInitialize(void);

 

   
LibShutdown是在这个DLL被卸载的时候调用的,你可以在这个函数里面做一些必要的清理工作。

 

int LibShutdown(void);

 

    
注意:LibInitialize和LibShutdown并不是必须实现的,你可以不实现这两个函数

 

 

   
LibDescription 返回一个描述此DLL的字符串,必须实现
。 

const TCHAR* LibDescription()

 

 

   
LibNumberClasses 返回这个DLL含有多少个插件

int LibNumberClasses()

    LibVersion 返回编译此插件所使用max
sdk版本

ULONG LibVersion()

 

 

   
LibClassDesc
根据插件索引返回每个 插件的Desc对象指针,插件的Desc类必须从 ClassDesc2 或
ClassDesc继承而来,这个Desc类描述了插件的基本信息

并负责创建插件的核心对象,这个会在后面介绍Desc类时进行详细介绍。

ClassDesc* LibClassDesc(int i)

 

    
这几个函数的更详细的描述可以参见 max sdk help的
Required DLL Functions一节。

    
本文所描述的插件DLL只含有一个插件,所以DLL入口函数和max插件基本函数的实现如下,GetPluginDesc函数将会在后面介绍Desc类时一起介绍.

 1
 2
 3HINSTANCE    g_Instance = NULL; 
 4bool         g_Initilization = false;
 5extern       ClassDesc* GetPluginDesc(void) ;
 6
 7BOOL APIENTRY DllMain( HINSTANCE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved){
 8    g_Instance = hModule;
 9
10    if(!g_Initilization){
11        InitCustomControls(g_Instance); 
12        InitCustomControls(NULL);  
13        g_Initilization = true ;
14    }
    
15    return TRUE;
16}

17
18__declspec(dllexport) const TCHAR* LibDescription(void){
19    return TEXT("Universal Exporter Plugin") ; 
20}

21
22__declspec(dllexport) int LibNumberClasses(void){
23    return 1;
24}

25
26__declspec(dllexport) ClassDesc* LibClassDesc(int i){    
27    switch(i)
28        case 0:
29            return GetPluginDesc();
30        default:
31            return NULL;
32    }

33}

34
35__declspec(dllexport) ULONG LibVersion(void){
36    return VERSION_3DSMAX ;
37}

忘了说一个基础知识,上述这些函数只能选择C式导出,C++编译器支持重名函数,并会在编译的时候生成后缀名,而这不是我们在插件里所希望看到的。所以以上的函数你可以选择用extern
"C" {}包含起来或者写一个单独的def文件来具名导出这些函数。在这儿我的选择是用def文件来导出这些函数。

 

LIBRARY    generic_exporter

;exported method
EXPORTS    

    LibDescription            @1

    LibNumberClasses        @2

    LibClassDesc            @3

    LibVersion                @4

;data defined
SECTIONS
    .data READ WRITE

一个老程序员的心得

  不知不觉做软件已经做了十年,有成功的喜悦,也有失败的痛苦,但总不敢称自己是高手,因为和我心目中真正的高手们比起来,还差的太远。世界上并没有成为高手的捷径,但一些基本原则是可以遵循的。
   1. 扎实的基础。数据结构、离散数学、编译原理,这些是所有计算机科学的基础,如果不掌握他们,很难写出高水平的程序。据我的观察,学计算机专业的人比学其他专业的人更能写出高质量的软件。程序人人都会写,但当你发现写到一定程度很难再提高的时候,就应该想想是不是要回过头来学学这些最基本的理论。(广州达内,中国高端IT培训专家。 www. tarena.com.cn/guangzhou不要一开始就去学OOP,即使你再精通OOP,遇到一些基本算法的时候可能也会束手无策。
   2. 丰富的想象力。不要拘泥于固定的思维方式,遇到问题的时候要多想几种解决问题的方案,试试别人从没想过的方法。丰富的想象力是建立在丰富的知识的基础上,除计算机以外,多涉猎其他的学科,比如天文、物理、数学等等。另外,多看科幻电影也是一个很好的途径。
   3. 最简单的是最好的。这也许是所有科学都遵循的一条准则,如此复杂的质能互换原理在爱因斯坦眼里不过是一个简单得不能再简单的公式:E=mc2。(IT培训咨询QQ:787031304;)简单的方法更容易被人理解,更容易实现,也更容易维护。遇到问题时要优先考虑最简单的方案,只有简单方案不能满足要求时再考虑复杂的方案。
   4. 不钻牛角尖。当你遇到障碍的时候,不妨暂时远离电脑,看看窗外的风景,听听轻音乐,和朋友聊聊天。当我遇到难题的时候会去玩游戏,而且是那种极暴力的打斗类游戏,当负责游戏的那部分大脑细胞极度亢奋的时候,负责编程的那部分大脑细胞就得到了充分的休息。当重新开始工作的时候,我会发现那些难题现在竟然可以迎刃而解。
   5. 对答案的渴求。人类自然科学的发展史就是一个渴求得到答案的过程,即使只能知道答案的一小部分也值得我们去付出。只要你坚定信念,一定要找到问题的答案,你才会付出精力去探索,即使最后没有得到答案,在过程中你也会学到很多东西。
   6. 多与别人交流。(达内科技IT培训,先就业,后付款!)三人行必有我师,也许在一次和别人不经意的谈话中,就可以迸出灵感的火花。多上上网,看看别人对同一问题的看法,会给你很大的启发。
   7. 良好的编程风格。注意养成良好的习惯,代码的缩进编排,变量的命名规则要始终保持一致。大家都知道如何排除代码中错误,却往往忽视了对注释的排错。注释是程序的一个重要组成部分,它可以使你的代码更容易理解,而如果代码已经清楚地表达了你的思想,就不必再加注释了,如果注释和代码不一致,那就更加糟糕。
   8. 韧性和毅力。这也许是"高手"和一般程序员最大的区别。A good programming is 99% sweat and 1% coffee。高手们并不是天才,他们是在无数个日日夜夜中磨练出来的。成功能给我们带来无比的喜悦,但过程却是无比的枯燥乏味。你不妨做个测试,找个10000以内的素数表,把它们全都抄下来,然后再检查三遍,如果能够不间断地完成这一工作,你就可以满足这一条。

前人经验总结

“又是一年毕业时”,看到一批批学子离开人生的象牙塔,走上各自的工作岗位;想想自己也曾经意气风发、踌躇满志,不觉感叹万千……本文是自己工作6年的经历沉淀或者经验提炼,希望对所有的软件工程师们有所帮助,早日实现自己的人生目标。本文主要是关于软件开发人员如何提高自己的软件专业技术方面的具体建议,前面几点旨在确定大的方向,算是废话吧。

谨以此文献给那个自己为你奉献3年青春与激情的开发团队。还有团队成员:PPL、YT、YK 、TYF、LGL、CHL、CDY、CB、DPD。

1、分享第一条经验:“学历代表过去、能力代表现在、学习力代表未来。”其实这是一个来自国外教育领域的一个研究结果。相信工作过几年、十几年的朋友对这个道理有些体会吧。但我相信这一点也很重要:“重要的道理明白太晚将抱憾终生!”所以放在每一条,让刚刚毕业的朋友们早点看到哈!

2、一定要确定自己的发展方向,并为此目的制定可行的计划。不要说什么,“我刚毕业,还不知道将来可能做什么?”,“跟着感觉走,先做做看”。因为,这样的观点会通过你的潜意识去暗示你的行为无所事事、碌碌无为。一直做技术,将来成为专家级人物?向管理方向走,成为职业经理人?先熟悉行业和领域,将来自立门户?还是先在行业里面混混,过几年转行做点别的?这很重要,它将决定你近几年、十年内“做什么事情才是在做正确的事情!”。

3、软件开发团队中,技术不是万能的,但没有技术是万万不能的!在技术型团队中,技术与人品同等重要,当然长相也比较重要哈,尤其在MM比较多的团队中。在软件项目团队中,技术水平是受人重视和尊重的重要砝码。无论你是做管理、系统分析、设计、编码,还是产品管理、测试、文档、实施、维护,多少你都要有技术基础。算我孤陋寡闻,我还真没有亲眼看到过一个外行带领一个软件开发团队成功地完成过软件开发项目,哪怕就一个,也没有看到。倒是曾经看到过一个“高学历的牛人”(非技术型)带一堆人做完过一个项目,项目交付的第二天,项目组成员扔下一句“再也受不了啦!”四分五裂、各奔东西。那个项目的“成功度”大家可想而知了。

4、详细制定自己软件开发专业知识学习计划,并注意及时修正和调整(软件开发技术变化实在太快)。请牢记:“如果一个软件开发人员在1、2年内都没有更新过自己的知识,那么,其实他已经不再属于这个行业了。”不要告诉自己没有时间。来自时间管理领域的著名的“三八原则”告诫我们:另外的那8小时如何使用将决定你的人生成败!本人自毕业以来,平均每天实际学习时间超过2小时。

5、书籍是人类进步的阶梯,对软件开发人员尤其如此。书籍是学习知识的最有效途径,不要过多地指望在工作中能遇到“世外高人”,并不厌其烦地教你。对于花钱买书,我个人经验是:千万别买国内那帮人出的书!我买的那些家伙出的书,!00%全部后悔了,无一本例外。更气愤的是,这些书在二手市场的地摊上都很难卖掉。“拥有书籍并不表示拥有知识;拥有知识并不表示拥有技能;拥有技能并不表示拥有文化;拥有文化并不表示拥有智慧。”只有将书本变成的自己智慧,才算是真正拥有了它。

6、不要仅局限于对某项技术的表面使用上,哪怕你只是偶尔用一、二次。“对任何事物不究就里”是任何行业的工程师所不应该具备的素质。开发Windows应用程序,看看Windows程序的设计、加载、执行原理,分析一下PE文件格式,试试用SDK开发从头开发一个Windows应用程序;用VC++、 Delphi、Java、.Net开发应用程序,花时间去研究一下MFC、VCL、J2EE、.Net它们框架设计或者源码;除了会用J2EE、 JBoss、Spring、Hibernate等等优秀的开源产品或者框架,抽空看看大师们是如何抽象、分析、设计和实现那些类似问题的通用解决方案的。试着这样做做,你以后的工作将会少遇到一些让你不明就里、一头雾水的问题,因为,很多东西你“知其然且知其所以然”!

7、在一种语言上编程,但别为其束缚了思想。“代码大全”中说:“深入一门语言编程,不要浮于表面”。深入一门语言开发还远远不足,任何编程语言的存在都有其自身的理由,所以也没有哪门语言是“包治百病”的“灵丹妙药”。编程语言对开发人员解决具体问题的思路和方式的影响与束缚的例子俯拾皆是。我的经验是:用面对对象工具开发某些关键模块时,为什么不可以借鉴C、C51、汇编的模块化封装方式?用传统的桌面开发工具(目前主要有VC++、Delphi)进行系统体统结构设计时,为什么不可以参考来自Java社区的IoC、AOP设计思想,甚至借鉴像Spring、Hibernate、JBoss等等优秀的开源框架?在进行类似于实时通信、数据采集等功能的设计、实现时,为什么不可以引用来自实时系统、嵌入式系统的优秀的体系框架与模式?为什么一切都必须以个人、团队在当然开发语言上的传统或者经验来解决问题???“他山之石、可以攻玉”。

8、养成总结与反思的习惯,并有意识地提炼日常工作成果,形成自己的个人源码库、解决某类问题的通用系统体系结构、甚至进化为框架。众所周知,对软件开发人员而言,有、无经验的一个显著区别是:无经验者完成任何任务时都从头开始,而有经验者往往通过重组自己的可复用模块、类库来解决问题(其实这个结论不应该被局限在软件开发领域、可以延伸到很多方面)。这并不是说,所有可复用的东西都必须自己实现,别人成熟的通过测试的成果也可以收集、整理、集成到自己的知识库中。但是,最好还是自己实现,这样没有知识产权、版权等问题,关键是自己实现后能真正掌握这个知识点,拥有这个技能。

9、理论与实践并重,内外双修。工程师的内涵是:以工程师的眼光观察、分析事物和世界。一个合格的软件工程师,是真正理解了软件产品的本质及软件产品研发的思想精髓的人(个人观点、欢迎探讨)。掌握软件开发语言、应用语言工具解决工作中的具体问题、完成目标任务是软件工程师的主要工作,但从软件工程师这个角度来看,这只是外在的东西,并非重要的、本质的工作。学习、掌握软件产品开发理论知识、软件开发方法论,并在实践中理解、应用软件产品的分析、设计、实现思想来解决具体的软件产品研发问题,才是真正的软件工程师的工作。站在成熟理论与可靠方法论的高度思考、分析、解决问题,并在具体实践中验证和修正这些思想与方式,最终形成自己的理论体系和实用方法论。

10、心态有多开放,视野就有多开阔。不要抱着自己的技术和成果,等到它们都已经过时变成垃圾了,才拿出来丢人现眼。请及时发布自己的研究成果:开发的产品、有创意的设计或代码,公布出来让大家交流或者使用,你的成果才有进化和升华的机会。想想自己2000年间开发的那些Windows系统工具,5、6年之后的今天,还是那个样子,今天流行的好多Windows系统工具都比自己的晚,但进化得很好,且有那么多用户在使用。并且,不要保守自己的技术和思想,尽可能地与人交流与分享,或者传授给开发团队的成员。“与人交换苹果之后,每个人还是只有一个苹果;但交换思想之后,每个人都拥有两种思想”,道理大家都懂,但有多少人真正能做到呢?

11、尽量参加开源项目的开发、或者与朋友共同研制一些自己的产品,千万不要因为没有钱赚而不做。网络早已不再只是“虚拟世界”,网上有很多的开源项目、合作开发项目、外包项目,这都是涉猎工作以外的知识的绝好机会,并且能够结识更广的人缘。不要因为工作是做ERP,就不去学习和了解嵌入式、实时、通信、网络等方面的技术,反过来也是一样。如果当他别人拿着合同找你合作,你却这也不会,那也不熟时,你将后悔莫及。

12、书到用时方恨少,不要将自己的知识面仅仅局限于技术方面。诺贝尔经济学奖得主西蒙教授的研究结果表明: “对于一个有一定基础的人来说,他只要真正肯下功夫,在6个月内就可以掌握任何一门学问。”教育心理学界为感谢西蒙教授的研究成果,故命名为西蒙学习法。可见,掌握一门陌生的学问远远没有想想的那么高难、深奥。多方吸取、广泛涉猎。极力夯实自己的影响圈、尽量扩大自己的关注圈。财务、经济、税务、管理等等知识,有空花时间看看,韬光养晦、未雨绸缪。

13、本文的总结与反思:不要去做技术上的高手,除非你的目标如此。虽然本文是关于提高软件开发知识的建议,做技术的高手是我一向都不赞同的。你可以提高自己的专业知识,但能胜任工作即止。提高软件知识和技术只是问题的表面,本质是要提高自己认识问题、分析问题、解决问题的思想高度。软件专业知识的很多方法和原理,可以很容易地延伸、应用到生活的其它方面。在能胜任工作的基础上,立即去涉猎其它领域的专业知识,丰富自己的知识体系、提高自己的综合素质,尤其是那些目标不在技术方面的朋友