Chapter 14
OLE Container Programming Techniques
Visual Basic provides some powerful OLE features, but those features
alone aren't enough to create applications that fulfill the entire vision
of OLE. This chapter tells you how to extend Visual Basic so that you can
create OLE applications that are document-centric. In this chapter, you
learn how to do the following:
- Create applications that start when the user double-clicks on a document
file in the Windows Desktop or Explorer.
- Balance memory and performance when developing OLE container applications.
- Test a user's hardware configuration at run time.
- Locate lost OLE references that consume memory.
- Create an OLE object storage system.
- Maintain the system registry.
- Register your application in the system registry.
- Check and maintain your application's system registry entries at run
time.
Creating Document-Centric Applications
Most OLE container applications are document-centric. That is,
the user uses the document to load the application. This approach becomes
increasingly important in Windows 95, where documents are often displayed
in the Program Manager. Figure 14.1 shows an example of how a user activates
and uses a document-centric application.
Fig. 14.1
Document-centric applications
let the user think about content rather than the process of using a computer.
Although document-centric applications make life easier for computer
users, they complicate things for us programmers. In order to create an
application that is document-centric, you must consider the following programming
tasks:
- Manage memory/performance tradeoffs when initializing OLE objects.
By default, OLE objects start their applications when they are activated;
however, loading an OLE application such as Word or Excel can take a while.
- Create a system for saving and loading the OLE objects used in your
documents.
- Create a file to register the file type for your application's documents.
File types are registered in the system registry.
- Register your application when it starts. This ensures that the Registry
doesn't become outdated if your application's executable file is moved
to another directory.
The rest of this chapter explains each of these tasks in greater detail.
Remember, you can use any of these techniques to create applications that
aren't document-centric. But when you combine these techniques, you're
probably working on the type of OLE application that Bill Gates envisions
as the future of computing.
The Big Tradeoff: Memory versus Performance
An OLE container application can use some very big chunks of code. For
instance, editing a single Excel worksheet requires the entire Excel application
to be loaded in memory. If Excel isn't in memory already, the user must
wait while it is loaded (see fig 14.2).
Fig. 14.2
Small user actions can
have big effects! Activating an Excel worksheet loads the entire Excel
application in memory.
You can reduce the delay by loading the Excel application when your
application loads. However, your target system must be able to keep all
the components you need in physical memory at once. Otherwise, performance
will degrade as Windows uses virtual memory by swapping to disk
(see fig. 14.3).
Fig. 14.3
When physical memory fills up, Windows starts using virtual memory on disk. Physical memory is much faster, since it doesn't require disk access.
Determining Hardware Requirements
To balance performance against memory requirements, you need to know
the hardware configuration on which your application will be run. In a
corporate environment, it's a good idea to set up a couple of test machines
to help you tune your application. Table 14.1 provides some guidelines
for hardware requirements based on the type of OLE applications you are
using.
Table 14.1 Estimated Minimum Hardware Guidelines for OLE Container
Applications Written in Visual Basic
Item | Minimum Processor | Minimum Physical Memory (M) |
.OCX |
486 |
8 |
Jet data objects |
486 |
8 |
Small OLE object (Paint, MS Graph) |
486 |
8 |
One large OLE object (Excel worksheet) |
486 |
12 |
Two large OLE objects (Excel worksheet, Word document) |
486 |
16 |
The kitchen sink |
Pentium |
16 |
Table 14.1 shows minimum requirements based on very subjective
criteria—performance as perceived by someone who's just downed his third
cup of coffee. OLE applications will run in leaner configurations than
those shown in table 14.1, but they won't run very fast.
Table 14.1 assumes that you are running your application under Windows
95. Windows 3.x has similar requirements, although you might be able to
squeak by with less memory in a minimum configuration. If you are running
under Windows NT, add 4–8M to each minimum physical memory guideline in
table 14.1.
Checking Hardware Configurations at Run Time
Ideally, you should test your application on an average configuration
and on a "worst-case" hardware configuration. Don't make all
your decisions based on the worst-case scenario, however. Instead, create
a fall-back strategy for machines that don't have adequate processors or
memory. The following code sets a global flag if the user's hardware doesn't
meet some minimum requirements:
' Global flags section.
Public gDisableAdvanced As Boolean
Private Sub Form_Load()
Dim System As Object
Set System = CreateObject("WinAPI.Information")
If System.MeetsCriteria(486, 8) = False Then
' Set flag to disable some features.
gbDisableAdvanced = True
' Notify user.
MsgBox ("Your computer does not meet the minimum " &
_
"requirements of 8 megabytes memory and 486 or " &
_
"later processor. Some features have been disabled.")
Else
gbDisableAdvanced = False
End If
End SubThe preceding code uses the method MeetsCriteria to test a hardware
configuration at run time. The MeetsCriteria method in listing 14.1 uses
Windows API functions to get system information. The MeetsCriteria method
is part of the SYSTEM.VBP OLE server found on the companion CD. Chapter
21, "Advanced OLE Programming Techniques," describes SYSTEM.VBP
more completely.
Listing 14.1 Testing Hardware Configuration (SYSTEM.VBP)
#If Win16 Then
Private Declare Function GlobalCompact Lib "Kernel" _
(ByVal dwMinFree As Long) As Long
Private Declare Function GetWinFlags Lib "Kernel" () As
Long
Const WF_CPU286 = &H2
Const WF_CPU386 = &H4
Const WF_CPU486 = &H8
#Else
Private Declare Sub GetSystemInfo Lib "kernel32" _
(lpSystemInfo As SYSTEM_INFO)
Private Declare Sub GlobalMemoryStatus Lib "kernel32"
_
(lpBuffer As MEMORYSTATUS)
' Type declaration for system information.
Private Type SYSTEM_INFO
dwOemId As Long
dwPageSize As Long
lpMinimumApplicationAddress As Long
lpMaximumApplicationAddress As Long
dwActiveProcessorMask As Long
dwNumberOfProcessors As Long
dwProcessorType As Long
dwAllocationGranularity As Long
dwReserved As Long
End Type
' Type declaration for system information.
Private Type MEMORYSTATUS
dwLength As Long ' sizeof(MEMORYSTATUS)
dwMemoryLoad As Long ' percent of memory in use
dwTotalPhys As Long ' bytes of physical memory
dwAvailPhys As Long ' free physical memory bytes
dwTotalPageFile As Long ' bytes of paging file
dwAvailPageFile As Long ' free bytes of paging file
dwTotalVirtual As Long ' user bytes of address space
dwAvailVirtual As Long ' free user bytes
End Type
#End If
' Checks if a system meets processor and memory
' hardware requirements.
' iProcessor is a three-digit number: 286, 386, or 486
' iMemory is the number of megabytes of physical memory required.
Public Function MeetsCriteria(iProcessor As Integer, _
iMemory As Integer) As Boolean
Dim iAvailableMemory As Integer, lWinFlags As Long
Dim bProcessor As Boolean
#If Win16 Then
lWinFlags = GetWinFlags()
#Else
Dim SysInfo As SYSTEM_INFO
GetSystemInfo SysInfo
lWinFlags = SysInfo.dwProcessorType
#End If
Select Case iProcessor
Case 286
' Windows 3.1 won't run on earlier machines, so True.
bProcessor = True
Case 386
' If meets critieria, set to True.
#If Win16 Then
If lWinFlags >= WF_CPU386 Then bProcessor = True
#Else
If lWinFlags >= 386 Then bProcessor = True
#End If
Case 486
#If Win16 Then
If lWinFlags And WF_CPU486 Then bProcessor = True
#Else
If lWinFlags >= 486 Then bProcessor = True
#End If
Case 586
#If Win16 Then
' There is no test for 586 under Win16,
' so test for 486 -- probably
' better than returning an error.
If lWinFlags And WF_CPU486 Then bProcessor = True
#Else
If lWinFlags >= 586 Then bProcessor = True
#End If
End Select
' Win16 and Win32 have different ways of getting
' available physical memory.
#If Win16 Then
' Get available physical memory.
iAvailableMemory = GlobalCompact(0) _
/ (1024000)
#Else
Dim MemStatus As MEMORYSTATUS
GlobalMemoryStatus MemStatus
iAvailableMemory = MemStatus.dwTotalPhys / (1024000)
#End If
' Combine results of two tests: True And True = True.
MeetsCriteria = bProcessor And iAvailableMemory >= iMemory
End Function
Loading Object Applications at Startup
To reduce the amount of time it takes to activate an OLE object for
editing, load the object's application at startup. For example, the following
code loads the Excel application if it is not already running:
Private Sub Form_Load()
Dim System As Object
Set System = CreateObject("WinAPI.Information")
If System.IsRunning("Microsoft Excel") = False Then
' Start Excel minimized.
Shell "c:\excel\excel.exe", 6
End If
End Sub
The preceding code uses the IsRunning method to determine whether Microsoft
Excel is running. The IsRunning method in listing 14.2 uses Windows API
functions to check whether the application is loaded.
Listing 14.2 Checking Whether an Application Is Running (SYSTEM.VBP)
#If Win16 Then
Private Declare Function GetNextWindow Lib "User" _
(ByVal hWnd As Integer, ByVal wFlag As Integer) As Integer
Private Declare Function GetActiveWindow Lib "User" ()
As Integer
Private Declare Function GetWindowText Lib "User" _
(ByVal hWnd As Integer, ByVal lpString As String, _
ByVal aint As Integer) As Integer
Private Declare Function APIFindWindow Lib "User" _
Alias "FindWindow" _
(ByVal lpClassName As Any, ByVal lpWindowName _
As Any) As Integer
#Else
Private Declare Function GetNextWindow Lib "user32" _
Alias "GetNextQueueWindow" _
(ByVal hWnd As Long, ByVal wFlag As Integer) _
As Long
Private Declare Function GetActiveWindow Lib "user32"
_
() As Long
Private Declare Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" (ByVal hWnd As Long, _
ByVal lpString As String, ByVal cch As Long) _
As Long
Private Declare Function APIFindWindow Lib "user32" _
Alias "FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
#End If
Const GW_HWNDNEXT = 2
Const SW_SHOW = 5
Public Function IsRunning(strAppName) As Boolean
#If Win16 Then
Dim hWnd As Integer, hWndStop As Long, hWndNext _
As Integer, iLen As Integer
#Else
Dim hWnd As Long, hWndStop As Long, hWndNext _
As Long, iLen As Long
#End If
Dim strTitle As String * 80
' Get a handle to the active window (first in task list).
hWnd = GetActiveWindow()
hWndStop = hWnd
' Loop until you reach the end of the list.
Do
' Get the next window handle.
hWndNext = GetNextWindow(hWnd, GW_HWNDNEXT)
' Get the text from the window's caption.
iLen = GetWindowText(hWndNext, strTitle, Len(strTitle))
If iLen Then
' If found, return True.
If InStr(strTitle, strAppName) Then
IsRunning = True
Exit Function
End If
End If
hWnd = hWndNext
Loop Until hWnd = hWndStop
' Not found, so return False.
IsRunning = False
End Function
The OLE control's AppIsRunning property returns True only if the object's
application is loaded and the object is active for editing. The
IsRunning method in SYSTEM.VBP tells you whether or not an application
is running, regardless of the OLE control's state.
Loading Object Applications on Demand
By default Visual Basic loads an OLE object's application when the object
is activated for editing. The OLE control's AutoActivate property controls
how the user activates the object. For information on the AutoActivate
property, see "Controlling Object Activation"
in Chapter 13.
If the object's application is already running, Visual Basic simply
increments the Windows reference count to the running application. Visual
Basic deincrements the Windows reference count for an object's application
when the form containing the object is closed. The object's application
continues running until the user closes the application manually, or until
the reference count goes to zero. Figure 14.4 shows how OLE loads and unloads
an OLE object's application used on a Visual Basic form.
Fig. 14.4
OLE controls when an
object's application starts and quits.
Table 14.2 describes the steps shown in figure 14.4.
Table 14.2 How OLE Loads,Activates, and Deactivates OLE Object Applications
Step |
User Action |
OLE Action |
Object's Application Reference Count |
1. |
Activates object on form. |
OLE loads object's application if it is not already running. |
References + 1 |
2. |
None. |
OLE object's application loads. |
References + 1 (if application is visible) |
3. |
User deactivates object. |
Updates object, closes object. |
No change. |
4. |
Form unloads. |
Decrement reference count. |
References – 1 |
5. |
None. |
Close object's application. |
References = 0 |
Whether or not an object's application quits when a form unloads is
determined by the reference count of that application. The application
will not quit if the reference count is not zero. There are three main
reasons an object's application won't close after a form unloads:
- The application is visible. Under OLE, the application's reference
count is incremented by 1 just for being visible. Visible applications
are assumed to be under user control. This prevents OLE from closing an
application that a user may be using.
- Another object has a reference to the application. A single application
may be shared by many objects. The application won't close until all the
objects are done using it.
- The application is not following the OLE standard. It is possible
for invisible applications to remain loaded after all objects have released
their references to the application. The application may remain loaded
until the user exits Windows.
Raiders of the Lost OLE Reference
In the movie Raiders of the Lost Ark, they never explained how
the Ark got lost in the first place—the point of the movie was that they
wanted it back. The same thing is true of OLE references. Applications
may or may not track their references correctly. When they don't, you need
to find them. Lost references to objects keep the object's application
loaded in memory. This consumes precious Windows resources and can affect
performance or require the user to reboot his or her system.
Excel is a good example of an application that doesn't always track
its references. For example, the following steps cause a "lost reference"
to the Excel application, leaving it loaded in memory with no easy way
to shut it down:
- Start Excel using the CreateObject function. The following procedure
starts the Excel application invisibly and returns a reference to the OLE
object for the Excel application:
- Sub CreateReference()
- Dim objExcel As Object
- Set objExcel = CreateObject("excel.application")
- End Sub
- Run the CreateReference procedure. The CreateObject function starts
Excel and returns a reference to the object. But when the object variable
objExcel loses scope at the end of the procedure, Excel remains loaded
in memory.
- Notice that Excel does not appear on the taskbar—there is no obvious
way for you to activate the Excel application so that you can shut it down!
Since you know that you just started Excel, this situation is easy to
fix. To find Excel after you've lost its reference, follow these steps:
- Use the GetObject function to obtain a reference to the running instance
of Excel.
- Set the returned object's Visible property to True. The following
procedure finds a lost reference to the Excel application object and makes
the application visible:
- Sub FindLostReference()
- Dim objExcel As Object
- Set objExcel = GetObject(, "excel.application")
- objExcel.Visible = True
- End Sub
You can avoid this specific situation by using object variables that
are global in scope and by being fastidious about closing instances of
Excel that you've started in your application.
However, you can't always account for the behavior of other applications
or unanticipated interruptions of your own application. For these cases,
you need to be able to find all hidden instances of applications running
on a system; and to do that, you need to make some Windows API calls. The
MakeVisible method in listing 14.3 makes all of the applications on your
system visible using common window-manipulation functions from the Windows
API. Listing 14.3 shows the declarations for the API functions used in
the MakeVisible method.
Listing 14.3 Making Hidden Application Instances Visible (SYSTEM.VBP)
' Makes all applications visible.
Public Sub MakeVisible()
#If Win16 Then
Dim hWnd As Integer, hWndFirst As Integer, iTemp _
As Integer, iLen As Integer
#Else
Dim hWnd As Long, hWndFirst As Long, iTemp As Long, _
iLen As Long
#End If
Dim strTitle As String * 80
' Get a handle to the active window (first in task list).
hWnd = GetActiveWindow()
hWndFirst = hWnd
' Loop until you reach the end of the list.
Do
iLen = GetWindowText(hWnd, strTitle, Len(strTitle))
If iLen Then
iTemp = ShowWindow(hWnd, SW_SHOW)
End If
' Get the next window handle.
hWnd = GetNextWindow(hWnd, GW_HWNDNEXT)
Loop Until hWnd = hWndFirst
End Sub
Don't close an OLE object's application in code unless you are absolutely
sure no other objects on the system have references to that application.
Doing so can lose data and disrupt file integrity. It is much safer to
allow the user to close an application.
Creating an OLE Storage System
By default, Visual Basic stores OLE objects in the form that contains
them. For linked objects, Visual Basic stores the image of the object and
the source of the object. For embedded objects, Visual Basic stores the
image of the object and the object's data.
When you compile a Visual Basic form, the object's image, source, or
data is written into your application's executable file. This has the affect
of "freezing" the initial state of the object. Changes you make
to embedded objects at run time are lost when the application closes. Changes
to linked objects are preserved, but the startup image of the object doesn't
reflect those changes.
As mentioned in Chapter 13, you save OLE objects to files using the
SaveToFile method and read OLE files using the ReadFromFile method. The
following code shows using a form's Load event procedure to update an OLE
object when the form is displayed:
Option Explicit
Const OLE_FILE = "c:\foo.ole"
Dim miOLE_File As Integer
Private Sub Form_Load()
miOLE_File = FreeFile()
Open OLE_FILE For Binary As miOLE_File
oleObject.ReadFromFile miOLE_File
End Sub
This code shows using the Unload event to save changes to the OLE object
in a file:
Private Sub Form_Unload(Cancel As Integer)
oleObject.SaveToFile miOLE_File
Close miOLE_File
End Sub
Since the file you use with ReadFromFile and SaveToFile is open for
Binary access, you don't have to close the file between loading and saving
the OLE object. Binary access files support read/write access.
Storing Multiple Objects in a Single File
The Load and Unload event procedures in the preceding section support
access to only one OLE object per file. To store more than one object in
a single file, repeat the SaveToFile method for each OLE object in your
application. The following cmdSave event procedure shows how to save multiple
OLE objects in a control array to a single file:
Private Sub cmdSave_Click()
' Get a file number.
iFileNum = FreeFile()
' Open the file for Binary access.
Open "NEW.OLE" For Binary As iFileNum
' For each object in the oleObjects control array
For Each oleControl In oleObjects
' Save the object to the open file.
oleControl.SaveToFile iFileNum
Next oleControl
' Close the file when done.
Close iFileNum
End Sub
The ReadFromFile and SaveToFile methods don't correctly reposition the
file pointer, so the Loc function is useless when saving or loading OLE
objects.
Loading Multiple Objects from a Single File
The process of loading OLE objects from an existing file is similar
to that of saving them as described in the preceding section. Use the OLE
object's ReadFromFile method, as shown in the following cmdLoad_Click event
procedure:
Private Sub cmdLoad_Click()
' Get a file number.
iFileNum = FreeFile()
' Open the file for Binary access.
Open "NEW.OLE" For Binary As iFileNum
' For each object in the oleObjects control array
For Each oleControl In oleObjects
' Save the object to the open file.
oleControl.ReadFromFile iFileNum
Next oleControl
' Close the file when done.
Close iFileNum
End Sub
The preceding code is simple enough, but it makes one major assumption:
that the number of OLE objects contained in the file matches the number
of controls in the oleObjects control array. To load an unknown number
objects from a file, you must add a loop that contains the ReadFromFile
method and poll for error number 31037. Listing 14.4 shows how to load
all the objects from a file.
Listing 14.4 Loading All the Objects from a File (STORE.VBP)
Private Sub mnuOpen_Click()
' Get a file name.
dlgFile.ShowOpen
' If the file name if valid, open it.
If Len(Dir(dlgFile.filename)) Then
mstrFileName = dlgFile.filename
' Get a file number.
iFileNum = FreeFile()
' Open a file to read.
Open mstrFileName For Binary As iFileNum
' Update the form caption.
Me.Caption = mstrFileName
' Enable the Save menu.
mnuSave.Enabled = True
Else
' Do nothing.
Exit Sub
End If
' Clear the existing OLE control array.
For Each oleControl In oleObjects
' Keep the control with Index = 0.
If oleControl.Index Then
Unload oleControl
End If
Next oleControl
' Turn on error handling.
On Error GoTo cmdLoad_Err
' Repeat until an error indicating you've reached
' the last object in the file, which causes an error.
Do
' Load an object into the control.
oleObjects(Index).ReadFromFile iFileNum
' Increment the count.
Index = Index + 1
' Create another control in the control array.
Load oleObjects(Index)
' Make the object visible
oleObjects(Index).Visible = True
' Reposition each object.
oleObjects(Index).TOP = oleObjects(Index - 1).TOP _
+ oleObjects(Index - 1).Height
Loop
cmdLoad_Err:
If Err = 31037 Then
' Unload the last control since it's empty.
Unload oleObjects(Index)
' Update the display.
Me.Refresh
' Close the file
Close iFileNum
' Update the number of objects.
mIndex = Index
Exit Sub
Else
MsgBox "An unhandled error occured: " & _
Err & Error
End If
End Sub
Listing 14.4 uses a rather simple system for positioning OLE objects
on a form—it simply lays them out end to end. Ideally, users should be
able to drag objects to new positions and have the program record those
positions in the file. This gets tricky, because you must store dissimilar
data in the same file. It's even further complicated by the fact that ReadFromFile
and SaveToFile don't reposition the file pointer. There is no easy way
to tell where in a file OLE objects begin and end. The easiest solution
to this problem is to store position information in a separate, random-access
file.
Maintaining the System Registry
Applications use the Registry to store the following information:
- The name of the executable file that is associated with a given file
name extension.
- The command line to execute or Dynamic Data Exchange (DDE) messages
to send when the user opens a file from Windows.
- The command line to execute or DDE messages to send when the user
prints a file from Windows.
- Initialization data for the application (formerly stored in an .INI
file under Windows 3.x).
- Details about the implementation of OLE if the application is an OLE
server.
You can edit the system registry directly using the Registry Editor
(see fig. 14.5). To run the Registry Editor, use this command line:
regedit
Fig. 14.5
The Windows 95 Registry
Editor displays a hierarchical list of the system registry.
When running the Registration Info Editor under Windows 3.x, you must
use the /v switch if you want to see all levels of detail.
The Structure of the System Registry
The data in the system registry is hierarchical..Below HKEY_CLASSES_ROOT
are all the entries for registered applications and file types. Below them
are sub-entries for the types of actions each application supports. These
entries and sub-entries are referred to as registration keys. Most
keys have a setting, as shown by the system registry entries for the Paint
accessory included with Windows 95 (see listing 14.5).
Listing 14.5 Registry Entries for Paint (PAINT.REG)
REGEDIT4
*****************************************************************
Register file types for the Paint application.
[HKEY_CLASSES_ROOT\.bmp]
@="Paint.Picture"
[HKEY_CLASSES_ROOT\.bmp\ShellNew]
"NullFile"=""
[HKEY_CLASSES_ROOT\.pcx]
@="Paint.Picture"
*****************************************************************
Register the command lines that Windows uses for opening and
printing Paint documents.
[HKEY_CLASSES_ROOT\Paint.Picture]
@="Bitmap Image"
[HKEY_CLASSES_ROOT\Paint.Picture\shell]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\open]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\open\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" \"%1\""
[HKEY_CLASSES_ROOT\Paint.Picture\shell\print]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\print\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" /p \"%1\""
[HKEY_CLASSES_ROOT\Paint.Picture\shell\printto]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\printto\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" /pt \"%1\"
\
"%2\" \"%3\" \"%4\""
[HKEY_CLASSES_ROOT\Paint.Picture\DefaultIcon]
@="C:\\Progra~1\\Access~1\\MSPAINT.EXE,1"
[HKEY_CLASSES_ROOT\Paint.Picture\Insertable]
@=""
[HKEY_CLASSES_ROOT\Paint.Picture\protocol]
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing]
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\verb]
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\verb\0]
@="&Edit"
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\server]
@="C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE"
[HKEY_CLASSES_ROOT\Paint.Picture\CLSID]
@="{D3E34B21-9D75-101A-8C3D-00AA001A1652}"
*****************************************************************
Register the class ID for the application.
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}]
@="Paintbrush Picture"
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}\
TreatAs]@="{D3E34B21-9D75-101A-8C3D-00AA001A1652}"
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}
\Ole1Class]@="PBrush"
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}
\ProgID]@="PBrush"
(other registration entries omitted here)
Because you can't create all types of OLE applications using Visual
Basic, you don't need to worry about all the different keys that can appear
for each application. In fact, listing 14.5 omits many lines that register
features that are either handled automatically or are unavailable through
Visual Basic. The keys that are of interest to Visual Basic programmers
are shown in table 14.3.
Table 14.3 Registry Keys
Key |
Example and Description |
ProgID |
[HKEY_CLASSES_ROOT\.bmp] @="Paint.Picture"
Identifies the application within the system registry. The programmatic
ID Paint.Picture is used in all the subsequent keys. The setting for ProgID
is the text that appears in the standard OLE Insert Object dialog box. |
FileType |
[HKEY_CLASSES_ROOT\.bmp]
@="Paint.Picture"
Associates a file type with the ProgID. For instance, double-clicking
on a .BMP file in Windows loads the file in Paint. |
CLSID |
[HKEY_CLASSES_ROOT\Paint.Picture\CLSID]
@="{D3E34B21-9D75-101A-8C3D-00AA001A1652}"
Identifies the application to OLE. OLE uses the class ID (the very
long number in braces) internally when starting an OLE object's application. |
protocol |
Identifies the OLE services that the application supports. |
StdFileEditing |
Identifies the OLE file editing services that the application supports. |
server |
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\server]
@="C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE"
Identifies the application's .EXE file to use as the server for OLE
editing services. |
verb |
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\verb\0]
@="&Edit"
Identifies the OLE verbs the application supports. These are the verbs
listed in the object's pop-up menu when the OLE object is right-clicked. |
shell |
Lists the Windows shell services that the application supports. Shell
services include the Windows Explorer and Desktop. |
open\command |
[HKEY_CLASSES_ROOT\Paint.Picture\shell\open\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" \"%1\"" |
print\command |
[HKEY_CLASSES_ROOT\Paint.Picture\shell\print\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" /p \"%1\""
Provides the command line Windows uses when the Explorer prints a file
with a file type associated with this application's programmatic identifier
(see the FileType key in this table). |
Changing Registry Entries and Seeing the Effects
You can edit system registry entries directly using the Registry Editor,
then save those entries to a separate .REG file. This is useful when creating
.REG files for your own applications.
To use the Registry Editor to change the value of an entry, follow these
steps:
- Select an item to edit by clicking on it in the list box.
- Type the new key value in the Value text box.
To use the Registry Editor to save an entry to an .REG file, follow
these steps:
- Select an item to save by clicking on it in the list box. The Registry
Editor saves all the entries subordinate to the entry that you select.
To save the entire database to a .REG file, select the root entry, \, as
shown in figure 14.6.
Fig. 14.6
Select the root entry
to save the entire Registry to a file.
- Choose Registry, Export Registration File. The Registry
Editor displays the Export Registry File dialog box (see fig 14.7).
Fig. 14.7
Registration files use
the .REG file type by convention.
- Type the name of the file to create and click OK. The Registry Editor
saves the entry and all its subordinate entries in a text format file.
Deleting Registry Entries
You might want to delete entries manually from your Registry after you
delete an OLE application from your system. The old entries don't do any
direct harm, but they can clutter up OLE dialog boxes with invalid entries.
To use the Registry Editor to delete a key, follow these steps:
- Select the item to delete from the Registration Info Editor list
box.
- Choose Edit, Delete.
Registering Your Application
Under Windows 3.x, you ran most Visual Basic applications by double-clicking
on the application's icon in the Windows Program Manager. You can run document-centric
applications by double-clicking on an icon on the Windows 95 desktop or
on the file name in the Windows Explorer (see fig. 14.8).
Fig. 14.8
Document-centric applications
can start directly from the documents that they create.
You should register your application in the system registry if you want
your users to be able to open application documents directly through the
Windows Desktop or Explorer.
There are currently two system registration tasks that are of interest
to Visual Basic programmers:
- Associating a file type with your application.
- Identifying the icon to display for application-specific documents.
The following sections describe these tasks in greater detail.
Registering a File Type for Your Application
The Registry contains FileType keys that identify specific file
extensions as belonging to specific applications. These keys enable the
Windows shell functions to start to load the file when the user double-clicks
on the file name in the Explorer or a shortcut for the file on the Windows
Desktop.
To associate a file type with your application, follow these steps:
- Create a registration file for your application. The following file
registers STORE.EXE and creates associations for loading and printing the
file:
- REGEDIT4
- *****************************************************************
- Register file types for the STORE application.
- [HKEY_CLASSES_ROOT\.ole]
- @="Store.Application"
- [HKEY_CLASSES_ROOT\.cdoc\ShellNew]
- "NullFile"=""
- *****************************************************************
- Register the command lines that Windows uses for opening and
- printing STORE documents.
- [HKEY_CLASSES_ROOT\Store.Application]
- @="OLE Storage Demo"
- [HKEY_CLASSES_ROOT\Store.Application\shell]
- [HKEY_CLASSES_ROOT\Store.Application\shell\open]
- [HKEY_CLASSES_ROOT\Store.Application\shell\open\command]
- @="\"C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE\" \"%1\""
- [HKEY_CLASSES_ROOT\Store.Application\shell\print]
- [HKEY_CLASSES_ROOT\Store.Application\shell\print\command]
- @="\"C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE\" /p \"%1\""
- [HKEY_CLASSES_ROOT\Store.Application\shell\printto]
- [HKEY_CLASSES_ROOT\Store.Application\shell\printto\command]
- @="\"C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE\" /pt
- \"%1\" \"%2\" \"%3\" \"%4\""
- [HKEY_CLASSES_ROOT\Store.Application\DefaultIcon]
- @="C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE,1"
- Merge the registration file. The following command line merges the
file STORE.REG with the system registry. The -s switch merges the changes
silently—no confirmation dialog box is shown.
- regedit -s store.reg
- After merging the file, you can view the changes using the Registry
Editor (see fig. 14.9).
Fig. 14.9
The Registry entries
for STORE.REG.
Registering Document Icons
Registering a file type for your application causes Windows to use your
application's default icon for program items created from files with the
specified type (see fig. 14.10).
Fig. 14.10
When you add a program
item with the file type .OLE, Windows 95 displays it with the icon from
STORE.EXE.
Visual Basic applications have always used the first icon stored in
the executable file as their document icon. You can't use other icons stored
in the executable as document icons.
Check Registry Entries at Startup
If your application has an entry in the system registry, it should check
its registered entries at startup to verify that the executable's file
path has not changed since the application was last run. If the executable's
current file path does not match the registered file path, the application
should update its registration.
Visual Basic does not provide built-in support for checking system registry
entries at startup, so you must use Windows API functions. Table 14.4 lists
the functions Windows provides to help you maintain the system registry.
Table 14.4 Windows System Registry Functions
Function |
Use |
RegCloseKey |
Closes a key after opening it with RegOpenKey. |
RegCreateKeyA |
Creates a new key. |
RegDeleteKey |
Deletes an existing key. |
RegEnumKey |
Gets the next subkey of a specified key. |
RegOpenKey |
Opens a key. |
RegQueryValueA |
Retrieves the value setting of a key as a text string. |
RegSetValueA |
Sets the value of a key. |
There are many more registration API functions than are shown in table
14.4. The ones listed in the table are compatible with Windows 3.x and
provide the capability to check and update Registry entries as discussed
in this chapter.
To check a registered key in the system registry, follow these steps:
- Use RegOpenKeyA to open the registration key that
contains path and file name information, such as store.application\shell\open.
- Use RegQueryValueA to return the value of the subentry containing
path and file name information.
- Compare the returned value to the application's current path and
file name. If the two values don't match, use RegSetValueA to change the
registered value of the subentry.
- Use RegCloseKey to close the registration key opened in step 1.
The CheckRegistrationEntry procedure in listing 14.6 shows the declarations
for the system registry Windows API functions, and demonstrates how to
check the registry entry for the STORE.VBP application found on the companion
CD.
Listing 14.6 Checking Registry Entries at Startup (STORE.VBP)
Option Explicit
' Registry APIs used to check entry.
#If Win16 Then
Declare Function RegOpenKey Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal LPCSTR As String, _
HKeyOut As Long) _
As Long
Declare Function RegCloseKey Lib "Shell" _
(ByVal HKeyIn As Long) _
As Long
Declare Function RegQueryValue Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal KeyValue As String, _
KeyValueLen As Long) _
As Long
Declare Function RegSetValue Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal lType As Long, _
ByVal strNewValue As String, _
ByVal lIngnored As Long) _
As Long
Declare Sub RegDeleteKey Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal SubKeyName As String)
#Else
Declare Function RegOpenKey Lib "advapi32" _
Alias "RegOpenKeyA" _
(ByVal HKeyIn As Long, _
ByVal LPCSTR As String, _
HKeyOut As Long) _
As Long
Declare Function RegOpenKeyEx Lib "advapi32" _
Alias "RegOpenKeyExA" _
(ByVal HKeyIn As Long, ByVal LPCSTR _
As String, ByVal dwRes _
As Long, ByVal dwAccess _
As Long, HKeyOut As _
Long) As Long _
Declare Function RegCloseKey Lib "advapi32" _
(ByVal HKeyIn As Long) _
As Long
Declare Function RegQueryValue Lib "advapi32" _
Alias "RegQueryValueA" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal KeyValue As String, _
KeyValueLen As Long) _
As Long
Declare Function RegSetValue Lib "advapi32" _
Alias "RegSetValueA" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal lType As Long, _
ByVal strNewValue As String, _
ByVal lIngnored As Long) _
As Long
Declare Function RegDeleteKey Lib "advapi32" _
Alias "RegDeleteKeyA" _
(ByVal HKeyIn As Long, _
ByVal SubKeyName As String) _
As Long
#End If
#If Win16 Then
Const HKEY_CLASSES_ROOT = &H1
#Else
Const HKEY_CLASSES_ROOT = &H80000000
Const HKEY_CURRENT_USER = &H80000001
Const HKEY_LOCAL_MACHINE = &H80000002
Const HKEY_USERS = &H80000003
Const HKEY_PERFORMANCE_DATA = &H80000004
#End If
Public Const ERROR_SUCCESS = 0
Sub CheckRegistrationEntry(strSearchKey As String)
Dim hkroot As Long, lError As Long, lLen As Long
Dim strKeyID As String, strKeyDesc As String
Dim strAppName As String
' Get current application path and file name.
strAppName = Chr(34) & App.Path & "\" & App.EXEName
& _
".EXE" & _
Chr(34) & Chr(32) & Chr(34) & "%1" & Chr(34)
lLen = 255
' Specify subentry value to check.
strKeyID = "command"
' Initialize key description (value returned by RegQueryValue).
strKeyDesc = String(lLen, 0)
' Get the registry entry for the Open key.
lError = RegOpenKey(HKEY_CLASSES_ROOT, strSearchKey & _
"\shell\open", hkroot)
' Get the value of the entry.
lError = RegQueryValue(hkroot, strKeyID, strKeyDesc, lLen)
' If RegOpenKey or RegQueryValue return an error,
' display a message and end.
If lError Then
MsgBox "Couldn't find registry entry. Please reinstall"
& _
"the application."
End
End If
' Check the value against the current installation.
If Left(strKeyDesc, lLen - 1) <> strAppName Then
' If it doesn't match, change the registered value.
lError = RegSetValue(hkroot, strKeyID, 1, strAppName, 0)
End If
' If RegOpenKey or RegQueryValue return an error,
' display a message and end.
If lError Then
MsgBox "Couldn't update registry entry."
End
End If
' Close the registration key.
lError = RegCloseKey(hkroot)
End Sub
From Here...
For more information on OLE container applications, see the following
chapters:
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a
Simon and Schuster Company.