Is MsiViewClose call required even when MsiCloseHandle is there?

Refresh

April 2019

Views

63 time

1

I open a MSI database view using MsiDatabaseOpenView followed by a call to MsiViewExecute. Then do I need to call MsiViewClose even if I do make a call to MsiCloseHandle? Will MsiCloseHandle not call MsiViewClose (or do something to close all required handles internally)?

The actual reason why I'm asking this: There is a class PMSIHANDLE which is recommended instead of manually closing the handles (destructor will call MsiCloseHandle - source code visible in VS). So when I open the view using MsiDatabaseOpenView and wrap the handle in PMSIHANDLE, I am relieved from calling MsiCloseHandle, but I must(?) call MsiViewClose!?

1 answers

3

Answer

MsiViewClose() is not required to close the handle. It is only required, if you want to run MsiViewExecute() on the same view again, which can be useful to pass different parameters to a parameterized SQL query. This is stated in the remarks of the documentation:

The MsiViewClose function must be called before the MsiViewExecute function is called again on the view, unless all rows of the result set have been obtained with the MsiViewFetch function.

In the most common use case, where you only do a single MsiViewExecute() call for a given view, you don't need to call MsiViewClose():

PMSIHANDLE pView;
UINT res = MsiDatabaseOpenViewW( hDatabase, L"SELECT * FROM `File`", &pView );
if( res == ERROR_SUCCESS )
{
    res = MsiViewExecute( pView, nullptr );
}
// Destructor of PMSIHANDLE calls MsiCloseHandle()

Side Notes

From a modern C++ point of view, PMSIHANDLE appears to be badly designed. For one, it provides no protection against accidentally copying of a handle, which would result in calling MsiViewClose() twice on the same handle. Also while the implicit conversion to MSIHANDLE* can be convenient, it is also dangerous because it makes it possible to accidentally overwrite an existing handle, without closing it first.

Here is an alternative to PMSIHANDLE based on C++11s std::unique_ptr:

// A deleter for MSIHANDLE.
struct MsiHandleDeleter
{
    // This alias enables us to actually store values of type MSIHANDLE in the unique_ptr
    // (by default it would be MSIHANDLE*).
    using pointer = MSIHANDLE;

    void operator()( MSIHANDLE h ) const { if( h ) ::MsiCloseHandle( h ); }
};

// A RAII wrapper for MSI handle. The destructor automatically closes the handle, if not 0.
using UniqueMsiHandle = std::unique_ptr< MSIHANDLE, MsiHandleDeleter >;

Usage example:

UniqueMsiHandle record{ ::MsiCreateRecord( 1 ) };
::MsiRecordSetInteger( record.get(), 1, 42 );
// Destructor takes care of calling MsiCloseHandle(), just like PMSIHANDLE.

Compared to PMSIHANDLE it's more cumbersome to use it with functions that have MSIHANDLE* out parameters, but this can easily be remedied by creating wrapper functions or classes that work with UnqiueMsiHandle.

Advantages:

  • Less ways to do things wrong.
  • Clear ownership and moveability.
  • Everyone who is used to std::unique_ptr will immediately understand the semantics of UniqueMsiHandle.