Author Topic: Win32 SDK Tutorial - Form7 - IFileSaveDialog, IFileOpenDialog  (Read 115 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 44
Win32 SDK Tutorial - Form7 - IFileSaveDialog, IFileOpenDialog
« on: October 20, 2021, 04:08:00 pm »
/*
     Form7 -- IFileOpenDialog, IFileSaveDialog, Menus And Dialogs Without Resource Scripts

     This is essentially the Form6 Project redone so as to not use resource scripts for menus and
dialogs, i.e., it shows how to create dialog boxes and menus programatically using Win32 API
functions, and it also serves as kind of an introduction to COM (Component Object Model), in that
rather than using the OPENFILENAME struct for OpenFile and SaveFile dialog boxes, it shows how to
use the IFileOpenDialog and IFileSaveDialog COM (Component Object Model) interfaces to create these
visual objects.  If you are not familiar with seeing names such as above prefaced with an 'I'
symbol, that stands for Interface.  What an interface is, is a struct.  Simple as that.  In one of
the more fundamental Windows includes included by Windows.h - ObjBase.h, is this... 

Code: [Select]
#define __STRUCT__ struct
#define interface __STRUCT__

...which is just a round about way of saying that an interface is a struct.  But it is a special
kind of struct.  According to Microsoft's Component Object Model, which is actually a Microsoft
standard, this struct/interface can't hold just anything.  It can only hold function pointers to the
actual functions which implemrnt the interface.  There are no data members in these interfaces, and
they maintain no 'state'.  In terms of C++ Classes, this Form7 Tutorial/Program instantiates two
classes, one of which implements the IOpenFileDialog Interface, and the other implements the
IFileSaveDialog.  However, those classes are not implemented in this Form7 code - they are imple-
mented in various COM library files which we'll link to in this application.  Which raises the
question, how will we instantiate these objects here if they are not implemented here?  The Windows
COM subsystem has it's own ways of creating objects, and the way we'll do it here is with the
function CoCresateInstance()....

Code: [Select]
CoCreateInstance
(
  REFCLSID    rclsid,
  LPUNKNOWN   pUnkOuter,
  DWORD       dwClsContext,
  REFIID      riid,
  LPVOID*     ppv 
);

rclsid        [in]  CLSID associated with the data and code that will be used to create the object.

pUnkOuter     [in]  If NULL, indicates that the object is not being created as part of an aggregate. If
                    non-NULL, pointer to the aggregate object's IUnknown interface (the controlling IUnknown).

dwClsContext  [in]  Context in which the code that manages the newly created object will run. The values are
                    taken from the enumeration CLSCTX.
                   
riid          [in]  Reference to the identifier of the interface to be used to communicate with the object.

ppv           [out] Address of pointer variable that receives the interface pointer requested in riid. Upon
                    successful return, *ppv contains the requested interface pointer. Upon failure, *ppv
                    contains NULL.

Return Value

S_OK                     An instance of the specified object class was successfully created.

REGDB_E_CLASSNOTREG      A specified class is not registered in the registration database. Also can indicate that
                         the type of server you requested in the CLSCTX enumeration is not registered or the
                         values for the server types in the registry are corrupt.

CLASS_E_NOAGGREGATION    This class cannot be created as part of an aggregate.

E_NOINTERFACE            The specified class does not implement the requested interface, or the controlling
                         IUnknown does not expose the requested interface.

....whose return value is an 'HRESULT', which is a success/failure code among other things (it
returns coded diagnostic information).  Two of those parameters above, as well as others, may be
things you are not familiar with - REFCLSID and REFIID.  These are 128 bit GUIDs - Globally Unique
Identifiers, which actually is another struct....

Code: [Select]
struct GUID
{
 DWORD Data1;
 WORD  Data2;
 WORD  Data3;
 BYTE  Data4[8];
};

The FileOpenDialog Class - which implements the IFileOpenDialog interface has this predefined GUID...

Code: [Select]

class __declspec(uuid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")) FileOpenDialog;

...which can be found in ShObjIdl.h and ShObjIdl.idl.  The __declspec and uuid symbols above are
Microsoft specific additions to C++ known as 'extended attributes'.  Rather than me describing it
let's just defer to Microsoft's docs....

"The extended attribute syntax for specifying storage-class information uses the __declspec keyword,
which specifies that an instance of a given type is to be stored with a Microsoft-specific storage-class
attribute listed below. Examples of other storage-class modifiers include the static and extern
keywords. However, these keywords are part of the ANSI specification of the C and C++ languages, and
as such are not covered by extended attribute syntax. The extended attribute syntax simplifies and
standardizes Microsoft-specific extensions to the C and C++ languages."

The string contained within the uuid attribute is a string representation of the class's GUID. 
These can be obtained with the StringFromCLSID function.  Here is a short program providing an
example of that, which returns the FileOpenDialog class string above...

Code: [Select]
// StrFromClsid.cpp
// cl StrFromClsid.cpp /link ole32.lib
// cl StrFromClsid.cpp /O1 /Os /GR- /GS- /Gy /link TCLib.lib ole32.lib
// 3,584 Bytes TCLib
#define TCLib
#include <windows.h>
#ifdef TCLib
   #include "stdio.h"
#else   
   #include <stdio.h>
#endif
#include <ShObjIdl.h>

int main()
{
 LPOLESTR lpOleStr = NULL;
 
 StringFromCLSID(CLSID_FileOpenDialog, &lpOleStr);
 wprintf(L"lpOleStr = %s\n", lpOleStr);
 CoTaskMemFree(lpOleStr);
 getchar();

 return 0;
}

...and here is the output from building and running it...

Code: [Select]
lpOleStr = {DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7}

Kind of intimidating I know, but take it slow, study some of the include files such as objbase.h and
shobjidl.h, and study this example.  Here is what the CoCreateInstance() call I just spoke of above
looks like in my procedure WndProc_OnFileOpen()....

Code: [Select]
IFileOpenDialog*  pFileOpen  = NULL;
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void**)(&pFileOpen));

The 1st parameter, CLSID_FileOpenDialog, is as described above, a 'Class ID' or GUID, defined in
ShObjIdl.h.  Note the name of the class is FileOpenDialog but that name isn't used.  The function
(CoCreateInstance) requires a REFCLSID instead, kind of approximately like so....

Code: [Select]
CLSID_FileOpenDialog = "DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7"  // <<< Pseudo Code String Representation

The 2nd GUID parameter of CoCreateInstance is another special kind of GUID known as an interface id
or 'IID'.  It can also be found in ShObjIdl.h and for the 4th parameter it is this in string repre-
sentation...

Code: [Select]
IID_IFileOpenDialog = "d57c7288-d4ad-4768-be02-9d969532d960"   // <<< Pseudo Code String Representation

Further note about this CoCreateInstance() call that the parameter we are looking for (cuz we need
it to get an Open File Dialog Box) is not returned by the function as a return value - that is an
HRESULT success/failure code as previously stated above, but is returned as an 'out' parameter as
the last parameter of the function.  Of the five parameters of CoCreateInstance(), the first four
are 'in' parameters, and the last is an 'out' parameter.  This is a design pattern you'll see
continually used in COM, where in most cases the last parameter or sometimes only parameter is typed
as an _OUT void**, which turns type checking off in the compiler - so anything can be returned (and
any kind of crazy mistake can be made). 

So to sum things up in terms of C++ understanding, and in very high level terms, what we are seeing
here is instantiating an object located in a library file, and getting returned to us a pointer to
an interface implemented by that object.  But search as you might in ShObjIdl.h or any other Windows
header file for a description of what the FileOpenDialog class looks like and you will come up 'dry'.
You won't find it.  It is completely encapsulated away.  Reason for that is quite 'deep'.  Bit of a
short digression on that...

In the early days of C++ folks made dlls containing classes, and shipped applications which used
these dlls.  It's natural to try to improve code, and when later and improved dlls were shipped, it
broke existing applications.  Reason is, there was too tight a coupling between an application's
knowledge of classes in a dll, i.e., their size and structure, and the definition of the classes as
actually built into the application's code.  So every application using a newer dll would need to be
rebuilt against that newer dll - something that doesn't always happen.  So applications were being
broken by changes in underlying dependencies, i.e., dlls.

COM was invented as a more advanced and robust type of OOP (Orject Oriented Programming) so as to
overcome this difficulty with the C++ Object Model.  All you'll ever get of COM Classes are
interface pointers to interfaces implemented in the class.  And while you'll have no information
on the underlying class, you'll have all the information you need on the interfaces implemented in
the class.  Below are some of the interfaces from IFileOpenDialog, which I've reformatted some, as
taken from ShObjIdl.h (Except IUnknown.  That's in Unknwn.h).  You could easily find these in those
includes if you look....     

Code: [Select]
interface __declspec(uuid("00000000-0000-0000-C000-000000000046")) __declspec(novtable) IUnknown                             //  3 Members
{
 virtual HRESULT __stdcall QueryInterface      (REFIID riid, void** ppvObject                           ) = 0;
 virtual ULONG   __stdcall AddRef              (void                                                    ) = 0;
 virtual ULONG   __stdcall Release             (void                                                    ) = 0;
};

interface __declspec(uuid("b4db1657-70d7-485e-8e3e-6fcb5a5c1802")) __declspec(novtable) IModalWindow : public IUnknown       //  1 Member
{
 virtual HRESULT __stdcall Show                (HWND hwndParent                                         ) = 0;
};

interface __declspec(uuid("42f85136-db7e-439c-85f1-e4075d135fc8")) __declspec(novtable) IFileDialog : public IModalWindow    // 23 Members
{
 virtual HRESULT __stdcall SetFileTypes        (UINT cFileTypes, const COMDLG_FILTERSPEC* rgFilterSpec  ) = 0;
 virtual HRESULT __stdcall SetFileTypeIndex    (UINT iFileType                                          ) = 0;
 virtual HRESULT __stdcall GetFileTypeIndex    (UINT* piFileType                                        ) = 0;
 virtual HRESULT __stdcall Advise              (IFileDialogEvents* pfde, DWORD* pdwCookie               ) = 0;
 virtual HRESULT __stdcall Unadvise            (WORD dwCookie                                           ) = 0;
 virtual HRESULT __stdcall SetOptions          (DWORD fos                                               ) = 0;
 virtual HRESULT __stdcall GetOptions          (DWORD* pfos                                             ) = 0;
 virtual HRESULT __stdcall SetDefaultFolder    (IShellItem* psi                                         ) = 0;
 virtual HRESULT __stdcall SetFolder           (IShellItem* psi                                         ) = 0;
 virtual HRESULT __stdcall GetFolder           (IShellItem** ppsi                                       ) = 0;
 virtual HRESULT __stdcall GetCurrentSelection (IShellItem** ppsi                                       ) = 0;
 virtual HRESULT __stdcall SetFileName         (LPCWSTR pszName                                         ) = 0;
 virtual HRESULT __stdcall GetFileName         (LPWSTR* pszName                                         ) = 0;
 virtual HRESULT __stdcall SetTitle            (LPCWSTR pszTitle                                        ) = 0;
 virtual HRESULT __stdcall SetOkButtonLabel    (LPCWSTR pszText                                         ) = 0;
 virtual HRESULT __stdcall SetFileNameLabel    (LPCWSTR pszLabel                                        ) = 0;
 virtual HRESULT __stdcall GetResult           (IShellItem** ppsi                                       ) = 0;
 virtual HRESULT __stdcall AddPlace            (IShellItem* psi, FDAP fdap                              ) = 0;
 virtual HRESULT __stdcall SetDefaultExtension (LPCWSTR pszDefaultExtension                             ) = 0;
 virtual HRESULT __stdcall Close               (HRESULT hr                                              ) = 0;
 virtual HRESULT __stdcall SetClientGuid       (REFGUID guid                                            ) = 0;
 virtual HRESULT __stdcall ClearClientData     (void                                                    ) = 0;
 virtual HRESULT __stdcall SetFilter           (IShellItemFilter* pFilter                               ) = 0;
};

interface __declspec(uuid("d57c7288-d4ad-4768-be02-9d969532d960")) __declspec(novtable) IFileOpenDialog : public IFileDialog //  2 Members
{
 public:
 virtual HRESULT __stdcall GetResults          (IShellItemArray** ppenum                                ) = 0;
 virtual HRESULT __stdcall GetSelectedItems    (IShellItemArray** ppsai                                 ) = 0;
};

I know this is a lot of material, but let me explain!  Four entities are listed above - objects
IUnknown, IModalDialog, IFileDialog, and IFileOpenDialog.  Note they are all defined as 'interfaces',
which we've shown is nothing but a C/C++ struct.  They are adorned with strange Microsoft specific
attributes such as __declspec(), uuid(), and __declspec(novtable).  These attributes support
Microsoft's Component Object Model, which, naturally, is Microsoft specific.  Otherwise though,
they are just C struct object definitions, but with a C++ slant to them.  They are also in C++
parlance 'Abstract Base Classes'.  Also note the '= 0' symbol at the end of each method, and the
method prefaced with the 'virtual' keyword. All the member functions of these interfaces are
declared above as 'pure virtual functions'.  The net effect of all this 'rigmarole' is the creation
of a 'virtual function table' in memory to which a client application will obtain a pointer.  In
that way the client will have no knowledge of the structure of the underlying code which implements
classes or interfaces exposed to the client.  If the underlying code is improved or changed in any
way, the client will be able to reap the rewards of those changes, but will otherwise not be
affected or damaged by them.  It's a wall of insulation between a dependency and a client app.

An issue that might be confusing is my description of interfaces as a virtual function table. 
These structs/interfaces don't actually contain functions as seen above, but rather pointers to
functions.  C++ does this or hides this fact in it's effort to streamline the process of calling
class member functions.  But if one were doing this Form7 application in C it would be more obvious.
For example, if one had a IFileOpenDialog* pFileOpen, and one wanted to call it's GetResults()
method as seen just above, the call might look like this in C where one is clearly routed through
the virtual function table of the class....

IFileOpenDialog*  pFileOpen  = NULL;
IShellItemArray** ppResults  = NULL

// Obtained pFileOpen and it is valid...
HRESULT hr = pFileOpen->VTbl->GetResults(pFileOpen, &ppResults);   

If you want to check this out, open ShObjIdl.h and look at all the interface descriptions, for
example, the interface description of IFileOpenDialog.  Every interface has a conditional
compilation directive at top like so....

#if defined(__cplusplus) && !defined(CINTERFACE)

...and there you will find the two IFileOpenDialog methods as I've listed them above if a C++ build
is ocurring.  If a C build is taking place you'll find this for the description of the GetResults()
method....

HRESULT (__stdcall* GetResults )(IFileOpenDialog* This, IShellItemArray** ppenum);

...and that will be the 28th of the 29 members of IFileOpenDialog.  Note the __stdcall* GetResults
term in parentheses.  That's a dead give away for a function pointer declaration. 

But getting back to those interfaces, the first entity is interface IUnknown.  This is the most
fundamental COM entity/interface, and every COM interface has the three IUnknown methods as the
first three methods of the interface.  A fancy way of saying this is that every COM interface is
polymorphic in IUnknown.  If you have any COM interface, and an IID of another interface, you can
call QueryInterface to attempt to get the latter interface pointer.  The call is dynamic and will
either succeed or fail at runtime depending on whether the class has implemented the requested
interface.  But continuing on, the next interface seen above is IModalWindow.  It only has one
member IModalWindow::Show(), but it inherits publically from IUnknown, so the 1st three members of
it's VTable (Virtual Function Table) are the three IUnknown members.  After that we have interface
IFileDialog, which inherits publically from IModalWindow, therefore adding those four functions to
its VTable, which now brings the total member function count up to 27.  Finally, we have
IFileOpenDialog, which is used in our Form7 app/tutorial, and that interface brings the member
function count up to 29 (count them if you like!).  It's all complicated, I know, but my
explanations above should help you understand it's usage, which, really, isn't any more complicated,
and perhaps even less, than usage of the older OPENFILENAME method of Form6.  Here is my
WndProc_OnFileOpen() code from the Form7 App below.... 

Code: [Select]
LRESULT WndProc_OnFileOpen(WndEventArgs& Wea)
{
 IFileOpenDialog*  pFileOpen       = NULL;    // Declaration Of This Interface Can Be Found in ShObjIdl.h, Likewise For
 IShellItem*       pShellItem      = NULL;    // iShellItem.  All The Member Functions Of These Interfaces Are Listed
 wchar_t*          ppszName        = NULL;    // There As Pure Virtual Functions, So The Compiler Won't Be Looking For
 COMDLG_FILTERSPEC ComDlgFS[3] =              // Implementations Of These Functions In This App.  And Since Only Pointers
 {                                            // To These Interfaces are Being Declared Here - the Compiler Will Be
   {L"C++ code files", L"*.cpp;*.h;*.rc"},    // Satisfied With That, As It Knows What The Virtual Function Table Looks
   {L"Executable Files", L"*.exe;*.dll"},     // Like From The Interface Declarations It Has.  Since It Knows What The
   {L"All Files",L"*.*"}                      // Virtual Function Table (VTable) Looks Like, i.e., It's Size, Names, And
 };                                           // Order Of Member Functions Within It, It Will Be Happy To Use The Pointer
 wchar_t*          pOpenFileName   = NULL;    // To Call Member Functions Of The Interfaces.  Note That The C++ Language
                                              // Does It's Best To Hide This Essential Fact, But VTables (Which Is What
 CoCreateInstance                             // Interfaces Are), Are Essentially Arrays Of Function Pointers.  So There
 (                                            // Are Two Levels Of Indirection Involved 1) The VTable/Interface Pointer
  CLSID_FileOpenDialog,                       // Returned By CoCreateInstance(), And 2) The Pointer To A Method Contained
  NULL,                                       // Within The VTable/Interface.  Note That The COMDLG_FILTERSPEC Object Is
  CLSCTX_INPROC_SERVER,                       // Also Declared/Defined In ShObjIdl.h.  Examining The Method Calls Below
  IID_IFileOpenDialog,                        // And Left Using Our IFileOpenDialog* pFileOpen, We See A Call To The
  (void**)(&pFileOpen)                        // IFileOpenDialog::GetResult Member.  The Single Parameter Is The Address
 );                                           // Of A IShellItem*, And Upon Successful Return We Have A Valid IShellItem*
 pFileOpen->SetFileTypes(3,ComDlgFS);                  // Which Can Be Used To Get The User's Chosen Filename Through The
 pFileOpen->SetTitle(L"A Single Selection Dialog");    // Call To pShellItem->GetDisplayName().  Once We Get That String
 pFileOpen->Show(0);                                   // We'll Copy It To The Memory We Allocated In WndProc_OnCreate To
 pFileOpen->GetResult(&pShellItem);                    // Store/Persist Application Data.  InvlidateRect() Will Force A
 pShellItem->GetDisplayName(SIGDN_FILESYSPATH,&ppszName);           // WM_PAINT Message And Draw That Filename String To
 pOpenFileName=(wchar_t*)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);  // The Applications's Window.  Finally, We'll Call
 wcscpy(pOpenFileName,ppszName);                                    // Release On All Our Interface Pointers - Which Are
 CoTaskMemFree(ppszName);                                           // Reference Counted, And If The Internal Reference
 pShellItem->Release();                                             // Count Declines To Zero, Which It Should Here, Logic
 pFileOpen->Release();                                              // Which Runs In The Underlying Class Should Cause The
 InvalidateRect(Wea.hWnd,NULL,TRUE);                                // Object To Self-Destruct And Unload. 
 
 return 0;
}

I keep saying it - this is all complex I know, and for me at least I have To work with these things
with lots of debug information.  I've got a #define Debug symbol in the code below which operates
on my WndProc_OnFileSave() procedure which exercises more COM code for the FileSaveDialog Class.  If
it isn't commented out it creates an Output.txt file in the app's folder, which looks like this from
one of my runs....

Code: [Select]
Entering WndProc_OnFileSave()
  CoCreateInstance(CLSID_FileSaveDialog) Succeeded!
  pFileSave = Ox000001EF55267BE8
  pFileSave->SetFileTypes() Succeeded!
  ppszName = C:\Code\Forms\Form7\stdio.h
Leaving WndProc_OnFileSave()

So you really need to debug these things.  What I've done, or using Patrice's highly regarded zTrace
tool, or a debugger is absolutely necessary.  Really useful Microsoft macros SUCCEEDED and FAILED
can help.  You can see my use of SUCCEEDED in the aformentioned File Save procedure in my code.

Finally, and leaving talk of COM behind, the Form7 app shows how to use the Windows Api to cresate
menus and dialogs without resource scripts located in *.rc files.  I do this commonly because for
one reason or another, justified or not, I just don't like resource scripts very much.  So there are
alternate ways of doing the same thing, which is nice.
*/

Code: [Select]
// cl Form7.cpp /O1 /Os /FeForm7_LibCmt.exe kernel32.lib user32.lib gdi32.lib shell32.lib ole32.lib uuid.lib
// cl Form7.cpp /O1 /Os /GR- /GS- /Gy /FeForm7_TCLib.exe TCLib.lib user32.lib gdi32.lib shell32.lib ole32.lib uuid.lib
//  9,728 Bytes VC19, TCLib,  UNICODE
// 96,768 Bytes VC19, LibCmt, UNICODE
//#define TCLib
//#define Debug
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE   
   #define _UNICODE
#endif
#include <windows.h>
#include <Shobjidl.h>
#ifdef TCLib
   #include "stdio.h"
#else
   #include <stdio.h>
#endif
#include <string.h>
#include "Form7.h"
#ifdef Debug
   FILE* fp=NULL;
#endif   


LRESULT CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)                                   // Note that this is a normal Window Procedure - not
 {                                             // a Dialog Procedure.  Since we have not used the
    case WM_CREATE:                            // Windows Dialog Engine to create the dialog box,
      EnableWindow(GetParent(hwnd),FALSE);     // but rather did it from scratch ourselves with
      return 0;                                // a CreateWindowEx() call, we use a normal Window
    case WM_COMMAND:                           // Procedure.
      switch(LOWORD(wParam))
      {
         case IDD_OK:
           MessageBox(hwnd,L"You Clicked The OK Button!",L"OK Button Click!",MB_OK);
           SendMessage(hwnd,WM_CLOSE,0,0);
           break;
         case IDD_CANCEL:
           MessageBox(hwnd,L"You Clicked The Cancel Button!",L"Cancel Button Click!",MB_OK);
           SendMessage(hwnd,WM_CLOSE,0,0);
           break;
      }
      return 0;
    case WM_CLOSE:
      EnableWindow(GetParent(hwnd),TRUE);
      DestroyWindow(hwnd);
      return 0;
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
 }

 return DefWindowProc(hwnd, msg, wParam, lParam);
}


LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
 wchar_t    szClassName[] = L"Dialog";
 wchar_t*   pOpenFileName = NULL;
 HMENU      hMenu         = NULL;
 HMENU      hSubMenu      = NULL;
 WNDCLASSEX wc;
 
 CoInitialize(NULL);  // Initialize COM Subsystem
 Wea.hIns = ((LPCREATESTRUCT)Wea.lParam)->hInstance;
 
 hMenu    = CreateMenu();                    // Creaste Menus Programatically
 hSubMenu = CreatePopupMenu();
 AppendMenu(hSubMenu,  MF_STRING,            (UINT_PTR)ID_FILE_OPEN,   L"&Open");
 AppendMenu(hSubMenu,  MF_STRING,            ID_FILE_SAVE,   L"&Save");
 AppendMenu(hSubMenu,  MF_SEPARATOR,         0,              0      );
 AppendMenu(hSubMenu,  MF_STRING,            (UINT_PTR)ID_FILE_EXIT,   L"E&xit");
 AppendMenu(hMenu,     MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&File");
 
 hSubMenu = CreatePopupMenu();
 AppendMenu(hSubMenu,  MF_STRING,            (UINT_PTR)ID_OPTIONS,     L"&Explorer");
 AppendMenu(hMenu,     MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"O&ptions");
 
 hSubMenu = CreatePopupMenu();
 AppendMenu(hSubMenu,  MF_STRING,            (UINT_PTR)IDD_ABOUT,      L"&About");
 AppendMenu(hMenu,     MF_STRING | MF_POPUP, (UINT_PTR)hSubMenu, L"&Help" );
 SetMenu(Wea.hWnd, hMenu);
 
 // Register Dialog Class
 wc.lpszClassName = szClassName,               wc.lpfnWndProc  = DlgProc;
 wc.cbSize        = sizeof(wc),                wc.style        = CS_HREDRAW | CS_VREDRAW;
 wc.cbClsExtra    = 0,                         wc.cbWndExtra   = 0;
 wc.hInstance     = Wea.hIns,                  wc.hCursor      = LoadCursor(NULL, IDC_ARROW);
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW,   wc.lpszMenuName = NULL;
 wc.hIcon         = 0;                         wc.hIconSm      = 0;
 RegisterClassEx(&wc);
 
 //And finally Allocate Memory To Store File Name from Open File Dialog for later display in WM_PAINT
 pOpenFileName=(wchar_t*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,256*sizeof(wchar_t));
 if(pOpenFileName)                           
    SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)pOpenFileName); // Store memory as part of instantiated
 else                                                                 // Window Class
    return -1;

 return 0;
}


