dahall / WinClassicSamplesCS

A duplication in C# of the "Windows-classic-samples" using Vanara libraries.
44 stars 8 forks source link

ShellServices in example CloudMirror not working #2

Closed aupous closed 1 year ago

aupous commented 3 years ago

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?

dahall commented 3 years ago

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

aupous commented 3 years ago

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

dahall commented 3 years ago

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

dahall commented 3 years ago

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();
}
aupous commented 3 years ago

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

dahall commented 3 years ago

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

aupous commented 3 years ago

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

aupous commented 3 years ago

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

dahall commented 3 years ago

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

dahall commented 3 years ago

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

kenjiuno commented 1 year ago

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

kenjiuno commented 1 year ago

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;
+           }
+       }
    }
 }
kenjiuno commented 1 year ago

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();
dahall commented 1 year ago

@kenjiuno Will you submit a PR with those changes?

kenjiuno commented 1 year ago

I will

dahall commented 1 year ago

Thank you!!