Friday, December 15, 2006 8:15 AM bart

Windows Vista - Introducing TxR in C# (Part 2)

Introduction

In the previous post about TxR, we covered how to interact with the KTM (Kernel Transaction Manager) directly from managed code and how to perform transactional registry operations, illustrated by using RegDeleteKeyTransacted. In today's post, we'll bring the DTC (Distributed Transaction Coordinator) and System.Transactions into play, which will allow us to do things like this:

using (TransactionScope tx = new TransactionScope()) { DeleteKey(HKey.HKEY_CURRENT_USER, key1); DeleteKey(HKey.HKEY_CURRENT_USER, key2); }

Needless to say, because of the role of the DTC, we can perform other transactional operations in the same scope and make all of these either complete together or not. Examples include the use of TxF in the same transaction scope, or database operations on SQL Server, or MSMQ stuff, or ... well whatever you might think of.

The code

Below, you can find a piece of sample code on how you might go ahead and create a very basic TxR library. The focus of this post will be on the System.Transactions stuff you have to take care of and the low-level plumbing to involve the DTC:

1 using System; 2 using System.Runtime.InteropServices; 3 using System.IO; 4 using Microsoft.Win32; 5 using System.Transactions; 6 7 namespace TxR 8 { 9 class Program 10 { 11 #region Transactional Registry operations 12 13 [DllImport("advapi32.dll", EntryPoint="RegDeleteKeyTransactedW")] 14 static extern long RegDeleteKeyTransacted(uint hkey, [MarshalAs(UnmanagedType.LPWStr)]string subkey, RegSam sam, uint reserved, IntPtr transaction, IntPtr reserved2); 15 16 enum HKey : uint 17 { 18 HKEY_CURRENT_USER = 0x80000001 19 } 20 21 [DllImport("Kernel32.dll")] 22 static extern bool CloseHandle(IntPtr handle); 23 24 enum RegSam : uint 25 { 26 KEY_WOW64_32KEY = 0x0200, 27 KEY_WOW64_64KEY = 0x0100 28 } 29 30 [ComImport] 31 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 32 [Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")] 33 internal interface IKernelTransaction 34 { 35 void GetHandle([Out] out IntPtr handle); 36 } 37 38 static void DeleteKey(HKey hkey, string subkey) 39 { 40 if (Transaction.Current != null) 41 { 42 IKernelTransaction tx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current); 43 IntPtr txh; 44 tx.GetHandle(out txh); 45 46 if (txh == IntPtr.Zero) 47 throw new Exception(); //Q-n-D 48 49 if (RegDeleteKeyTransacted((uint)hkey, subkey, RegSam.KEY_WOW64_32KEY, 0, txh, IntPtr.Zero) != 0) 50 throw new Exception(); //Q-n-D 51 52 CloseHandle(txh); 53 } 54 else 55 { 56 // 57 // Perform non-transacted delete using Microsoft.Win32 APIs. 58 // 59 } 60 } 61 62 #endregion 63 64 static void Main(string[] args) 65 { 66 // 67 // Demo setup. 68 // 69 string key1 = "RegDeleteKeyTransactedDemo_01"; 70 string key2 = "RegDeleteKeyTransactedDemo_02"; 71 Registry.CurrentUser.CreateSubKey(key1); 72 Registry.CurrentUser.CreateSubKey(key2); 73 74 // 75 // Start the demo. 76 // 77 Console.WriteLine("Press <ENTER> to start the transaction."); 78 Console.ReadLine(); 79 80 // 81 // Make it transacted. 82 // 83 using (TransactionScope tx = new TransactionScope()) 84 { 85 // 86 // Delete the keys (transacted). 87 // 88 DeleteKey(HKey.HKEY_CURRENT_USER, key1); 89 DeleteKey(HKey.HKEY_CURRENT_USER, key2); 90 91 // 92 // Commit or rollback? 93 // 94 char c; 95 do 96 { 97 Console.WriteLine("{0} {1}.", key1, Registry.CurrentUser.OpenSubKey(key1) != null ? "still exists" : "has vanished"); 98 Console.WriteLine("{0} {1}.", key2, Registry.CurrentUser.OpenSubKey(key2) != null ? "still exists" : "has vanished"); 99 Console.Write("Commit transaction (Y/N)? "); 100 c = (char)Console.Read(); 101 } 102 while (c != 'Y' && c != 'y' && c != 'N' && c != 'n'); 103 104 if (c == 'Y' || c == 'y') 105 tx.Complete(); 106 } 107 } 108 } 109 }

The "library" is implemented in lines 11 to 62. First of all, compared to the code in the previous post, observe the omission of the KTM-related functions. Instead, we'll rely on the DTC to do the plubming behind the scenes to create the transaction. We only need to obtain a handle to the transaction to get ahead and call the RegDeleteKeyTransacted function in line 49.

A few remarks:

  • Line 14 has remained unchanged compared to the original version. It just declares our much desired demo function RegDeleteKeyTransacted.
  • Lines 16 to 19 define a simple enum which of course needs to be extended to be really useful. Basically, I just wanted to encapsulate HKEY_CURRENT_USER in a an enum that will be used further on as a parameter to indicate the root hive when calling our DeleteKey function.
  • Lines 30 to 36 are used to obtain a handle to the KTM transaction through DTC. This IKernelTransaction interface is new in Windows Vista and is used to build the bridge between DTC and the KTM for transactional Windows API. It has only one method called GetHandle described as follows in the SDK: Returns a handle that represents the transaction and can be passed as a parameter to transacted Windows APIs.
  • One line 40, we investigate whether or not we're already involved in a System.Transactions transaction (by means of a TransactionScope that is):
    • If not, we'll just perform the operation without using a transaction (line 56-59) using Microsoft.Win32 (you can supply this code yourself; I didn't want to do it here because it'd need a switch over different HKey values, which still needs to be extended).
    • Otherwise, in line 42, the IKernelTransaction::GetHandle function gets called. Luckily, the System.Transactions.TransactionInterop class has a static method GetDtcTransaction defined that can obtain the low-level DTC transaction corresponding to a System.Transactions transaction. The handle is obtained in lines 43 and 44.
      • If no valid handle is retrieved, we throw an exception, which on the caller side of our function will terminate the transaction in flight (when exceptions are thrown through the course of a TransactionScope, it rolls back).
      • Otherwise, the RegDeleteKeyTransacted function is called on line 49. Again, if something goes wrong, we throw an exception (which is done in a quick-n-dirty way for demo purposes; you'll need to provide more information through a self-written exception to do it the right way).
    • On line 52, the transaction's handle is closed using CloseHandle. Again, code needs to be polished to be ready for production and to make sure this handle is closed under all circumstances. SafeHandles might be your friends to do this (which I'll blog about later).

On the consumer side, not much has changed. Again we do create two keys for demo purposes (on lines 69 to 72). Next, in the System.Transactions.TransactionScope transaction scope (lines 83 to 106), the keys are deleted. If one of the deletions fails, an exception is thrown by our DeleteKey function and the transaction will roll back. Otherwise, execution reaches line 94, triggering the end-user decision logic, which ultimately calls Complete for commit the transaction scope if the user decides to do so.

Conclusion

All of the magic done in DeleteKey makes the use of the KTM and transactional APIs completely invisible for the managed code programmers that can rely on the comfortable System.Transactions namespace. Just what we needed.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# Windows Vista - Introducing TxR in C# (Part 1)

Friday, December 22, 2006 3:38 AM by B# .NET Blog

Introduction Last month I blogged about TxF in Windows Vista and how to use it from your own application

# rimonabantexcellence site title

Sunday, July 21, 2013 9:01 AM by rimonabantexcellence site title

Pingback from  rimonabantexcellence site title