【问题标题】:Passing IDataObject to IAssocHandler::Invoke in C#在 C# 中将 IDataObject 传递给 IAssocHandler::Invoke
【发布时间】:2018-01-26 10:54:14
【问题描述】:

我想重建一个 OpenWith 上下文菜单。

到目前为止,我使用SHAssocEnumHandler 来检索给定文件扩展名的所有可能应用程序。

现在我想调用所选AssocHandlerInvoke method。此方法需要一个指向IDataObject 的指针来调用具有给定对象的应用程序。

在我的情况下,我有一个要打开的文件的路径:

c:\devtest\htmlTest.html

我的问题是如何从这条路径到达IDataObject。然后是一个指向这个对象的指针。

到目前为止我的代码:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace AssocHandlerWithPointer
{
  [Flags]
  public enum ASSOC_FILTER
  {
    ASSOC_FILTER_NONE = 0x00000000,
    ASSOC_FILTER_RECOMMENDED = 0x00000001
  }

  public class Test
  {
    [DllImport("Shell32", EntryPoint = "SHAssocEnumHandlers", PreserveSig = false)]
    public extern static void SHAssocEnumHandlers([MarshalAs(UnmanagedType.LPWStr)] string pszExtra, ASSOC_FILTER afFilter, [Out] out IntPtr ppEnumHandler);

    // IEnumAssocHandlers
    [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
    private delegate int FuncNext(IntPtr refer, int celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 1)] IntPtr[] rgelt, [Out] out int pceltFetched);

    // IAssocHandler
    [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
    private delegate int FuncGetUiName(IntPtr refer, out IntPtr ppsz);

    [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
    private delegate int FuncInvoke(IntPtr refer, IntPtr pdo);

    static void Main(string[] args)
    {
      const string extension = ".html";

      IntPtr pEnumAssocHandlers;
      SHAssocEnumHandlers(extension, ASSOC_FILTER.ASSOC_FILTER_RECOMMENDED, out pEnumAssocHandlers);

      IntPtr pFuncNext = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pEnumAssocHandlers) + 3 * sizeof(int));
      FuncNext next = (FuncNext)Marshal.GetDelegateForFunctionPointer(pFuncNext, typeof(FuncNext));

      IntPtr[] pArrayAssocHandlers  = new IntPtr[255];
      int num;

      int resNext = next(pEnumAssocHandlers, 255, pArrayAssocHandlers, out num);
      if (resNext == 0)
      {
        for (int i = 0; i < num; i++)
        {
          IntPtr pAssocHandler = pArrayAssocHandlers[i];

          IntPtr pFuncGetUiName = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pAssocHandler) + 4 * sizeof(int));
          FuncGetUiName getUiName = (FuncGetUiName)Marshal.GetDelegateForFunctionPointer(pFuncGetUiName, typeof(FuncGetUiName));
          IntPtr pUiName;
          int resGetUiName = getUiName(pAssocHandler, out pUiName);
          Console.WriteLine("UI: " + Marshal.PtrToStringUni(pUiName));

          const string filePath = "c:\\devtest\\htmlTest.html";
          DataObject dataObject = new DataObject();
          dataObject.SetData(filePath);

          IntPtr pDataObject = Marshal.GetIUnknownForObject(dataObject);
          IntPtr pFuncInvoke = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pAssocHandler) + 5 * sizeof(int));
          FuncInvoke invoke = (FuncInvoke) Marshal.GetDelegateForFunctionPointer(pFuncInvoke, typeof (FuncInvoke));
          int resInvoke = invoke(pAssocHandler, Marshal.ReadIntPtr(pDataObject));
          Console.WriteLine(resInvoke);
          Console.WriteLine("-----");

          Marshal.Release(pArrayAssocHandlers[i]);
        }
      }
      Marshal.Release(pEnumAssocHandlers);
      Console.ReadLine();
    }
  }
}

我使用来自System.Windows.FormsDataObject。应该没问题,因为 DataObject 继承自 IDataObject

我知道在循环内调用Invoke 方法对用例没有意义。而且也没有用户交互。 但是要让它工作应该没问题。

注意:
该解决方案必须适用于 Windows 7 和 Windows 10。

手动解析注册表并检索 shell/open/command 是不可能的解决方案。

