I want to rebuild a OpenWith context menu.
So far i use the SHAssocEnumHandler
to retrieve all possible applications for a given file extension.
Now i want to call the Invoke method of an chosen AssocHandler. This method needs a pointer to a IDataObject
to call the application with the given Object.
In my case i have the path to a file i want to open:
c:\devtest\htmlTest.html
My Problem is how do i get from this Path to an IDataObject
. And then a pointer to this Object.
My Code so far:
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();
}
}
}
I use the DataObject
from System.Windows.Forms
. What should be ok because DataObject
inherits from IDataObject
.
I know calling the Invoke
method inside the loop makes no sens for the use case. And also there is no user interaction.
But to get it to work it should be ok.
Note:
The solution has to work an Windows 7 and Windows 10.
Parsing the Registry manually and retrieve the shell/open/command is no possible solution.