# ATL Object Wizard

All wizard directives are enclosed in [!…].

SYMBOLS are denoted in uppercase.

The wizard is case sensitive.

The arguments to the wizard directives should be between ( ).

Wizard Directives.

SYMBOL

> Replaces the symbol with the value of the symbol.

if(SYMBOL)

> Wizard ignores whatever is after this statement till it finds an else or endif directive if it does not find the symbol in its map. It does not care for the value of the symbol.

if!(SYMBOL)

> Wizard ignores whatever is after this statement till it finds an else or endif directive if it finds the symbol in its map. It does not care for the value of the symbol.

if=(SYMBOL, "value")

> Wizard performs the directives after this statement till it finds an else or endif directive only if the value of the symbol matches the string.

if!=(SYMBOL, "value")

> Wizard performs the directives after this statement till it finds an else or endif directive only if the value of the symbol does not match the string.

endif

> Indicates the end of the if directives.

else

> Used with if directives to associate directives when the condition fails.

crlf

> Inserts a new line in the output file.

outputoff

> Turns off the output of the processed file.

outputon

> Turns on the output of the processed file.

set(SYMBOL, "value")

> Sets the value of the symbol in the map of symbols. If the symbol is not in the map then it is added to the map.

RemoveSymbol (SYMBOL)

> Removes the symbol from the internal map.

include(FILENAME)

> Parses the contents of the specified file and add the parsed contents to the outputfile. It should be included after a target directive.

target() or target(FILENAME)

> Specifies that further output should be sent to the specified file if one is suggested. Use the include directive to add contents of a template file in to the output file after processing it.

> When specified without a filename it closes the current outputfile.

> Before processing the file this Sets a symbol FileExists to "TRUE" or "FALSE" depending on whether the target file already exists.

> Example :

> target(CPPFILE)

> include(CPPTEMPLATE)

> target()

> The above directives assume that CPPFILE and CPPTEMPLATE symbols point to valid filenames. The first directive target(CPPFILE) specifies that further output should be sent to the specified file. The include(CPPTEMPLATE) directive specifies that the template file specified by CPPTEMPLATE should be processed by the wizard and the processed contents sent to the current output file. The last directive target() specifies that the current output file should be closed. At this point there is not current output file.

newguid(SYMBOL)

> Directive creates a new guid and puts its value into the specified SYMBOL

CopyFile(EXISTINGFILENAME, NEWFILENAME)

> Copies existing file to a new file

DeleteFile(FILENAME)

> Deletes the file

GetTemporaryFileName(SYMBOL)

> Uses system calls to create a temporary filename and sets the symbol to the value.

DoubleSlash(SYMBOL)

> Adds a \ after an existing \ to the value of the symbol. This will be useful when adding headers files with a path in them.

> Example

> INCLUDEFILESTRING has value of "..\include\headerfile.h"

> After the following directive is executed

> DoubleSlash(INCLUDEFILESTRING)

> INCLUDEFILESTRING will be "..\\include\\headerfile.h"

AddInterfaceToIDL(IDLFILENAME, FILENAME)

> Processes the contents of the specified file and adds it to the IDL file assuming that the contents in the specified file is information about an Interface.

AddCoClassToIDL(IDLFILENAME, FILENAME)

> Processes the contents of the specified file and adds it to the IDL file assuming that the contents in the specified file is information about a CoClass.

AddToObjectMap(COCLASSNAME, CLASSNAME)

> Adds a directive to the ObjectMap of project associating the CoClass with the class.

AddIncludeFile(FILENAME, INCLUDEDIRECTIVE)

> Adds the #include preprocessor directive to the specified file. INCLUDEDIRECTIVE specifies what follows #include. The is added after any existing #include directives in the file.

AddImportFile(IDLFILENAME, "value")

> Adds the import statement line to the specified IDL file. Value specifies what to import. This is added after any existing import statements in the IDL file

AddSymbolToString(NEWSYMBOL, "value", SYMBOL)

> Appends the string specified by the symbol to the value and associates the resulting value with the newsymbol.

AddStringToSymbol(NEWSYMBOL, SYMBOL, "value")

> Appends the value of the symbol specified by the second argument to the contents of the string value and associates the resulting value with the symbol specified as the first argument.

AddSymbolToSymbol(SYMBOL1, SYMBOL2)

> Appends the value of SYMBOL2 to the value of SYMBOL1 and associates the resulting string with SYMBOL1

Dialog("dialog1_progid"[,"dialog2_progid"[,…])

> Directive allows the user to invoke the property pages to allow the user to specify the settings for the object being inserted. Specify the ProgID of the registered property pages. Please refer to the section on property pages for information about registering property pages and communicating with the wizard from the property page.

> You can specify a maximum of 9 pages.

AddFileToProject(FILENAME)

> Adds the file to the project with the default build settings.

strcpy(SYMBOL1, SYMBOL2)

> Copies the value of symbol2 to the symbol1

toupper(SYMBOL)

> converts the value of the symbol to upper case

tolower(SYMBOL)

> Converts the value of symbol to lower case

AddRegistryToRC(RGSFILENAME, ID_RGS)

> Adds the specified RGS file into the resources as type REGISTRY with the specified ID.

AddStringResource(ID_OF_STRING, SYMBOL)

> Adds a string resource with the specified ID to the RC file. The values of the string will be the value of the symbol.

AddResourceFromFile(FILE, ID_RESOURCE, RESOURCETYPE)

> Adds the contents of the specified file to the RC file under the correct resource type with the specified ID

Debug ()

> If this directive is added to the .CTL file the wizard displays the directive that failed in case of a failure.

Error ("error message")

> Use this directive to terminate the wizard. It displays the error message before terminating.

AddToMap(CLASSNAME, "map", "map entry", "#of parameters" [, "parameter1" [,...]])

> Adds an entry to the specified map in the given class.

> CLASSNAME – specifies the class that contains the map

> map name – name of the map to add the entry to.

> map entry – the type of entry

> #of parameters – number of parameters in the entry.

> parameter1, ... – parameters to the entry. Max of 5 parameters.

> Example.

> AddToMap(CONTROLCLASS, "COM", " COM_INTERFACE_ENTRY", "1", "ISomeInterface")

> This will add the following line to the COM map in the class specified by CONTROLCLASS.

> COM_INTERFACE_ENTRY(ISomeInterface)

AddClassInclude([CLASSNAME | "classname"], [INCLUDENAME | "includename"], "[h | cpp]")

> Add the include directive to the header or the implementation file associated with a specified class.

> CLASSNAME or classname – the class used to determine the file to put the directive in. This can be a symbol or a value.

> INCLUDENAME or includename – specified what to include

> h or cpp – specifies where to put the directive. h specifies the header file for the class. cpp specifies implementation file for the class.

AddMemberFunction([CLASSNAME | "classname"], "function name", "return type", "parameters", "access", TEMPLATEFILE, "classwizfunc")

> Adds the specified member function to the class.

> CLASSNAME or classname – the class the function has to be added to.

> function name – name of the function to add

> return type – return type of the function

> parameters – parameters to the function

> access – can be one of the following.

> ACCESS_PUBLIC – adds the function as a public member function.

> ACCESS_PROTECTED – adds the function as a protected member function.

> ACCESS_PRIVATE – adds the function as a private member function.

> TEMPLATEFILE – specifies the template file that contains the body of the function.

> classwizfunc – indicates if the function declaration should be between classwizard comments.

> > If this is "0" then it is not a function that should be added between classwizard comments.
> > If it is "1" the it is added between the classwizard comments that pertain to virtual functions.
> > Any other value add it between the comments associated with class wizard message handler comments.

AddMemberVariable([CLASSNAME | "classname"], "variable name", "variable type", "access", "unique variable"[, UNIQUEVARSYMBOL])

> Adds the specified member variable to the class.

> CLASSNAME or classname – the class the variable has to be added to.

> variable name – name of the variable to add

> variable type –type of variable

> access – can be one of the following.

> > ACCESS_PUBLIC – adds the function as a public member variable.

> > ACCESS_PROTECTED – adds the function as a protected member variable

> > ACCESS_PRIVATE – adds the function as a private member variable.

> unique variable – indicates whether the variable has to be an unique symbol.

> > "1" indicates that the symbol has to be unique. requires the next parameter.

> > other values indicate that if a variable is already present then do nothing. Does not require the next parameter.

> UNIQUEVARSYMBOL – If a unique symbol first checks to see if the variable is already a member of the class. If not it add the variable and sets this symbol to value passed in. If the variable already exists then the wizard adds numbers after the given variable name till it finds a value that is unique and adds the variable to the class. On return this symbol will contain the value that was created and inserted into the class.

AddToMemberFunction([CLASSNAME | "classname"], function, TEMPLATEFILE, SYMBOL, SEARCHTYPE[, "search criteria"])

> Adds code to a function depending onto search. When a string search fails the code is added to the end of the function.

> CLASSNAME or classname – the class the function is a member of. Use NULL for global functions.

> function – function to add code to.

> TEMPLATEFILE – template file that contains the code to be inserted.

> SYMBOL – this symbol is set to 1 before the template file is processed and then removed after the processing is over. This allows a template file to contain code for different purposes all of them wrapped in different conditionals.

> SEARCHTYPE – can be one of the following.

> > BEGIN – beginning of the function. Does not require the next parameter.

> > END – end of the function. Does not require the next parameter.

> > LINEOFFSET – a line offset from the beginning of the function. Next parameter is a number that gives the offset.

> AFTERSEARCH – Inserts on the line after the line that contains the search string. Next parameter is the string to search for. Search begins at the beginning of the function and the first match is used. If search fails the string is added to the end of the function.

> BEFORESEARCH – Inserts on the line before the line that contains the search string. Search begins at the beginning of the function and the first match is used. If search fails the string is added to the end of the function.

> search criteria – depends on the previous parameter.

AddBaseClass(CLASSNAME, BASECLASS, "access")

> Adds a class as a base class to the specified class.

> CLASSNAME – the class to which the baseclass is to be added

> BASECLASS – the class to be added as the base class

> access – This is ignored. The class is added as public.

ExistsMemberFunction([CLASSNAME | "classname"], "function prototype", SYMBOL)

> Checks if a function is a member of the specified class.

> CLASSNAME or classname – The class that should contain the member function. Use NULL for global functions.

> function prototype – prototype of the function to check

> SYMBOL – this symbol is set to "1" if the function is a member otherwise it is set to "0".

ExistsInMemberFunction([CLASSNAME | "classname"], "function prototype", "search string", SYMBOL)

> Checks if a function contains the specified string.

> CLASSNAME or classname – the class the function is a member of. Use NULL for global functions.

> function prototype – prototype of the function to search in.

> search string – string to search for

> SYMBOL – this symbol is set to "1" if the search succeeds otherwise it is set to "0".

IDLSettingsForATL()

> Modifies the settings for an IDL/ODL already in the project to match the settings required for using ATL in the project.

OpenDialog(DIALOGID)

> Opens a dialog in the dialog editor after the wizard exists.

> DIALOGID – the id of the dialog to open.

Commit()

> Commits changes made by the wizard till that point. Changes made before this directive will not undone if the wizard fails after this directive. This directive commits all the changes before it so the changes can be accessed in the wizard. This is used by the Dialog wizard to open the dialog resource. Any change made by the wizard will not be accessible in the wizard till they are committed by this directive.

PropertyPages

Property pages can be created to allow customization of the object being inserted. The wizard creates a property sheet using the OleCreatePropertyFrame API. The property pages that are displayed using this property sheet should support the IPropertyPage interface.

Property pages can communicate with the wizard using the ISymbolMap interface implemented by the wizard.

ISymbolMap

In addition to the IUnknown methods it implements the following.

HRESULT Set(/* [in] */ LPCOLESTR strSymbol, /* [in] */ LPCOLESTR strValue);

> Adds the symbol string pair to the internal map of the wizard

> Returns S_OK

HRESULT Get(/* [in] */ LPCOLESTR strSymbol, /* [retval][out] */ BSTR *pstrValue);

> Retrieves the value associated with the specified symbol. If the call is successful pstrValue will point to a BSTR which is allocated using SysAllocString. The caller should free this string using SysFreeString

> Returns S_OK if it finds the symbol. E_FAIL if the symbol is not found.

HRESULT STDMETHODCALLTYPE Clear(void) ;

> Clears all the internal map of the wizard of all symbols.

HRESULT SetStatus(const CLSID *pclsid, BOOL bEnableOK);

> Allows each individual property pages to specify if the OK button can be enabled to allow the user to complete their selections. This is useful when the OK button has to be disabled till the user has entered some required values.

> pcslid points to the CLSID of the property page that is calling this method.

> bEnableOK specifies whether the OK button should be enable or disabled. TRUE specifies enable.

ISymbolMap can be obtained by Querying the IUnknown passed through SetObjects method of IPropertyPage.

Existing Property Pages

Following are some of the existing property pages.

Names Property Page

Sets the following symbols. ProgID is "Names98". The symbol ComponentType controls enabling/disabling controls. This is a string with each character representing the field in the tab order. Each char should be either 0 or 1.

Symbol | Contents of field on the Property Page that is used for value
ShortName | Short Name
ClassName | Class
HeaderName | .H File
CPPName | .CPP File
CoClassName | CoClass
InterfaceName | Interface
TypeName | Type
ProgID | ProgID with a .1 appended
VersionIndependentProgID | ProgId
ObjectGUID | None. A new GUID is generated and its value is used.

Miscellaneous Property Page

Sets the following Symbols. ProgID is "CtlDlg98"

Symbol | Contents of field on the Property Page that is used for value
WindowedOnlyEnabled | Windowed (TRUE or FALSE)
InsertableEnabled | Insertable (TRUE or FALSE)
NormalizeDCEnabled | Normalize DC (TRUE or FALSE)
Opaque | Opaque (TRUE or FALSE)
SolidBackground | SolidBackground (TRUE or FALSE)
MiscStatus | Misc setting fields
SubclassWindow | Subclass Combobox selection
SubclassWindowSymbol | Subclass Combobox selection without those characters that cannot be used in an identifier.

Attributes Property Page

Sets the following Symbols. ProgID is "ObjDlg98"

The symbol Attributes controls enabling/disabling controls. This is a string with each character representing the field in the tab order. Each char should be either 0 or 1.

Symbol | Contents of field on the Property Page that is used for value
FreeThreadedMarshaler | FreeThreadedMarshaler (TRUE or FALSE)
ErrorInfoEnabled | ErrorInfo (TRUE or FALSE)
ConnectionPointsEnabled | ConnectionPoints (TRUE or FALSE)
Dual | Dual (TRUE or FALSE)
ThreadingModel | ThreadingModel (Single, Apartment, Both, Free)
Aggregatable | Aggregatable (YES, NO, ONLY)

Registry entries.

The following keys are added to the registry under the

HKCU\Software\Microsoft\ATLWizard

{GUID}

Category – REG_SZ "<Category>"

Control – REG_SZ "<File name>"

Icon – REG_SZ "File Name>"

Name – REG_SZ "<Name>"

{GUID} – a GUID for your component. This is not associated with anything else. A GUID is used to get a unique name

Category – specifies the category under which the component should appear.

Control – name of the control file that has directives for the component. It is normally a .CTL file.

Icon – Name of the file that contains the Icon for the component.

Name – Name of the component.

Register the property page. The wizard loads the property page using its ProgID specified in the directive file.

Symbols set by the Wizard before invoking the control file.

Symbol | Value
LibName | Name of the typelib in the .IDL file for the project
LibGUID | GUID of the typelib
GalleryPath | Points to the directory that contains the template files
ProjectName | Name of the project
ProjectNameCPP | Name of the project cpp file. (projectname.cpp)
ProjectType | type of project. EXE or DLL
ProjectAppID | the AppID of the local server it the project is of type EXE. Otherwise not set
ProjectNameRC | Name of the RC file for the project. (Projectname.rc)
ProjectNameHeader | Name of the project header. (Projectname.h)
VCINCDIR | Points to vc include directory
NULLSTR | set to ""
VC | Set to 6.0 in VC 6.0
STDAFXH | Set to stdafx.h
stdafx.h | Set stdafx.h
STDAFXCPP | Set to stdafx.cpp
stdafx.cpp | Set to stdafx.cpp
ObjectMap | Set to 1 if object map is found. otherwise symbol is not available
ProjectDirectory | Projects directory
IDLProject | Name of the IDL/ODL file for the project. Set to "" if the project does not have an IDL/ODL
ProjectHadIDL | Set to TRUE if the project has IDL/ODL file. Otherwise the symbol is not available.
CWinApp | If the project is MFC then this SYMBOL contains the name of the CWinApp derived class otherwise set to ""
MFC | Set to "1" for MFC projects. Otherwise not available
MFCCTL | Set to "1" if the project is a MFC control project. otherwise set to "0"
MFCOLE | Set to "1" if MFC project is MFC control project. Otherwise not available.
ProjectNameSafe | If the project name contains characters that cannot be used as an identifier the this symbol contain the project name after removing such characters.

Sample Registry files.

    .RGS file for the wizard.

    HKCR
    {
    	ATLOBJ.ATLWiz = s 'ATL Object Wizard'
    	{
    		CurVer = s 'ATLOBJ.ATLWiz.1'
    		CLSID = s '{2EF12E30-3C3B-11d1-AFA9-00C04FB99436}'
    	}
    	ATLOBJ.ATLWiz.1 = s 'ATL Object Wizard'
    	{
    		CLSID = s '{2EF12E30-3C3B-11d1-AFA9-00C04FB99436}'
    	}
    	NoRemove CLSID
    	{
    		ForceRemove {2EF12E30-3C3B-11d1-AFA9-00C04FB99436} = s 'ATL Object Wizard'
    		{
    			ProgID = s 'ATLOBJ.ATLWiz.1'
    			VersionIndependentProgID = s 'ATLOBJ.ATLWiz'
    			InprocServer32 = s '%Module%'
    		}
    	}
    }
    HKLM
    {
    	Software
    	{
    		Microsoft
    		{
    			DevStudio
    			{
    				6.0
    				{
    					ATLWizard
    					{
    						'{6E871954-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Objects'
    							val 'Control' = s 'object.ctl'
    							val 'Icon' = s 'object.ico'
    							val 'Name' = s 'Simple Object'
    						}
    						'{6E871955-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Objects'
    							val 'Name' = s 'Add-in Object'
    							val 'Control' = s 'addin.ctl'
    							val 'Icon' = s 'addin.ico'
    						}
    						'{6E871956-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Objects'
    							val 'Name' = s 'Internet Explorer Object'
    							val 'Control' = s 'ieuiless.ctl'
    							val 'Icon' = s 'iexplore.ico'
    						}
    						'{6E871957-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Objects'
    							val 'Name' = s 'ActiveX Server Component'
    							val 'Control' = s 'asp.ctl'
    							val 'Icon' = s 'asp.ico'
    						}
    						'{6E871960-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Objects'
    							val 'Name' = s 'MS Transaction Server Component'
    							val 'Control' = s 'msdtx.ctl'
    							val 'Icon' = s 'msdtx.ico'
    						}
    						'{6E871961-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Objects'
    							val 'Name' = s 'Component Registrar Object'
    							val 'Control' = s 'compreg.ctl'
    							val 'Icon' = s 'compreg.ico'
    						}
    						'{6E871958-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Controls'
    							val 'Name' = s 'Full Control'
    							val 'Control' = s 'control.ctl'
    							val 'Icon' = s 'control.ico'
    						}
    						'{6E871959-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Controls'
    							val 'Name' = s 'Lite Control'
    							val 'Control' = s 'control.ctl'
    							val 'Icon' = s 'iexplore.ico'
    							val 'SetSymbol' = s 'IEControl'
    						}
    						'{6E87195D-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Controls'
    							val 'Name' = s 'Composite Control'
    							val 'Control' = s 'control.ctl'
    							val 'Icon' = s 'compsite.ico'
    							val 'SetSymbol' = s 'CompositeControl'
    						}
    						'{6E871962-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Controls'
    							val 'Name' = s 'HTML Control'
    							val 'Control' = s 'control.ctl'
    							val 'Icon' = s 'HTML.ico'
    							val 'SetSymbol' = s 'HTMLControl'
    						}
    						'{6E87195A-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Controls'
    							val 'Name' = s 'Property Page'
    							val 'Control' = s 'proppage.ctl'
    							val 'Icon' = s 'proppage.ico'
    						}
    						'{6E87195B-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Miscellaneous'
    							val 'Control' = s 'dialog.ctl'
    							val 'Icon' = s 'dialog.ico'
    							val 'Name' = s 'Dialog'
    						}
    						'{6E87195E-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Objects'
    							val 'Control' = s 'snapin.ctl'
    							val 'Icon' = s 'snapin.ico'
    							val 'Name' = s 'MMC SnapIn'
    						}
    						'{6E87195C-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Data Access'
    							val 'Name' = s 'Provider'
    							val 'Control' = s 'provider.ctl'
    							val 'Icon' = s 'provider.ico'
    						}
    						'{6E87195F-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Data Access'
    							val 'Name' = s 'Consumer'
    							val 'Control' = s 'consumer.ctl'
    							val 'Icon' = s 'consumer.ico'
    						}
    						'{6E871963-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Controls'
    							val 'Name' = s 'Lite Composite Control'
    							val 'Control' = s 'control.ctl'
    							val 'Icon' = s 'compsite.ico'
    							val 'SetSymbol' = s 'CompositeControl,IEControl'
    						}
    						'{6E871964-50AD-11D0-883E-080000185165}'
    						{
    							val 'Category' = s 'Controls'
    							val 'Name' = s 'Lite HTML Control'
    							val 'Control' = s 'control.ctl'
    							val 'Icon' = s 'HTML.ico'
    							val 'SetSymbol' = s 'HTMLControl,IEControl'
    						}
    					}
    				}
    			}
    		}
    	}
    }