LRESULT WndProc_OnFileOpen(WndEventArgs& Wea)
{
 IFileOpenDialog*  pFileOpen       = NULL;    // Declaration Of This Interface Can Be Found in ShObjIdl.h, Likewise For
 IShellItem*       pShellItem      = NULL;    // iShellItem.  All The Member Functions Of These Interfaces Are Listed
 wchar_t*          ppszName        = NULL;    // There As Pure Virtual Functions, So The Compiler Won't Be Looking For
 COMDLG_FILTERSPEC ComDlgFS[3] =              // Implementations Of These Functions In This App.  And Since Only Pointers
 {                                            // To These Interfaces are Being Declared Here - the Compiler Will Be
   {L"C++ code files", L"*.cpp;*.h;*.rc"},    // Satisfied With That, As It Knows What The Virtual Function Table Looks
   {L"Executable Files", L"*.exe;*.dll"},     // Like From The Interface Declarations It Has.  Since It Knows What The
   {L"All Files",L"*.*"}                      // Virtual Function Table (VTable) Looks Like, i.e., It's Size, Names, And
 };                                           // Order Of Member Functions Within It, It Will Be Happy To Use The Pointer
 wchar_t*          pOpenFileName   = NULL;    // To Call Member Functions Of The Interfaces.  Note That The C++ Language
                                              // Does It's Best To Hide This Essential Fact, But VTables (Which Is What
 CoCreateInstance                             // Interfaces Are), Are Essentially Arrays Of Function Pointers.  So There
 (                                            // Two Levels Of Indirection Involved 1) The VTable/Interface Pointer
  CLSID_FileOpenDialog,                       // Returned By CoCreateInstance(), And 2) The Pointer To A Method Contained
  NULL,                                       // Within The VTable/Interface.  Note That The COMDLG_FILTERSPEC Object Is
  CLSCTX_INPROC_SERVER,                       // Also Declared/Defined In ShObjIdl.h.  Examining The Method Calls Below
  IID_IFileOpenDialog,                        // And Left Using Our IFileOpenDialog* pFileOpen, We See A Call To The
  (void**)(&pFileOpen)                        // IFileOpenDialog::GetResult Member.  The Single Parameter Is The Address
 );                                           // Of A IShellItem*, And Upon Successful Return We Have A Valid IShellItem*
 pFileOpen->SetFileTypes(3,ComDlgFS);                  // Which Can Be Used To Get The User's Chosen Filename Through The
 pFileOpen->SetTitle(L"A Single Selection Dialog");    // Call To pShellItem->GetDisplayName().  Once We Get That String
 pFileOpen->Show(0);                                   // We'll Copy It To The Memory We Allocated In WndProc_OnCreate To
 pFileOpen->GetResult(&pShellItem);                    // Store/Persist Application Data.  InValidateRect() Will Force A
 pShellItem->GetDisplayName(SIGDN_FILESYSPATH,&ppszName);           // WM_PAINT Message And Draw That Filename String To
 pOpenFileName=(wchar_t*)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);  // The Applications's Window.  Finally, We'll Call
 wcscpy(pOpenFileName,ppszName);                                    // Release On All Our Interface Pointers - Which Are
 CoTaskMemFree(ppszName);                                           // Reference Counted, And If The Internal Reference
 pShellItem->Release();                                             // Count Declines To Zero, Which It Should Here, Logic
 pFileOpen->Release();                                              // Which Runs In The Underlying Class Should Cause The
 InvalidateRect(Wea.hWnd,NULL,TRUE);                                // Object To Self-Destruct And Unload. 
 
 return 0;
}


