Skip to content

Instantly share code, notes, and snippets.

@yizhang82
Last active November 15, 2023 22:09
Show Gist options
  • Save yizhang82/a1268d3ea7295a8a1496e01d60ada816 to your computer and use it in GitHub Desktop.
Save yizhang82/a1268d3ea7295a8a1496e01d60ada816 to your computer and use it in GitHub Desktop.
Example on creating your own IDispatch implementation
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication3
{
class Program
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("Begin test...");
//
// Test using shell
//
object shell = Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("{13709620-C279-11CE-A49E-444553540000}")));
object application = shell.InvokeDispMember("Application", new object[] { }, null, InvokeMemberHelper.MethodKind.PropertyGet);
if (application != null)
Console.WriteLine("Application property success!");
else
Console.WriteLine("Application property returns null!");
object folders = shell.InvokeDispMember("BrowseForFolder", new object[] { (int)0, @"Choose!", 0 });
object folderItems = folders.InvokeDispMember("Items", new object[] { }, null, InvokeMemberHelper.MethodKind.Method);
int count = (int)folderItems.InvokeDispMember("Count", new object[] { }, null, InvokeMemberHelper.MethodKind.PropertyGet);
Console.WriteLine("Folder has {0} items.", count);
object folderItem = folderItems.InvokeDispMember("Item", new object[] { 0 }, null, InvokeMemberHelper.MethodKind.Method);
string name = (string)folderItem.InvokeDispMember("Name", new object[] { }, null, InvokeMemberHelper.MethodKind.PropertyGet);
Console.WriteLine("First FolderItem is '{0}'.", name);
folderItem.InvokeDispMember("Name", new object[] { name + "__changed" }, null, InvokeMemberHelper.MethodKind.PropertyPut);
name = (string)folderItem.InvokeDispMember("Name", new object[] { }, null, InvokeMemberHelper.MethodKind.PropertyGet);
Console.WriteLine("Changed to '{0}'.", name);
shell.InvokeDispMember("FindFiles", new object[] { });
shell.InvokeDispMember("ShellExecute", new object[] { @"C:\windows\system32\cmd.exe" });
object dblClickTime = shell.InvokeDispMember("GetSystemInformation", new object[] { "DoubleClickTime" });
Console.WriteLine("Double click time = " + dblClickTime);
}
}
static class InvokeMemberHelper
{
public enum MethodKind
{
Method,
PropertyPut,
PropertyPutRef,
PropertyGet
}
public static object InvokeDispPropertyGet(
this object target,
string name,
params object[] args)
{
return target.InvokeDispMember(
name, args, null, MethodKind.PropertyGet);
}
public static object InvokeDispPropertyPut(
this object target,
string name,
params object[] args)
{
return target.InvokeDispMember(
name, args, null, MethodKind.PropertyPut);
}
public static object InvokeDispMethod(
this object target,
string name,
params object[] args)
{
return target.InvokeDispMember(
name, args, null, MethodKind.Method);
}
unsafe public static object InvokeDispMember(
this object target,
string name,
object[] args,
ParameterModifier[] modifiers = null,
MethodKind methodKind = MethodKind.Method
)
{
// There should be either no modifiers or exactly one modifier
if (modifiers != null && modifiers.Length > 1)
throw new ArgumentException();
//
// Obtain IDispatch interface
//
IDispatch disp = (IDispatch)target;
//
// Retrieve DISPID
//
uint dispId = GetDispID(disp, name);
IntPtr pVariantArgArray = IntPtr.Zero;
IntPtr pDispIDArray = IntPtr.Zero;
int argCount = args == null ? 0 : args.Length;
int variantSize = Marshal.SizeOf<Variant>();
object result;
try
{
//
// Package arguments
//
if (argCount > 0)
{
pVariantArgArray = Marshal.AllocCoTaskMem(variantSize * argCount);
for (int i = 0; i < argCount; ++i)
{
object arg = args[i];
// !! The arguments should be in REVERSED order!!
int actualIndex = (argCount - i - 1);
// If need to pass by ref, create a by-ref variant
if (modifiers != null && modifiers[0][i])
{
// Create a VARIANT that the by-ref VARIANT points to
IntPtr pTmpVariant = Marshal.AllocCoTaskMem(variantSize);
Marshal.GetNativeVariantForObject(args[i], pTmpVariant);
// Create the by-ref VARIANT
MakeByRefVariant(pVariantArgArray + actualIndex * variantSize, pTmpVariant);
}
else
{
Marshal.GetNativeVariantForObject(args[i], pVariantArgArray + actualIndex * variantSize);
}
}
}
DISPPARAMS[] paramArray = new DISPPARAMS[1];
paramArray[0].varArgs = pVariantArgArray;
paramArray[0].argCount = argCount;
if (methodKind == MethodKind.PropertyPut || methodKind == MethodKind.PropertyPutRef)
{
//
// For property putters, the first DISPID argument needs to be DISPID_PROPERTYPUT
//
pVariantArgArray = Marshal.AllocCoTaskMem(variantSize * argCount);
Marshal.WriteInt32(pVariantArgArray, DISPID_PROPERTYPUT);
paramArray[0].namedArgCount = 1;
paramArray[0].namedArgDispIds = pVariantArgArray;
}
else
{
//
// Otherwise, no named parameters are necessary
//
paramArray[0].namedArgCount = 0;
paramArray[0].namedArgDispIds = IntPtr.Zero;
}
//
// Make the call
//
EXCEPINFO info = default(EXCEPINFO);
uint err;
short flags;
if (methodKind == MethodKind.Method)
flags = (short)Flags.DISPATCH_METHOD;
else if (methodKind == MethodKind.PropertyPut)
flags = (short)Flags.DISPATCH_PROPERTYPUT;
else if (methodKind == MethodKind.PropertyPutRef)
flags = (short)Flags.DISPATCH_PROPERTYPUTREF;
else if (methodKind == MethodKind.PropertyGet)
flags = (short)Flags.DISPATCH_PROPERTYGET;
else
throw new ArgumentException();
try
{
disp.Invoke(
(uint)dispId,
IID_NULL,
LCID_DEFAULT,
flags,
paramArray,
out result,
out info,
out err);
}
catch (Exception ex)
{
if (ex.HResult == DISP_E_EXCEPTION)
{
Exception realException;
if (info.scode != 0)
realException = Marshal.GetExceptionForHR(info.scode, IntPtr.Zero);
else
realException = Marshal.GetExceptionForHR((int)info.code, IntPtr.Zero);
if (info.strDescription != IntPtr.Zero)
{
string realErrorMessage = Marshal.PtrToStringBSTR(info.strDescription);
// @TODO - find a way to return this to the caller
}
throw realException;
}
throw;
}
//
// Now back propagate the by-ref arguments
//
for (int i = 0; i < argCount; ++i)
{
object arg = args[i];
// !! The arguments should be in REVERSED order!!
int actualIndex = (argCount - i - 1);
// If need to pass by ref, back propagate
if (modifiers != null && modifiers[0][i])
{
args[i] = Marshal.GetObjectForNativeVariant(pVariantArgArray + actualIndex * variantSize);
}
}
return result;
}
finally
{
//
// Free memory
//
if (pVariantArgArray != IntPtr.Zero)
{
for (int i = 0; i < argCount; ++i)
{
VariantClear(pVariantArgArray + i * variantSize);
}
Marshal.FreeCoTaskMem(pVariantArgArray);
}
if (pDispIDArray != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pDispIDArray);
}
}
}
static uint GetDispID(IDispatch disp, string name)
{
uint[] dispid = new uint[1];
disp.GetIDsOfNames(IID_NULL, new string[] { name }, 1, LCID_DEFAULT, dispid);
return dispid[0];
}
public enum VarEnum : ushort
{
VT_EMPTY = 0,
VT_NULL = 1,
VT_I2 = 2,
VT_I4 = 3,
VT_R4 = 4,
VT_R8 = 5,
VT_CY = 6,
VT_DATE = 7,
VT_BSTR = 8,
VT_DISPATCH = 9,
VT_ERROR = 10,
VT_BOOL = 11,
VT_VARIANT = 12,
VT_UNKNOWN = 13,
VT_DECIMAL = 14,
VT_I1 = 16,
VT_UI1 = 17,
VT_UI2 = 18,
VT_UI4 = 19,
VT_I8 = 20,
VT_UI8 = 21,
VT_INT = 22,
VT_UINT = 23,
VT_VOID = 24,
VT_HRESULT = 25,
VT_PTR = 26,
VT_SAFEARRAY = 27,
VT_CARRAY = 28,
VT_USERDEFINED = 29,
VT_LPSTR = 30,
VT_LPWSTR = 31,
VT_RECORD = 36,
VT_FILETIME = 64,
VT_BLOB = 65,
VT_STREAM = 66,
VT_STORAGE = 67,
VT_STREAMED_OBJECT = 68,
VT_STORED_OBJECT = 69,
VT_BLOB_OBJECT = 70,
VT_CF = 71,
VT_CLSID = 72,
VT_VECTOR = 0x1000,
VT_ARRAY = 0x2000,
VT_BYREF = 0x4000
}
/*
const byte[] SizeOfVariant = new map[]
{
0, // VT_EMPTY
0, // VT_NULL
2, // VT_I2
4, // VT_I4
4, // VT_R4
8, // VT_R8
8, // VT_CY
IntPtr.Size, // VT_DATE
IntPtr.Size, // VT_BSTR
IntPtr.Size, // VT_DISPATCH
4, // VT_ERROR
2, // VT_BOOL
IntPtr.Size, // VT_VARIANT
IntPtr.Size, // VT_UNKNOWN
IntPtr.Size, // VT_DECIMAL
0, // unused
1, // VT_I1
1, // VT_UI1
2, // VT_UI2
4, // VT_UI4
8, // VT_I8
8, // VT_UI8
4, // VT_INT
4, // VT_UINT
0, // VT_VOID
4, // VT_HRESULT
IntPtr.Size, // VT_PTR
IntPtr.Size, // VT_SAFEARRAY
IntPtr.Size, // VT_CARRAY
IntPtr.Size, // VT_USERDEFINED
IntPtr.Size, // VT_LPSTR
IntPtr.Size, // VT_LPWSTR
};
*/
static unsafe void MakeByRefVariant(IntPtr pDestVariant, IntPtr pSrcVariant)
{
Variant* psrcvar = (Variant*)pSrcVariant;
Variant* pdestvar = (Variant*)pDestVariant;
switch ((VarEnum)psrcvar->_typeUnion._vt)
{
case VarEnum.VT_EMPTY:
// BY REF VT_EMPTY is not valid
throw new ArgumentException();
case VarEnum.VT_RECORD:
// Representation of record is the same with or without byref
pdestvar->_typeUnion._unionTypes._record._record = psrcvar->_typeUnion._unionTypes._record._record;
pdestvar->_typeUnion._unionTypes._record._recordInfo = psrcvar->_typeUnion._unionTypes._record._recordInfo;
break;
case VarEnum.VT_VARIANT:
pdestvar->_typeUnion._unionTypes._byref = new IntPtr(psrcvar);
break;
case VarEnum.VT_DECIMAL:
pdestvar->_typeUnion._unionTypes._byref = new IntPtr(&(psrcvar->_decimal));
break;
default:
// All the other cases start at the same offset so using &_i4 should work
pdestvar->_typeUnion._unionTypes._byref = new IntPtr(&(psrcvar->_typeUnion._unionTypes._i4));
break;
}
pdestvar->_typeUnion._vt = (ushort)(psrcvar->_typeUnion._vt | (ushort)VarEnum.VT_BYREF);
}
#region Implementation Details
static int LCID_DEFAULT = 0x0409;
static int DISPID_PROPERTYPUT = -3;
static int DISP_E_EXCEPTION = unchecked((int)0x80020009);
static Guid IID_NULL = new Guid();
[DllImport("oleaut32.dll")]
static extern void VariantClear(IntPtr pVariant);
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
interface IDispatch
{
void GetTypeInfoCount(out int typeInfoCount);
void GetTypeInfo(int info, int lcid, out IntPtr typeInfo);
void GetIDsOfNames(
[MarshalAs(UnmanagedType.LPStruct)] Guid iid,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] names,
int nameCount,
int lcid,
[MarshalAs(UnmanagedType.LPArray)][Out] uint[] DISPID);
void Invoke(uint dispid, [MarshalAs(UnmanagedType.LPStruct)]Guid iid, int lcid, short flags,
[MarshalAs(UnmanagedType.LPArray)] [In, Out] DISPPARAMS[] paramArray,
out object result,
out EXCEPINFO excepInfo,
out uint err
);
}
struct DISPPARAMS
{
public IntPtr varArgs;
public IntPtr namedArgDispIds;
public int argCount;
public int namedArgCount;
}
struct EXCEPINFO
{
public short code;
public short reserved;
public IntPtr strSource;
public IntPtr strDescription;
public IntPtr strHelpFile;
public uint helpContext;
public IntPtr pvReserved;
public IntPtr pfnDeferredFillIn;
public int scode;
}
enum Flags : short
{
DISPATCH_METHOD = 0x1,
DISPATCH_PROPERTYGET = 0x2,
DISPATCH_PROPERTYPUT = 0x4,
DISPATCH_PROPERTYPUTREF = 0x8
}
[StructLayout(LayoutKind.Explicit)]
public struct Variant
{
// Most of the data types in the Variant are carried in _typeUnion
[FieldOffset(0)]
internal TypeUnion _typeUnion;
// Decimal is the largest data type and it needs to use the space that is normally unused in TypeUnion._wReserved1, etc.
// Hence, it is declared to completely overlap with TypeUnion. A Decimal does not use the first two bytes, and so
// TypeUnion._vt can still be used to encode the type.
[FieldOffset(0)]
internal Decimal _decimal;
[StructLayout(LayoutKind.Explicit)]
internal struct TypeUnion
{
[FieldOffset(0)]
internal ushort _vt;
[FieldOffset(2)]
internal ushort _wReserved1;
[FieldOffset(4)]
internal ushort _wReserved2;
[FieldOffset(6)]
internal ushort _wReserved3;
[FieldOffset(8)]
internal UnionTypes _unionTypes;
}
[StructLayout(LayoutKind.Sequential)]
internal struct Record
{
internal IntPtr _record;
internal IntPtr _recordInfo;
}
[StructLayout(LayoutKind.Explicit)]
internal struct UnionTypes
{
[FieldOffset(0)]
internal SByte _i1;
[FieldOffset(0)]
internal Int16 _i2;
[FieldOffset(0)]
internal Int32 _i4;
[FieldOffset(0)]
internal Int64 _i8;
[FieldOffset(0)]
internal Byte _ui1;
[FieldOffset(0)]
internal UInt16 _ui2;
[FieldOffset(0)]
internal UInt32 _ui4;
[FieldOffset(0)]
internal UInt64 _ui8;
[FieldOffset(0)]
internal Int32 _int;
[FieldOffset(0)]
internal UInt32 _uint;
[FieldOffset(0)]
internal Int16 _bool;
[FieldOffset(0)]
internal Int32 _error;
[FieldOffset(0)]
internal Single _r4;
[FieldOffset(0)]
internal Double _r8;
[FieldOffset(0)]
internal Int64 _cy;
[FieldOffset(0)]
internal double _date;
[FieldOffset(0)]
internal IntPtr _bstr;
[FieldOffset(0)]
internal IntPtr _unknown;
[FieldOffset(0)]
internal IntPtr _dispatch;
[FieldOffset(0)]
internal IntPtr _pvarVal;
[FieldOffset(0)]
internal IntPtr _byref;
[FieldOffset(0)]
internal Record _record;
}
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment