OTA - Implementing the Project Module

Before we can use the Module Services to create a new Project Module we need to do a little bit of background work first. There is a method in the Module Services interface specifically for creating new Modules:

  function CreateModule(const Creator: IOTACreator): IOTAModule;

  Notice this takes a parameter of IOTACreator. What is a Creator? A Creator is basically the definition of the Module to be created. The IDE calls the methods of the Creator and uses that information to choose what Module to create and how to create it. Once the Module is created it is returned to the caller.

  IOTACreator is one of the most frustrating interfaces in the OTA. It seems clear enough how to use it but the subtleties quickly show through. When methods return information that is not compatible the usual response of the IDE is to either fail in the creation of the Module, or more often throw error strange and useless error messages.

  I will try to take the frustration and mystery out of this important interface.


Implementing IOTACreator

  The type of Module to be created will dictate the IOTACreator that is necessary. The following Creators are available:

  • IOTAProjectGroupCreator

  • IOTAProjectCreator50 (D5 and up only)
  • IOTAProjectCreator
  • IOTAModuleCreator
  • IOTAAdditionalFilesModuleCreator (D7 and up only)

Since we are coding a ProjectCreator the IOTAProjectCreator is the interface to use:

IOTACreator = interface(IUnknown)
  ['{6EDB9B9E-F57A-11D1-AB23-00C04FB16FB3}']
  function GetCreatorType: string;
  function GetExisting: Boolean;
  function GetFileSystem: string;
  function GetOwner: IOTAModule;
  function GetUnnamed: Boolean;
end;

IOTAProjectCreator = interface(IOTACreator)
  ['{6EDB9B9D-F57A-11D1-AB23-00C04FB16FB3}']
  function GetFileName: string;
  function GetOptionFileName: string;
  function GetShowSource: Boolean;
  procedure NewDefaultModule;
  function NewOptionSource(const ProjectName: string): IOTAFile;
  procedure NewProjectResource(const Project: IOTAProject);
  function NewProjectSource(const ProjectName: string): IOTAFile;
end;

D8 and D2005
It is also necessary to implement another interface for these versions of Delphi to create a project. Newer version of the IDE support more than one programming language so it can no longer be assumed that the project should be in Delphi!

  IOTAProjectCreator80 = interface(IOTAProjectCreator50)
  ['{9A1D6AF5-84FA-481C-A446-746D9A50F53E}']
  function GetProjectPersonality: string;

  property ProjectPersonality: string read GetProjectPersonality;
  end;

  Lets take the methods one at a time.

IOTACreator


function IOTACreator.GetCreatorType: string;

  This method informs the IDE as to what type of project you would like to create. The valid choices are:

  • sApplication - Creates a default Application

  • sLibrary - Creates a default DLL
  • sConsole - Creates a default Console Application
  • sPackage - Creates a default Package
  • Empty String - Uses the IOTAFile returned by NewProjectSource to create source

At first this seem clear enough but there is a hidden catch. If you use one of these constants you must not return an IOTAFile in the

 function NewProjectSource(const ProjectName: string): IOTAFile;

method. Returning an IOTAFile is how you define your own contents of the file. If you do strange things can happen with the most common being this useless error message during the execution of the CreateModule call:

Useless Error

So to summarize

  • Only use the defined constants if you want to create default projects where the IDE creates the code

  • If you want to define the code through an IOTAFile then return an empty string in this method.

  • If using a later version of Delphi the predefined constants will create a cross platform Module. For instance creating a sApplication will cause the code to contain the QForms unit instead of the Forms unit, meaning that for cases where the Wizard needs to support Win32 only you will be returning an empty string and creating your own IOTAFile with the source code.


function IOTACreator.GetExisting: Boolean;

  This method informs the IDE if the module is existing. Typically when creating a new object the object does not exist so the normal return value is False. The catch here is a object may have multiple Owner Modules so I am assuming that a "creation" in this case means the object exists and it is now a shared resource between more than one Module. I have not seen an example of this in my testing.

There is one pitfall to this. If you look at the IOTAProjectCreator.GetFileName method it appears that you could use this method to tell the IDE that the file does not exist and to create the it using the file name returned in the other method. This does not work. By doing this you are creating a cooperation of methods across interfaces (IOTACreator and IOTAProjectCreator) which should not be (and is not) done. See IOTAProjectCreator.GetFileName for more information.

For a Project Module the only time this is valid to have True is if you currently have a valid Project Group Module handle to return in IOTACreator.GetOwner.


function IOTACreator.GetFileSystem: string;

  Returns the IDString of the FileSystem to use for this module source. File Systems are an advanced topic that are more suited for IDE Editor enhancements. Typically an empty string is returned.


function IOTACreator.GetOwner: IOTAModule;

  Returns the Module that will be the owner of the new object being created. For example if the object being created is a Form then the owner would be the Project. If the object being created is a Project Module then the owner would be the Project Group.


function IOTACreator.GetUnnamed: Boolean;

  By returning true the IDE will show the Save As.. dialog box when the user tries to save the object for the first time.

This has strange effects if False when the object is a Project Module. It throws an error that the associated *.res file for the project does not exist and wants to create it.


 

IOTAProjectCreator


function IOTAProjectCreator.GetFileName: string;

  Return a fully qualified file name of the source that already exists.

You can not use this method to suggest a name for the new Module, it is only valid if the Module is to be associated with a file that already exists. It may not necessarily mean the file is on the disk depending on what type of file system is returned, although this is pure speculation as I have never tried it.



function IOTAProjectCreator.GetOptionFileName: string;

  Returns the name on an existing C++ Option file. The same caution applies as explained in the IOTAProjectCreator.GetFileName method.

C++ Only. I believe the Option file is the equivalent to the Delphi *.dof file which is handled a bit differently in Delphi.


function IOTAProjectCreator.GetShowSource: Boolean;

  If you want the IDE to show the source in the Editor when the Module is created return true.


procedure IOTAProjectCreator.NewDefaultModule;

  Called to create a new Default Module for the Project.

I am not sure quite how to use this method as this is called in the middle of creating the project so when it is called we still do not have a IOATProjectModule interface to assign as and Default Modules owner.


function IOTAProjectCreator.NewOptionSource(const ProjectName: string): IOTAFile;

  Called to create the C++ Option file by returning an IOTAFile that implements the file source. The parameter is the Project Name. If the IDE created the Module then it has assigned a default identifier to the project, typically something like "Project1" if it is the first Project created. It allows you to use the name of the Project in any source code that may be generated in this method.

C++ Only. I believe the Option file is the equivalent to the Delphi *.dof file which is handled a bit differently in Delphi.

It is best to design your IOTAFile implementation such that the ProjectName can be passed on to the File object.


procedure IOTAProjectCreator.NewProjectResource(const Project: IOTAProject);

  If there is some special non standard resource file that is necessary for your Project it may be created here. Notice that the Project Module is created and a pointer to it is passed in this method.

Never seen this be called in Delphi, is it a C++ only method?


function IOTAProjectCreator.NewProjectSource(const ProjectName: string): IOTAFile;

  This is where the source for the project is added. Again if you are using a standard Creator Type return a nil, see IOTACreator.GetCreatorType, if GetCreatorType is returning an empty string you must return a valid IOTAFile interface or you will get an AV.

  The actual source code is not written in the method. It is done indirectly in the IOTAFile.GetSource implementation.

It is best to design your IOTAFile implementation such that the ProjectName can be passed on to the File object.

IOTAProjectCreator80


function GetProjectPersonality: string;

  This method tells the IDE what language the new project will be created in. The supported Personalities are:

  •   sDefaultPersonality = 'Default.Personality';

  •   sDelphiPersonality = 'Delphi.Personality';
  •   sDelphiDotNetPersonality = 'DelphiDotNet.Personality';
  •   sCBuilderPersonality = 'CPlusPlusBuilder.Personality';
  •   sCSharpPersonality = 'CSharp.Personality';
  •   sVBPersonality = 'VB.Personality';

 

 


IDE ProjectCreator Call Stack

  The following is a debug output of how the IDE calls the Project Module Creator when a default sApplication string is returned for GetCreatorType.

Project Creator Debug Output

  Notice the NewProjectResource and NewOptionSource methods are not called but the NewProjectSource is, even though if you return a IOTAFile it messes everything up. This was with Delphi 7.

 

  Now when an empty string is returned for GetCreatorType.

Output Debug

There is one mystery here. Why does the second window show a ProjectName of Project1 passed as a parameter to NewProjectSource? According to these call stacks the IDE does not know that the GetCreatorType is returning an empty string yet.

 

  Next we need to implement a File to create our own code in the unit.

 


mustangpeak.net

  Last Modified on: