A few days ago I received a mail from a reader of my blog who had an issue with a similar CreateFileTransacted code snippet which allows to manipulate file content in the scope of a transaction. In today's post, I show a little demo on how to do this.
1 using System;
2 using System.IO;
3 using Microsoft.Win32.SafeHandles;
4 using System.Runtime.InteropServices;
5
6 class TxfDemo
7 {
8 const uint GENERIC_READ = 0x80000000;
9 const uint GENERIC_WRITE = 0x40000000;
10 const uint CREATE_NEW = 1;
11 const uint CREATE_ALWAYS = 2;
12 const uint OPEN_EXISTING = 3;
13
14 [DllImport("kernel32.dll", SetLastError = true)]
15 static extern SafeFileHandle CreateFile(
16 string lpFileName,
17 uint dwDesiredAccess,
18 uint dwShareMode,
19 IntPtr lpSecurityAttributes,
20 uint dwCreationDisposition,
21 uint dwFlagsAndAttributes,
22 IntPtr hTemplateFile);
23
24 [DllImport("kernel32.dll", SetLastError = true)]
25 static extern SafeFileHandle CreateFileTransacted(
26 string lpFileName,
27 uint dwDesiredAccess,
28 uint dwShareMode,
29 IntPtr lpSecurityAttributes,
30 uint dwCreationDisposition,
31 uint dwFlagsAndAttributes,
32 IntPtr hTemplateFile,
33 IntPtr hTransaction,
34 IntPtr pusMiniVersion,
35 IntPtr pExtendedParameter);
36
37 [DllImport("ktmw32.dll", SetLastError = true)]
38 static extern IntPtr CreateTransaction(
39 IntPtr lpTransactionAttributes,
40 IntPtr uow,
41 uint createOptions,
42 uint isolationLevel,
43 uint isolationFlags,
44 uint timeout,
45 string description);
46
47 [DllImport("ktmw32.dll", SetLastError = true)]
48 static extern bool CommitTransaction(
49 IntPtr transaction);
50
51 [DllImport("ktmw32.dll", SetLastError = true)]
52 static extern bool RollbackTransaction(
53 IntPtr transaction);
54
55 [DllImport("Kernel32.dll")]
56 static extern bool CloseHandle(IntPtr handle);
57
58 static void Main()
59 {
60 string file = "txfdemo.txt";
61
62 //
63 // For demo purposes, delete the file first (if it already exists).
64 //
65 if (File.Exists(file))
66 File.Delete(file);
67
68 //
69 // Create KTM transaction.
70 //
71 IntPtr transaction = CreateTransaction(IntPtr.Zero, IntPtr.Zero, 0, 0, 0, 0, null);
72
73 //
74 // Check transaction handle.
75 //
76 if (transaction == IntPtr.Zero)
77 {
78 Console.WriteLine("Oops! KTM has taken a day off.");
79 return;
80 }
81
82 try
83 {
84 //
85 // Non-transacted.
86 //
87 //SafeFileHandle handle = CreateFile(file, GENERIC_READ | GENERIC_WRITE, 0, IntPtr.Zero, CREATE_NEW, 0, IntPtr.Zero);
88
89 //
90 // Transacted file creation (CREATE_NEW).
91 //
92 SafeFileHandle handle = CreateFileTransacted(file, GENERIC_READ | GENERIC_WRITE, 0, IntPtr.Zero, CREATE_NEW, 0, IntPtr.Zero, transaction, IntPtr.Zero, IntPtr.Zero);
93
94 //
95 // Check handle.
96 //
97 if (handle.IsInvalid)
98 Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
99
100 //
101 // Using pattern for stream operations.
102 //
103 using (FileStream fs = new FileStream(handle,FileAccess.ReadWrite))
104 {
105 using (StreamWriter sw = new StreamWriter(fs))
106 {
107 sw.WriteLine("Hello");
108 }
109 }
110
111 //
112 // To commit or not to commit?
113 //
114 Console.WriteLine("Press Y to commit");
115
116 char c = Console.ReadKey().KeyChar;
117 if (c == 'y' || c == 'Y')
118 CommitTransaction(transaction);
119 else
120 RollbackTransaction(transaction);
121 }
122 catch (Exception ex)
123 {
124 Console.WriteLine("Oops! {0}", ex.Message);
125
126 //
127 // Rollback.
128 //
129 RollbackTransaction(transaction);
130 }
131 finally
132 {
133 //
134 // Close kernel mode transaction handle.
135 //
136 CloseHandle(tx);
137 }
138 }
139 }
The sample shows how to create a file (change CREATE_NEW on line 89 if you want to open an existing file for instance) and how to hand over the SafeFileHandle to the FileStream class for further usage. Notice that the code above has some fundamental security problems concerning path sanitation of the variable "file". You might want to take a look at the SSCLI code (\clr\src\bcl\system\io\filestream.cs - FileStream::Init(...) method) for details on how to implement a much safer library that calls CreateFile(Transacted). Especially, you have to pay attention to path normalization (do you really want ".." sequences in your path?), the infamous \\.\ sequence that allows to talk to ports (e.g. COM1), the \\?\ prefix for long path names (cf. MAX_PATH), code access security (cf. FileIOPermission demand), etc.
Ignoring the warnings above (which - one day - I might resolve using a TxF aware set of classes that incorporates the required security checks), the sample will create a file called "txfdemo.txt" in the current folder, write "Hello" to it (line 104) and wait for the end-user to press Y to commit. Before you press a key (line 113), open up an instance of Windows Explorer or another command prompt and observe that (due to the isolation property of ACID transactions) the file isn't present yet. If you press Y, the file will appear out of the blue because of the transaction commit. Any other key (other than 'y' and 'Y') will rollback the transaction, which also happens in case of an exception.