.RGS file for Names property page.

    HKCR
    {
    	Names98.Names98 = s 'Names98 Class'
    	{
    		CurVer = s 'Names98.Names98.1'
    		CLSID = s '{53FCBC28-5015-11d1-AFC7-00C04FB99436}'
    	}
    	Names98.Names98.1 = s 'Names98 Class'
    	{
    		CLSID = s '{53FCBC28-5015-11d1-AFC7-00C04FB99436}'
    	}
    	NoRemove CLSID
    	{
    		ForceRemove {53FCBC28-5015-11d1-AFC7-00C04FB99436} = s 'Names98 Class'
    		{
    			InprocServer32 = s '%MODULE%'
    			{
    				val ThreadingModel = s 'Apartment'
    			}
    		}
    	}
    }

Smaple Property Page code

.H File

    // testpg.h : Declaration of the Ctestpg

    #ifndef __TESTPG_H_
    #define __TESTPG_H_

    #include "resource.h"       // main symbols

    EXTERN_C const CLSID CLSID_testpg;

    /////////////////////////////////////////////////////////////////////////////
    // Ctestpg
    class ATL_NO_VTABLE Ctestpg :
    	public CComObjectRootEx<CComSingleThreadModel>,
    	public CComCoClass<Ctestpg, &CLSID_testpg>,
    	public IPropertyPageImpl<Ctestpg>,
    	public CDialogImpl<Ctestpg>
    {
    public:
    	Ctestpg()
    	{
    		m_dwTitleID = IDS_TITLEtestpg;
    		m_dwHelpFileID = IDS_HELPFILEtestpg;
    		m_dwDocStringID = IDS_DOCSTRINGtestpg;
    	}

    	enum {IDD = IDD_TESTPG};

    DECLARE_REGISTRY_RESOURCEID(IDR_TESTPG)

    BEGIN_COM_MAP(Ctestpg)
    	COM_INTERFACE_ENTRY_IMPL(IPropertyPage)
    END_COM_MAP()

    BEGIN_MSG_MAP(Ctestpg)
    	COMMAND_ID_HANDLER(IDC_SET, OnSet);
    	COMMAND_ID_HANDLER(IDC_GET, OnGet);
    	CHAIN_MSG_MAP(IPropertyPageImpl<Ctestpg>)
    END_MSG_MAP()

    	LRESULT OnSet(short , short nID , HWND hWnd, BOOL& bHandled)
    	{
    		CComQIPtr<ISymbolMap, &IID_ISymbolMap> pMap(m_ppUnk[0]);
    		if (!pMap)
    			return S_OK;

    		TCHAR szSymbol[128];
    		TCHAR szValue[128];
    		GetDlgItemText(IDC_SYMBOL, szSymbol, 128);
    		GetDlgItemText(IDC_VALUE, szValue, 128);
    		pMap->Set(CComBSTR(szSymbol), CComBSTR(szValue));
    		return 0;
    	}

    	LRESULT OnGet(short , short nID , HWND hWnd, BOOL& bHandled)
    	{
    		CComQIPtr<ISymbolMap, &IID_ISymbolMap> pMap(m_ppUnk[0]);
    		if (!pMap)
    			return S_OK;

    		TCHAR szSymbol[128];
    		GetDlgItemText(IDC_SYMBOL, szSymbol, 128);
    		CComBSTR szValue;
    		pMap->Get(CComBSTR(szSymbol), &szValue);
    		USES_CONVERSION;
    		SetDlgItemText(IDC_VALUE, OLE2T(szValue));

    		return 0;
    	}

    	STDMETHOD(Apply)(void)
    	{
    		ATLTRACE(_T("Ctestpg::Apply\n"));
    		if (m_hWnd == NULL)
    			return S_OK;
    		CComQIPtr<ISymbolMap, &IID_ISymbolMap> pMap(m_ppUnk[0]);
    		if (!pMap)
    			return S_OK;

    		TCHAR szSymbol[128];
    		TCHAR szValue[128];
    		GetDlgItemText(IDC_SYMBOL, szSymbol, 128);
    		GetDlgItemText(IDC_VALUE, szValue, 128);
    		pMap->Set(CComBSTR(szSymbol), CComBSTR(szValue));

    		m_bDirty = FALSE;
    		return S_OK;
    	}
    };

    #endif //__TESTPG_H_

.rgs

    HKCR
    {
    	testpg.testpg.1 = s 'testpg Class'
    	{
    		CLSID = s '{6992CC0C-472B-11D1-AA8C-000000000000}'
    	}
    	testpg.testpg = s 'testpg Class'
    	{
    		CLSID = s '{6992CC0C-472B-11D1-AA8C-000000000000}'
    		CurVer = s 'testpg.testpg.1'
    	}
    	NoRemove CLSID
    	{
    		ForceRemove {6992CC0C-472B-11D1-AA8C-000000000000} = s 'testpg Class'
    		{
    			ForceRemove 'Programmable'
    			InprocServer32 = s '%MODULE%'
    			{
    				val ThreadingModel = s 'Apartment'
    			}
    		}
    	}
    }

Dialog resource for the page

    IDD_TESTPG DIALOG DISCARDABLE  0, 0, 271, 105
    STYLE WS_CHILD
    FONT 8, "MS Sans Serif"
    BEGIN
        EDITTEXT        IDC_SYMBOL,22,24,40,14,ES_AUTOHSCROLL
        LTEXT           "Symbol",IDC_STATIC,22,8,24,8
        LTEXT           "Value",IDC_STATIC,87,8,19,8
        EDITTEXT        IDC_VALUE,87,24,40,14,ES_AUTOHSCROLL
        PUSHBUTTON      "Set",IDC_SET,22,49,40,14
        PUSHBUTTON      "Get",IDC_GET,87,49,40,14
    END

String resources.

    IDS_TITLEtestpg "Title"

    IDS_HELPFILEtestpg "Help File Name"

    IDS_DOCSTRINGtestpg "Your Help String"

# ISymbolMap

    [
        object,
        uuid(C6D58201-1FA3-11D0-BF1E-0000E8D0D146),
        helpstring("ISymbolMap Interface"),
        pointer_default(unique)
    ]
    interface ISymbolMap : IUnknown
    {
        HRESULT Set([in] LPCOLESTR strSymbol, [in] LPCOLESTR strValue);
        HRESULT Get([in] LPCOLESTR strSymbol, [out, retval] BSTR* pstrValue);
        HRESULT Clear();
        HRESULT SetStatus(const CLSID* pclsid, BOOL bEnableOK);
    };