LRESULT WndProc_OnFileSave(WndEventArgs& Wea)
{
 COMDLG_FILTERSPEC ComDlgFS[3] =
 {
   {L"C++ code files", L"*.cpp;*.h;*.rc"},
   {L"Executable Files", L"*.exe;*.dll"},
   {L"All Files",L"*.*"}
 };
 wchar_t*         pSaveFileName = NULL;
 IFileSaveDialog* pFileSave     = NULL;
 IShellItem*      pShellItem    = NULL;
 wchar_t*         ppszName      = NULL;
 HRESULT          hr            = 0;
 
 #ifdef Debug
 fp=fopen("Output.txt","w");
 fprintf(fp,"Entering WndProc_OnFileSave()\n");
 #endif
 hr=CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileSaveDialog, (void**)(&pFileSave));
 if(SUCCEEDED(hr))
 {
    #ifdef Debug
    fprintf(fp,"  CoCreateInstance(CLSID_FileSaveDialog) Succeeded!\n");
    fprintf(fp,"  pFileSave = Ox%p\n",pFileSave);
    #endif
    hr=pFileSave->SetFileTypes(3,ComDlgFS);
    if(SUCCEEDED(hr))
    {
       #ifdef Debug
       fprintf(fp,"  pFileSave->SetFileTypes() Succeeded!\n");
       #endif
       pFileSave->SetTitle(L"A Single Selection Dialog");
       pFileSave->Show(0);   
       pFileSave->GetResult(&pShellItem);
       pShellItem->GetDisplayName(SIGDN_FILESYSPATH,&ppszName);
       pSaveFileName=(TCHAR*)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
       wcscpy(pSaveFileName,ppszName);
       #ifdef Debug
       fwprintf(fp,L"  ppszName = %s\n",ppszName);
       #endif
       CoTaskMemFree(ppszName);
       pShellItem->Release();
       InvalidateRect(Wea.hWnd,NULL,TRUE);
    }   
    pFileSave->Release(); 
 }
 else
 {
    #ifdef Debug
    fprintf(fp,"  CoCreateInstance(CLSID_FileSaveDialog) Failed!\n");
    #endif
 }   
 #ifdef Debug
 fprintf(fp,"Leaving WndProc_OnFileSave()\n"); 
 fclose(fp);
 #endif
 
 return 0;
}


