dahall / WinClassicSamplesCS

A duplication in C# of the "Windows-classic-samples" using Vanara libraries.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ShellServices in example CloudMirror not working

aupous opened this issue · comments

I have ran example CloudMirror, I found that the program can create Placeholder file, but some ShellServices didn't work. I cannot see file thumbnail, and TestCommand not showed neither. What is the problem?

Do those capabilities work when you run the corresponding C++ sample from Microsoft?

Hi @dahall , thank you for your reply. Yes they work well on C++ sample from Microsoft.

Ok. I'll check into the code I converted to see where I went wrong.

Since you have the code there, can you try the following changes to ShellService.InitAndStartServiceTask():

public static void InitAndStartServiceTask()
{
   var thread = new Thread(() =>
   {
      CoInitializeEx(default, COINIT.COINIT_APARTMENTTHREADED).ThrowIfFailed();

      uint cookie;
      var thumbnailProvider = new ThumbnailProvider();
      CoRegisterClassObject(typeof(ThumbnailProvider).GUID, thumbnailProvider, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();

      var contextMenu = new TestExplorerCommandHandler();
      CoRegisterClassObject(typeof(TestExplorerCommandHandler).GUID, contextMenu, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();

      var customStateProvider = new CustomStateProvider();
      CoRegisterClassObject(typeof(CustomStateProvider).GUID, customStateProvider, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();

      var uriSource = new UriSource();
      CoRegisterClassObject(typeof(UriSource).GUID, uriSource, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();

      using var dummyEvent = CreateEvent(null, false, false);
      if (dummyEvent.IsInvalid)
         Win32Error.ThrowLastError();
      CoWaitForMultipleHandles(COWAIT_FLAGS.COWAIT_DISPATCH_CALLS, INFINITE, 1, new[] { (IntPtr)dummyEvent }, out _);
   });
   thread.SetApartmentState(ApartmentState.STA);
   thread.Start();
}

I have tried your code above but it doesn't work

It doesn't build, or it fails to fix the problems you originally reported?

The program built successfully, I can run it then placeholder files were created. But thumbnail and custom command on right click not shown, just like before

Hi @dahall , did you have anything updated? I try to understand why it didn't work but I have little experience with C# and C++ so I can't

I've been working on it and have made lots of changes, but the sample still fails. I'll keep trying.

To see if it was the base library calls, I enhanced the testing over on Vanara. There are working examples of the Cloud API there that may be of use: https://github.com/dahall/Vanara/tree/master/UnitTests/PInvoke/CldApi

CoRegisterClassObject function (combaseapi.h) - Win32 apps | Microsoft Learn

At startup, a multiple-use EXE object application must create a class object (with the IClassFactory interface on it), and call CoRegisterClassObject to register the class object.

CoRegisterClassObject will register a kind of factory rather than class object itself like ThumbnailProvider.

https://github.com/microsoft/Windows-classic-samples/blob/d42a2ea76a6d50c6790bbcf88a60de73cce069a9/Samples/CloudMirror/CloudMirror/ShellServices.cpp#L40-L41

This may help on regstration of ShellServices.

diff --git a/CloudMirror/CloudProviderRegistrar.cs b/CloudMirror/CloudProviderRegistrar.cs
index a221439..54990d7 100644
--- a/CloudMirror/CloudProviderRegistrar.cs
+++ b/CloudMirror/CloudProviderRegistrar.cs
@@ -20,7 +20,7 @@ namespace CloudMirror
 			{
 				var info = new StorageProviderSyncRootInfo();
 				info.Id = GetSyncRootId();
-				var folder = StorageFolder.GetFolderFromPathAsync(ProviderFolderLocations.ClientFolder).GetResults();
+				var folder = StorageFolder.GetFolderFromPathAsync(ProviderFolderLocations.ClientFolder).AsTask().Result;
 				info.Path = folder;
 				info.DisplayNameResource = "TestStorageProviderDisplayName";
 				info.IconResource = "%SystemRoot%\\system32\\charmap.exe,0"; // This icon is just for the sample. You should provide your own branded icon here
diff --git a/CloudMirror/ShellServices.cs b/CloudMirror/ShellServices.cs
index f9a0eea..b1a77f1 100644
--- a/CloudMirror/ShellServices.cs
+++ b/CloudMirror/ShellServices.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Threading;
-using System.Threading.Tasks;
 using Vanara.PInvoke;
 using static Vanara.PInvoke.Kernel32;
 using static Vanara.PInvoke.Ole32;
@@ -15,17 +14,17 @@ namespace CloudMirror
 			{
 				CoInitializeEx(default, COINIT.COINIT_APARTMENTTHREADED).ThrowIfFailed();
 
-				var thumbnailProvider = new ThumbnailProvider();
-				CoRegisterClassObject(typeof(ThumbnailProvider).GUID, thumbnailProvider, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out var cookie).ThrowIfFailed();
+				var thumbnailProviderFactory = new Factory(() => new ThumbnailProvider());
+				CoRegisterClassObject(typeof(ThumbnailProvider).GUID, thumbnailProviderFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out var cookie).ThrowIfFailed();
 
-				var contextMenu = new TestExplorerCommandHandler();
-				CoRegisterClassObject(typeof(TestExplorerCommandHandler).GUID, contextMenu, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
+				var contextMenuFactory = new Factory(() => new TestExplorerCommandHandler());
+				CoRegisterClassObject(typeof(TestExplorerCommandHandler).GUID, contextMenuFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
 
-				var customStateProvider = new CustomStateProvider();
-				CoRegisterClassObject(typeof(CustomStateProvider).GUID, customStateProvider, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
+				var customStateProviderFactory = new Factory(() => new CustomStateProvider());
+				CoRegisterClassObject(typeof(CustomStateProvider).GUID, customStateProviderFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
 
-				var uriSource = new UriSource();
-				CoRegisterClassObject(typeof(UriSource).GUID, uriSource, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
+				var uriSourceFactory = new Factory(() => new UriSource());
+				CoRegisterClassObject(typeof(UriSource).GUID, uriSourceFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
 
 				using var dummyEvent = CreateEvent(null, false, false);
 				if (dummyEvent.IsInvalid)
@@ -35,5 +34,37 @@ namespace CloudMirror
 			thread.SetApartmentState(ApartmentState.STA);
 			thread.Start();
 		}
+
+		private class Factory : IClassFactory
+		{
+			public Factory(Func<object> generator)
+			{
+				_generator = generator;
+			}
+
+			private readonly Func<object> _generator;
+			private static readonly Guid IID_IUnknown = new Guid("{00000000-0000-0000-c000-000000000046}");
+
+			HRESULT IClassFactory.CreateInstance(object pUnkOuter, in Guid riid, out object ppvObject)
+			{
+				if (riid != IID_IUnknown)
+				{
+					// We cannot handle this for now
+					ppvObject = null;
+					return HRESULT.E_NOINTERFACE;
+				}
+				else
+				{
+					var obj = _generator();
+					ppvObject = obj;
+					return HRESULT.S_OK;
+				}
+			}
+
+			HRESULT IClassFactory.LockServer(bool fLock)
+			{
+				return HRESULT.S_OK;
+			}
+		}
 	}
 }

With this fix, it worked on my Windows 10 21H2.

diff --git a/CloudMirror/CloudProviderRegistrar.cs b/CloudMirror/CloudProviderRegistrar.cs
index a221439..54990d7 100644
--- a/CloudMirror/CloudProviderRegistrar.cs
+++ b/CloudMirror/CloudProviderRegistrar.cs
@@ -20,7 +20,7 @@ namespace CloudMirror
 			{
 				var info = new StorageProviderSyncRootInfo();
 				info.Id = GetSyncRootId();
-				var folder = StorageFolder.GetFolderFromPathAsync(ProviderFolderLocations.ClientFolder).GetResults();
+				var folder = StorageFolder.GetFolderFromPathAsync(ProviderFolderLocations.ClientFolder).AsTask().Result;
 				info.Path = folder;
 				info.DisplayNameResource = "TestStorageProviderDisplayName";
 				info.IconResource = "%SystemRoot%\\system32\\charmap.exe,0"; // This icon is just for the sample. You should provide your own branded icon here
diff --git a/CloudMirror/FakeCloudProvider.cs b/CloudMirror/FakeCloudProvider.cs
index f149199..cf94bab 100644
--- a/CloudMirror/FakeCloudProvider.cs
+++ b/CloudMirror/FakeCloudProvider.cs
@@ -1,5 +1,5 @@
 using System;
-using System.Threading.Tasks;
+using System.Threading;
 using static Vanara.PInvoke.CldApi;
 
 namespace CloudMirror
@@ -20,6 +20,8 @@ namespace CloudMirror
 
 			if (ProviderFolderLocations.Init(serverFolder, clientFolder))
 			{
+				var onStop  = new CancellationTokenSource();
+
 				try
 				{
 					// Stage 1: Setup
@@ -27,7 +29,7 @@ namespace CloudMirror
 					// The client folder (syncroot) must be indexed in order for states to properly display
 					Utilities.AddFolderToSearchIndexer(ProviderFolderLocations.ClientFolder);
 					// Start up the task that registers and hosts the services for the shell (such as custom states, menus, etc)
-					ShellServices.InitAndStartServiceTask();
+					ShellServices.InitAndStartServiceTask(onStop.Token);
 					// Register the provider with the shell so that the Sync Root shows up in File Explorer
 					CloudProviderRegistrar.RegisterWithShell();
 					// Hook up callback methods (in this class) for transferring files between client and server
@@ -50,6 +52,8 @@ namespace CloudMirror
 					// Stage 3: Done Running-- caused by CTRL-C
 					//--------------------------------------------------------------------------------------------
 					// Unhook up those callback methods
+					onStop.Cancel();
+
 					DisconnectSyncRootTransferCallbacks();
 
 					// A real sync engine should NOT unregister the sync root upon exit.
diff --git a/CloudMirror/FileCopierWithProgress.cs b/CloudMirror/FileCopierWithProgress.cs
index 2e13c69..bc3d2d9 100644
--- a/CloudMirror/FileCopierWithProgress.cs
+++ b/CloudMirror/FileCopierWithProgress.cs
@@ -194,6 +194,22 @@ namespace CloudMirror
 				readContext.StartOffset += numberOfBytesTransfered;
 				readContext.RemainingLength -= numberOfBytesTransfered;
 
+				Marshal.WriteInt64(
+					ptr: IntPtr.Add(
+						pointer: (IntPtr)overlapped,
+						offset: (int)Marshal.OffsetOf<READ_COMPLETION_CONTEXT>(nameof(readContext.StartOffset))
+					),
+					val: readContext.StartOffset
+				);
+
+				Marshal.WriteInt64(
+					ptr: IntPtr.Add(
+						pointer: (IntPtr)overlapped,
+						offset: (int)Marshal.OffsetOf<READ_COMPLETION_CONTEXT>(nameof(readContext.RemainingLength))
+					),
+					val: readContext.RemainingLength
+				);
+
 				// See if there is anything left to read
 				if (readContext.RemainingLength > 0)
 				{
@@ -244,7 +260,7 @@ namespace CloudMirror
 			};
 			var opParams = new CF_OPERATION_PARAMETERS
 			{
-				ParamSize = (uint)Marshal.SizeOf<CF_OPERATION_PARAMETERS.TRANSFERDATA>() + (uint)Marshal.SizeOf<uint>(),
+				ParamSize = (uint)CF_OPERATION_PARAMETERS.CF_SIZE_OF_OP_PARAM<CF_OPERATION_PARAMETERS.TRANSFERDATA>(),
 				TransferData = new CF_OPERATION_PARAMETERS.TRANSFERDATA
 				{
 					CompletionStatus = completionStatus,
diff --git a/CloudMirror/ShellServices.cs b/CloudMirror/ShellServices.cs
index f9a0eea..68a5f5d 100644
--- a/CloudMirror/ShellServices.cs
+++ b/CloudMirror/ShellServices.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Threading;
-using System.Threading.Tasks;
 using Vanara.PInvoke;
 using static Vanara.PInvoke.Kernel32;
 using static Vanara.PInvoke.Ole32;
@@ -9,31 +8,68 @@ namespace CloudMirror
 {
 	internal static class ShellServices
 	{
-		public static void InitAndStartServiceTask()
+		public static void InitAndStartServiceTask(CancellationToken cancellationToken)
 		{
 			var thread = new Thread(() =>
 			{
 				CoInitializeEx(default, COINIT.COINIT_APARTMENTTHREADED).ThrowIfFailed();
 
-				var thumbnailProvider = new ThumbnailProvider();
-				CoRegisterClassObject(typeof(ThumbnailProvider).GUID, thumbnailProvider, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out var cookie).ThrowIfFailed();
+				var thumbnailProviderFactory = new Factory(() => new ThumbnailProvider());
+				CoRegisterClassObject(typeof(ThumbnailProvider).GUID, thumbnailProviderFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out var thumbnailProviderFactoryCookie).ThrowIfFailed();
 
-				var contextMenu = new TestExplorerCommandHandler();
-				CoRegisterClassObject(typeof(TestExplorerCommandHandler).GUID, contextMenu, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
+				var contextMenuFactory = new Factory(() => new TestExplorerCommandHandler());
+				CoRegisterClassObject(typeof(TestExplorerCommandHandler).GUID, contextMenuFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out var contextMenuFactoryCookie).ThrowIfFailed();
 
-				var customStateProvider = new CustomStateProvider();
-				CoRegisterClassObject(typeof(CustomStateProvider).GUID, customStateProvider, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
+				var customStateProviderFactory = new Factory(() => new CustomStateProvider());
+				CoRegisterClassObject(typeof(CustomStateProvider).GUID, customStateProviderFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out var customStateProviderFactoryCookie).ThrowIfFailed();
 
-				var uriSource = new UriSource();
-				CoRegisterClassObject(typeof(UriSource).GUID, uriSource, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out cookie).ThrowIfFailed();
+				var uriSourceFactory = new Factory(() => new UriSource());
+				CoRegisterClassObject(typeof(UriSource).GUID, uriSourceFactory, CLSCTX.CLSCTX_LOCAL_SERVER, REGCLS.REGCLS_MULTIPLEUSE, out var uriSourceFactoryCookie).ThrowIfFailed();
 
-				using var dummyEvent = CreateEvent(null, false, false);
-				if (dummyEvent.IsInvalid)
-					Win32Error.ThrowLastError();
-				CoWaitForMultipleHandles(COWAIT_FLAGS.COWAIT_DISPATCH_CALLS, INFINITE, 1, new[] { (IntPtr)dummyEvent }, out _);
+				var stopEvent = cancellationToken.WaitHandle.SafeWaitHandle.DangerousGetHandle();
+				CoWaitForMultipleHandles(COWAIT_FLAGS.COWAIT_DISPATCH_CALLS, INFINITE, 1, new[] { (IntPtr)stopEvent }, out _);
+
+				CoRevokeClassObject(uriSourceFactoryCookie);
+				CoRevokeClassObject(customStateProviderFactoryCookie);
+				CoRevokeClassObject(contextMenuFactoryCookie);
+				CoRevokeClassObject(thumbnailProviderFactoryCookie);
+
+				CoUninitialize();
 			});
 			thread.SetApartmentState(ApartmentState.STA);
 			thread.Start();
 		}
+
+		private class Factory : IClassFactory
+		{
+			public Factory(Func<object> generator)
+			{
+				_generator = generator;
+			}
+
+			private readonly Func<object> _generator;
+			private static readonly Guid IID_IUnknown = new Guid("{00000000-0000-0000-c000-000000000046}");
+
+			HRESULT IClassFactory.CreateInstance(object pUnkOuter, in Guid riid, out object ppvObject)
+			{
+				if (riid != IID_IUnknown)
+				{
+					// We cannot handle this for now
+					ppvObject = null;
+					return HRESULT.E_NOINTERFACE;
+				}
+				else
+				{
+					var obj = _generator();
+					ppvObject = obj;
+					return HRESULT.S_OK;
+				}
+			}
+
+			HRESULT IClassFactory.LockServer(bool fLock)
+			{
+				return HRESULT.S_OK;
+			}
+		}
 	}
 }
\ No newline at end of file
diff --git a/CloudMirror/Utilities.cs b/CloudMirror/Utilities.cs
index 0ea27fc..234ae4c 100644
--- a/CloudMirror/Utilities.cs
+++ b/CloudMirror/Utilities.cs
@@ -81,7 +81,7 @@ namespace CloudMirror
 				propStoreVolatile.Item.SetValue(PKEY_StorageProviderTransferProgress, new long[] { completed, total }, false);
 
 				// Set the sync transfer status accordingly
-				propStoreVolatile.Item.SetValue(PROPERTYKEY.System.SyncTransferStatus, (completed < total) ? SYNC_TRANSFER_STATUS.STS_TRANSFERRING : SYNC_TRANSFER_STATUS.STS_NONE, false);
+				propStoreVolatile.Item.SetValue(PROPERTYKEY.System.SyncTransferStatus, (uint)((completed < total) ? SYNC_TRANSFER_STATUS.STS_TRANSFERRING : SYNC_TRANSFER_STATUS.STS_NONE), false);
 
 				// Without this, all your hard work is wasted.
 				propStoreVolatile.Item.Commit();

@kenjiuno Will you submit a PR with those changes?

I will

Thank you!!