【问题讨论】:

    标签: c# winapi pinvoke marshalling


    【解决方案1】:

    知道何时要使用 shell 的基本对象是IShellItem。从这里您可以使用BindToHandler method 获取其他接口。

    要从路径创建初始 IShellItem,您可以使用 SHCreateItemFromParsingName

    您应该使用普通的 COM 接口定义而不是尝试手动构建 v-tables。这是一个似乎有效的示例代码:

    class Program
    {
        [STAThread] // don't forget STAThread, most shell methods require that
        static void Main(string[] args)
        {
            string path = @"d:\kilroy_was_here\test.htm";
    
            int hr = SHCreateItemFromParsingName(path, null, typeof(IShellItem).GUID, out IShellItem item);
            if (hr != 0)
                throw new Win32Exception(hr);
    
            hr = SHAssocEnumHandlers(Path.GetExtension(path), ASSOC_FILTER.ASSOC_FILTER_RECOMMENDED, out IEnumAssocHandlers eah);
            if (hr != 0)
                throw new Win32Exception(hr);
    
            do
            {
                eah.Next(1, out IAssocHandler handler, out int fetched);
                if (fetched != 1)
                    break;
    
                handler.GetName(out string name);
                Console.WriteLine(name);
                if (name.Contains("firefox")) // open with firefox, for example
                {
                    var BHID_DataObject = new Guid("b8c0bd9f-ed24-455c-83e6-d5390c4fe8c4");
                    var dao = item.BindToHandler(null, BHID_DataObject, typeof(IDataObject).GUID);
                    handler.Invoke(dao);
                }
            }
            while (true);
        }
    
        [Flags]
        public enum ASSOC_FILTER
        {
            ASSOC_FILTER_NONE = 0x00000000,
            ASSOC_FILTER_RECOMMENDED = 0x00000001
        }
    
        [Guid("973810ae-9599-4b88-9e4d-6ee98c9552da"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IEnumAssocHandlers
        {
            [PreserveSig]
            int Next(int celt, out IAssocHandler rgelt, out int pceltFetched);
        }
    
        [Guid("f04061ac-1659-4a3f-a954-775aa57fc083"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IAssocHandler
        {
            void GetName([MarshalAs(UnmanagedType.LPWStr)] out string ppsz);
            void GetUIName([MarshalAs(UnmanagedType.LPWStr)] out string ppsz);
            void GetIconLocation([MarshalAs(UnmanagedType.LPWStr)] out string ppszPath, out int pIndex);
            [PreserveSig]
            int IsRecommended();
            void MakeDefault([MarshalAs(UnmanagedType.LPWStr)] string pszDescription);
            void Invoke(IDataObject pdo);
            void CreateInvoker(IDataObject pdo, out /*IAssocHandlerInvoker*/ object invoker);
        }
    
        [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellItem
        {
            // here we only need this member
            IDataObject BindToHandler(IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid);
        }
    
        [DllImport("shell32", CharSet = CharSet.Unicode)]
        private extern static int SHCreateItemFromParsingName(string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
    
        [DllImport("shell32", CharSet = CharSet.Unicode)]
        private extern static int SHAssocEnumHandlers(string pszExtra, ASSOC_FILTER afFilter, out IEnumAssocHandlers ppEnumHandler);
    }
    

    PS:我使用了 System.Runtime.InteropServices.ComTypes 中的 IDataObject

    【讨论】:

    • 嗨,西蒙,您的代码在 Windows 10 机器上运行良好。在 Windows 7 机器上,我在调用 SHAssocEnumHandlers 时收到带有未知错误的 COMException。另请参阅此问题:link
    • 错误号是多少? STAThread 属性你放了吗?
    • 错误号为:HRESULT: 0x80004005 (E_FAIL)。我放了 [STAThread] 属性,我还尝试了“[PreserveSig]”,没有任何变化。
    • 好的,我可以在Win7上重现。哇,这很奇怪,我理解你为什么要按照你的方式编写这个调用。我测试了 C++,它工作正常,正如预期的那样。事实上,只要我们想取回这个特定的 COM 对象,它就会在 CLR 封送层崩溃(它与对象一起工作,然后如果我们想强制转换则失败)。绝对看起来像一个错误。也许 Win7 注册表遗漏了有关该接口的一些关键信息......无论如何,我的其余部分关于如何构建 IDataObject 应该是正确的
    • IShellItem::BindToHandler 方法中有一个BHID_EnumAssocHandlers 可用,也许我们需要在SHAssocEnumHandlers 输出上使用它。但我不知道怎么做。
    猜你喜欢
    • 2021-07-27
    • 1970-01-01
    • 2013-03-04
    • 1970-01-01
    • 1970-01-01
    • 2019-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多