LRESULT WndProc_OnAbout(WndEventArgs& Wea)               // Create Dialog Box Programatically With
{                                                        // CreateWindowEx() call. 
 wchar_t szCap2[]=L"An Example Program Showing How To";
 wchar_t szCap1[]=L"About This Program";
 DWORD dwExStyle,dwStyle,dwSty;
 HWND hDlg,hCtl,hGroup;
 MSG Msg;

 dwExStyle = WS_EX_DLGMODALFRAME | WS_EX_CONTROLPARENT;
 dwStyle   = WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE;
 dwSty     = WS_CHILD|WS_VISIBLE;
 Wea.hIns  = GetModuleHandle(L"");
 hDlg=CreateWindowEx(dwExStyle,L"Dialog",L"Dialog Window",dwStyle,175,100,400,145,Wea.hWnd,0,Wea.hIns,NULL);
 hGroup=CreateWindow(L"button",szCap1,BS_GROUPBOX|dwSty,5,5,385,100,hDlg,(HMENU)IDD_GROUP,Wea.hIns,0);
 hCtl=CreateWindow(L"button",L"OK",WS_TABSTOP|dwSty,295,25,80,25,hDlg,(HMENU)IDD_OK,Wea.hIns,0);
 hCtl=CreateWindow(L"button",L"Cancel",WS_TABSTOP|dwSty,295,65,80,25,hDlg,(HMENU)IDD_CANCEL,Wea.hIns,0);
 hCtl=CreateWindow(L"static",szCap2,dwSty,25,25,275,25,hGroup,(HMENU)-1,Wea.hIns,0);
 hCtl=CreateWindow(L"static",L"              Use Dialog Boxes",dwSty,25,42,275,25,hGroup,(HMENU)-1,Wea.hIns,0);
 hCtl=CreateWindow(L"static",L"                     By Fred",dwSty,25,72,275,25,hGroup,(HMENU)-1,Wea.hIns,0);
 while(GetMessage(&Msg, NULL, 0, 0))
 {
   if(!IsDialogMessage(hDlg,&Msg))     // This will require 'unusual' 2nd message pump in application. 
   {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
   }
 }

 return 0;
}


LRESULT WndProc_OnOptions(WndEventArgs& Wea)
{
 wchar_t szBuffer[512];
 GetCurrentDirectory(512,szBuffer);
 ShellExecute(Wea.hWnd,NULL,szBuffer,NULL,NULL,SW_SHOWNORMAL);

 return 0;
}


LRESULT WndProc_OnExit(WndEventArgs& Wea)
{
 SendMessage(Wea.hWnd,WM_CLOSE,0,0);
 return 0;
}


LRESULT fnWndProc_OnCommand(WndEventArgs& Wea)
{
 switch(LOWORD(Wea.wParam))
 {
    case ID_FILE_OPEN:
      return WndProc_OnFileOpen(Wea);
    case ID_FILE_SAVE:
      return WndProc_OnFileSave(Wea);
    case IDD_ABOUT:
      return WndProc_OnAbout(Wea);
    case ID_OPTIONS:
      return WndProc_OnOptions(Wea);
    case ID_FILE_EXIT:
      return WndProc_OnExit(Wea);
 }

 return 0;
}


LRESULT fnWndProc_OnPaint(WndEventArgs& Wea)  // In this procedure we retrieve pointer to allocated
{                                             // memory from WndProc_OnCreate, which may contain a
 wchar_t* pOpenFileName=NULL;                 // file name string if the menu selections Open or Save
 PAINTSTRUCT ps;                              // were executed.  We use GetWindowLongPtr() function
 HDC hDC;                                     // to access this pointer to allocsted memory.

 hDC=BeginPaint(Wea.hWnd,&ps);
 pOpenFileName=(wchar_t*)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
 TextOut(hDC,2,2,pOpenFileName,(int)wcslen(pOpenFileName));
 EndPaint(Wea.hWnd,&ps);

 return 0;
}


LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 wchar_t* pOpenFileName=NULL;

 pOpenFileName=(wchar_t*)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
 HeapFree(GetProcessHeap(),0,pOpenFileName);
 CoUninitialize();
 PostQuitMessage(0);

 return 0;
}


LRESULT CALLBACK WndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
 WndEventArgs Wea;

 for(size_t i=0; i<dim(EvtHdlr); i++)
 {
     if(EvtHdlr[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EvtHdlr[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
 wchar_t szClass[]=L"Non-Resource Script Dialog";
 HWND hWnd=NULL;
 WNDCLASSEX wc;
 MSG Msg;

 wc.lpszClassName = szClass,                    wc.lpfnWndProc   = WndProc;;
 wc.cbSize        = sizeof(WNDCLASSEX),         wc.style         = 0;
 wc.cbClsExtra    = 0,                          wc.cbWndExtra    = 0;
 wc.hInstance     = hInstance,                  wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
 wc.hCursor       = LoadCursor(NULL,IDC_ARROW), wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
 wc.lpszMenuName  = NULL,                       wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
 RegisterClassEx(&wc);
 hWnd=CreateWindow(szClass,L"Non-Dialog Engine Dialog Boxes",WS_OVERLAPPEDWINDOW,350,350,500,200,NULL,NULL,hInstance,NULL);
 ShowWindow(hWnd,nCmdShow);
 while(GetMessage(&Msg, NULL, 0, 0))
 {
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
 }

 return (int)Msg.wParam;
}