Firstly, apologies for not updating this more frequently. I
haven't wanted to post about things until they're bug-free, just to be
certain I'm not talking about something that won't work for you.
So far, I've shown transactional writers. In these cases,
modifications to files and directories aren't visible outside of a
transaction until that transaction commits. There is also a
second class of transactions, transactional readers, that isolate
changes made outside of a particular transaction from being
visible. This form of isolation is performed at the handle level:
within a transaction, a handle opened for read will typically be
isolated for the duration of that handle.
To illustrate, let's start with some code:
#include <windows.h>
#include <ktmw32.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <strsafe.h>
LPTSTR szKeywords[10]={_T("cat"), _T("dog"), _T("rabbit"), _T("horse"), _T("cow"), _T("chicken"),
_T("bird"), _T("mouse"), _T("donkey"), _T("camel")};
#define NUM_WORDS 10
LPTSTR szFileName=_T("testfile");
void WriteRandomWord(HANDLE hFile)
{
BOOL bRet;
DWORD dwWord, dwWrote;
dwWord = rand()%NUM_WORDS;
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
bRet = WriteFile(hFile, szKeywords[dwWord], (_tcslen(szKeywords[dwWord])+1) * sizeof(TCHAR),
&dwWrote, NULL);
SetEndOfFile(hFile);
if (bRet)
_tprintf(_T("Wrote \"%s\" to file\n"), szKeywords[dwWord]);
}
void DisplayFileContents(HANDLE hFile, LPTSTR szDesc)
{
TCHAR szReadIn[20];
DWORD dwLen;
DWORD dwRead;
BOOL bRet;
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
dwLen = GetFileSize(hFile, NULL);
bRet = ReadFile(hFile, szReadIn, dwLen, &dwRead,
NULL);
if (bRet)
_tprintf(_T("Read \"%s\" from file (%s)\n"), szReadIn, szDesc);
}
int __cdecl _tmain(int argc, TCHAR ** argv)
{
HANDLE hWriterFile;
HANDLE hReaderFile;
HANDLE hNonTransReaderFile;
HANDLE hWriterTrans;
HANDLE hReaderTrans;
DWORD dwLoop;
srand(GetTickCount());
hReaderTrans = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);
if (hReaderTrans == NULL) {
_tprintf(_T("Could not create transaction\n"));
return EXIT_FAILURE;
}
hWriterFile = CreateFile(szFileName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hWriterFile == INVALID_HANDLE_VALUE) {
_tprintf(_T("Could not create test file\n"));
return EXIT_FAILURE;
}
WriteRandomWord(hWriterFile);
CloseHandle(hWriterFile);
hNonTransReaderFile = CreateFile(szFileName,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
SetCurrentTransaction(hReaderTrans);
hReaderFile = CreateFile(szFileName,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
for (dwLoop=0;dwLoop<10;dwLoop++) {
hWriterTrans = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);
if (hWriterTrans == NULL) {
_tprintf(_T("Could not create transaction\n"));
return EXIT_FAILURE;
}
SetCurrentTransaction(hWriterTrans);
hWriterFile = CreateFile(szFileName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
WriteRandomWord(hWriterFile);
CloseHandle(hWriterFile);
CommitTransaction(hWriterTrans);
CloseHandle(hWriterTrans);
SetCurrentTransaction(NULL);
DisplayFileContents(hReaderFile, _T("transacted"));
DisplayFileContents(hNonTransReaderFile, _T("non-transacted"));
}
CloseHandle(hReaderFile);
CloseHandle(hNonTransReaderFile);
RollbackTransaction(hReaderTrans);
CloseHandle(hReaderTrans);
SetCurrentTransaction(NULL);
DeleteFile(szFileName);
return EXIT_SUCCESS;
}
Take note of a few things in this code: firstly, it has no error
checking. That's due to space constraints, not the absence of
failure conditions. I'm not advocating zero error checking in
code.
More to the point, note that the reader handle is opened once, and
closed once. It remains open the entire time. Also note
that the writing transaction commits, to make its changes visible to
other users. To make the illustration clearer, I've added a
traditional, non-transacted reader handle into the mix. Both
readers, and sometimes a writer, are open at the same time.
Here is some sample output from this application:
Wrote "cat" to file
Wrote "rabbit" to file
Read "cat" from file (transacted)
Read "rabbit" from file (non-transacted)
Wrote "mouse" to file
Read "cat" from file (transacted)
Read "mouse" from file (non-transacted)
Wrote "cat" to file
Read "cat" from file (transacted)
Read "cat" from file (non-transacted)
Wrote "cat" to file
Read "cat" from file (transacted)
Read "cat" from file (non-transacted)
Wrote "bird" to file
Read "cat" from file (transacted)
Read "bird" from file (non-transacted)
Wrote "bird" to file
Read "cat" from file (transacted)
Read "bird" from file (non-transacted)
Wrote "donkey" to file
Read "cat" from file (transacted)
Read "donkey" from file (non-transacted)
Wrote "cat" to file
Read "cat" from file (transacted)
Read "cat" from file (non-transacted)
Wrote "dog" to file
Read "cat" from file (transacted)
Read "dog" from file (non-transacted)
Wrote "chicken" to file
Read "cat" from file (transacted)
Read "chicken" from file (non-transacted)
While the view from the non-transacted reader handle changes with each
modification, the transacted reader handle remains frozen at that point
in time. To get an updated view, a transacted reader simply needs
to close the existing handle and open the file again.