private ComPointer(TComInterface comInterface) { Interface = comInterface; _pUnk = RdMarshal.GetIUnknownForObject(Interface); ConstructorObjectPrint(); }
/// <summary> /// Do our best to validate that the input memory address is actually a COM object /// </summary> /// <param name="comObjectPtr">the input memory address to check</param> /// <returns>false means definitely not a valid COM object. true means _probably_ a valid COM object</returns> public static bool ValidateComObject(IntPtr comObjectPtr) { // Is it a valid memory address, with at least one accessible vTable ptr if (!IsValidMemoryRange(comObjectPtr, IntPtr.Size)) { return(false); } var vTablePtr = RdMarshal.ReadIntPtr(comObjectPtr); // And for a COM object, we need a valid vtable, with at least 3 vTable entries (for IUnknown) if (!IsValidMemoryRange(vTablePtr, IntPtr.Size * 3)) { return(false); } var firstvTableEntry = RdMarshal.ReadIntPtr(vTablePtr); // And lets check the first vTable entry actually points to EXECUTABLE memory // (we could check all 3 initial IUnknown entries, but we want to be reasonably // efficient and we can never 100% guarantee our result anyway.) if (IsValidMemoryRange(firstvTableEntry, 1, checkIsExecutable: true)) { // As best as we can tell, it looks to be a valid COM object return(true); } else { // One of the validation checks failed. The COM object is definitely not a valid COM object. return(false); } }
/// <summary> /// Takes an unmanaged memory address and reads the unmanaged memory given by its pointer, /// with memory address validation for added protection /// </summary> /// <typeparam name="T">the type of structure to return</typeparam> /// <param name="memAddress">the unamanaged memory address to read</param> /// <returns>the requested structure T</returns> public static T ReadStructureSafe <T>(IntPtr memAddress) { if (UnmanagedMemoryHelper.IsValidMemoryRange(memAddress, RdMarshal.SizeOf(typeof(T)))) { return((T)RdMarshal.PtrToStructure(memAddress, typeof(T))); } throw new ArgumentException("Bad data pointer - unable to read structure data."); }
/// <summary> /// Takes an unmanaged memory address and reads the unmanaged memory given by its pointer /// </summary> /// <typeparam name="T">the type of structure to return</typeparam> /// <param name="memAddress">the unmanaged memory address to read</param> /// <returns>the requested structure T</returns> /// <remarks>use this over ReadStructureSafe for efficiency when there is no doubt about the validity of the pointed to data</remarks> public static T ReadStructureUnsafe <T>(IntPtr memAddress) { // We catch the most basic mistake of passing a null pointer here as it virtually costs nothing to check, // but no other checks are made as to the validity of the pointer. if (memAddress == IntPtr.Zero) { throw new ArgumentException("Unexpected null pointer."); } return((T)RdMarshal.PtrToStructure(memAddress, typeof(T))); }
// simply check if a COM object supports a particular COM interface // (without doing any casting by the CLR, which does much more than this under the covers) public static bool DoesComObjPtrSupportInterface <T>(IntPtr comObjPtr) { var iid = typeof(T).GUID; var hr = RdMarshal.QueryInterface(comObjPtr, ref iid, out var outInterfacePtr); if (!ComHelper.HRESULT_FAILED(hr)) { RdMarshal.Release(outInterfacePtr); return(true); } return(false); }
// The aggregation magic starts here public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv) { ppv = IntPtr.Zero; if (iid == typeof(T).GUID) { ppv = _outerObject; RdMarshal.AddRef(_outerObject); return(CustomQueryInterfaceResult.Handled); } return(CustomQueryInterfaceResult.Failed); }
private void TraceRelease(int rcwCount, ref bool addRef) { if (!addRef) { // Temporarily add a ref so that we can safely call IUnknown::Release // to report the ref count in the log. RdMarshal.AddRef(_pUnk); } var refCount = RdMarshal.Release(_pUnk); Debug.Print($"ComPointer:: Disposed: _pUnk: {RdMarshal.FormatPtr(_pUnk)} _interface: {typeof(TComInterface).Name} - {Interface.GetHashCode()} addRef: {_addRef} rcwCount: {rcwCount} refCount: {refCount}"); addRef = false; }
private ComPointer(IntPtr pUnk, bool addRef) { var refCount = -1; _pUnk = pUnk; if (addRef) { _addRef = true; refCount = RdMarshal.AddRef(_pUnk); } Interface = (TComInterface)RdMarshal.GetTypedObjectForIUnknown(pUnk, typeof(TComInterface)); ConstructorPointerPrint(refCount); }
/// <summary> /// frees all unmanaged memory assoicated with a DISPPARAMS structure /// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms221416(v=vs.85).aspx /// </summary> /// <param name="pDispParams"></param> private static void UnprepareDispatchArgs(ComTypes.DISPPARAMS pDispParams) { if (pDispParams.rgvarg != IntPtr.Zero) { // free the array of COM VARIANTs var variantStructSize = RdMarshal.SizeOf(typeof(VARIANT)); var variantArgsArrayOffset = pDispParams.rgvarg; var argIndex = 0; while (argIndex < pDispParams.cArgs) { VariantClear(variantArgsArrayOffset); variantArgsArrayOffset += variantStructSize; argIndex++; } RdMarshal.FreeHGlobal(pDispParams.rgvarg); } }
private void ReleaseUnmanagedResources() { if (_disposed) { return; } var rcwCount = RdMarshal.ReleaseComObject(Interface); var addRef = _addRef; TraceRelease(rcwCount, ref addRef); if (addRef) { RdMarshal.Release(_pUnk); } _disposed = true; }
void ComTypes.ITypeInfo.CreateInstance(object pUnkOuter, ref Guid riid, out object ppvObj) { // initialize out parameters ppvObj = default; using (var outPpvObj = AddressableVariables.CreateObjectPtr <object>()) { var unkOuter = RdMarshal.GetIUnknownForObject(pUnkOuter); var hr = _this_Internal.CreateInstance(unkOuter, riid, outPpvObj.Address); RdMarshal.Release(unkOuter); if (ComHelper.HRESULT_FAILED(hr)) { HandleBadHRESULT(hr); } ppvObj = outPpvObj.Value; } }
/// <summary> /// Validate a memory address range /// </summary> /// <param name="memOffset">the input memory address to check</param> /// <param name="size">the minimum size of data we are expecting to be available next to memOffset</param> /// <param name="checkIsExecutable">optionally check if the memory address points to EXECUTABLE memory</param> public static bool IsValidMemoryRange(IntPtr memOffset, int size, bool checkIsExecutable = false) { if (memOffset == IntPtr.Zero) { return(false); } var memInfo = new MEMORY_BASIC_INFORMATION(); var sizeOfMemInfo = new IntPtr(RdMarshal.SizeOf(memInfo)); // most of the time, a bad pointer will fail here if (VirtualQuery(memOffset, out memInfo, sizeOfMemInfo) != sizeOfMemInfo) { return(false); } // check the memory area is not a guard page, or otherwise inaccessible if ((memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_NOACCESS)) || (memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_GUARD))) { return(false); } // We've confirmed the base memory address is valid, and is accessible. // Finally just check the full address RANGE is also valid (i.e. the end point of the structure we're reading) var validMemAddressEnd = memInfo.BaseAddress.ToInt64() + memInfo.RegionSize.ToInt64(); var endOfStructPtr = memOffset.ToInt64() + size; if (endOfStructPtr > validMemAddressEnd) { return(false); } if (checkIsExecutable) { // We've been asked to check if the memory address is marked as containing executable code return(memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE) || memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE_READ) || memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE_READWRITE) || memInfo.Protect.HasFlag(ALLOCATION_PROTECTION.PAGE_EXECUTE_WRITECOPY)); } return(true); }
/// <summary> /// Takes a COM object, and reads the unmanaged memory given by its pointer, allowing us to read internal fields /// </summary> /// <typeparam name="T">the type of structure to return</typeparam> /// <param name="comObj">the COM object</param> /// <returns>the requested structure T</returns> public static T ReadComObjectStructure <T>(object comObj) { // Reads a COM object as a structure to copy its internal fields if (!RdMarshal.IsComObject(comObj)) { throw new ArgumentException("Expected a COM object"); } var referencesPtr = RdMarshal.GetIUnknownForObjectInContext(comObj); if (referencesPtr == IntPtr.Zero) { throw new InvalidOperationException("Cannot access the TypeLib API from this thread. TypeLib API must be accessed from the main thread."); } var retVal = ReadStructureSafe <T>(referencesPtr); RdMarshal.Release(referencesPtr); return(retVal); }
/// <summary> /// A basic helper for IDispatch::Invoke /// </summary> /// <param name="obj">The IDispatch object of which you want to invoke a member on</param> /// <param name="memberId">The dispatch ID of the member to invoke</param> /// <param name="invokeKind">See InvokeKind enumeration</param> /// <param name="args">Array of arguments to pass to the call, or null for no args</param> /// <remarks>TODO support DISPATCH_PROPERTYPUTREF (property-set) which requires special handling</remarks> /// <returns>An object representing the return value from the called routine</returns> public static object Invoke(IDispatch obj, int memberId, InvokeKind invokeKind, object[] args = null) { var pDispParams = PrepareDispatchArgs(args); var pExcepInfo = new ComTypes.EXCEPINFO(); var hr = obj.Invoke(memberId, ref _guid_null, 0, (uint)invokeKind, ref pDispParams, out var pVarResult, ref pExcepInfo, out var pErrArg); UnprepareDispatchArgs(pDispParams); if (ComHelper.HRESULT_FAILED(hr)) { if ((hr == (int)KnownComHResults.DISP_E_EXCEPTION) && (ComHelper.HRESULT_FAILED(pExcepInfo.scode))) { throw RdMarshal.GetExceptionForHR(pExcepInfo.scode); } throw RdMarshal.GetExceptionForHR(hr); } return(pVarResult); }
/// <summary> /// Constructor /// </summary> /// <param name="outerObject">The object that needs interface requests filtered</param> /// <param name="queryForType">determines whether we call QueryInterface for the interface or not</param> /// <remarks>if the passed in outerObject is known to point to the correct vtable for the interface, then queryForType can be false</remarks> /// <returns>if outerObject is IntPtr.Zero, then a null wrapper, else an aggregated wrapper</returns> public RestrictComInterfaceByAggregation(IntPtr outerObject, bool queryForType = true) { if (queryForType) { var iid = typeof(T).GUID; if (ComHelper.HRESULT_FAILED(RdMarshal.QueryInterface(outerObject, ref iid, out _outerObject))) { // allow null wrapping here return; } } else { _outerObject = outerObject; RdMarshal.AddRef(_outerObject); } var clrAggregator = RdMarshal.CreateAggregatedObject(_outerObject, this); WrappedObject = (T)RdMarshal.GetObjectForIUnknown(clrAggregator); // when this CCW object gets released, it will free the aggObjInner (well, after GC) RdMarshal.Release(clrAggregator); // _wrappedObject holds a reference to this now }
/// <summary> /// Convert input args into a contiguous array of real COM VARIANTs for the DISPPARAMS struct used by IDispatch::Invoke /// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms221416(v=vs.85).aspx /// </summary> /// <param name="args">An array of arguments to wrap</param> /// <returns><see cref="ComTypes.DISPPARAMS"/> structure ready to pass to IDispatch::Invoke</returns> private static ComTypes.DISPPARAMS PrepareDispatchArgs(object[] args) { var pDispParams = new ComTypes.DISPPARAMS(); if ((args != null) && (args.Length != 0)) { var variantStructSize = RdMarshal.SizeOf(typeof(VARIANT)); pDispParams.cArgs = args.Length; var argsVariantLength = variantStructSize * pDispParams.cArgs; var variantArgsArray = RdMarshal.AllocHGlobal(argsVariantLength); // In IDispatch::Invoke, arguments are passed in reverse order var variantArgsArrayOffset = variantArgsArray + argsVariantLength; foreach (var arg in args) { variantArgsArrayOffset -= variantStructSize; RdMarshal.GetNativeVariantForObject(arg, variantArgsArrayOffset); } pDispParams.rgvarg = variantArgsArray; } return(pDispParams); }
protected virtual void Dispose(bool disposing) { if (_isDisposed || !disposing) { return; } _isDisposed = true; if (WrappedObject != null) { RdMarshal.ReleaseComObject(WrappedObject); } if (_outerObject != IntPtr.Zero) { RdMarshal.Release(_outerObject); // dont set _outerObject to IntPtr.Zero here, as GetInterface() can still be called by the outer RCW // if it is still alive. For example, if ExtractWrappedObject was used and the outer object hasn't yet // been released with ReleaseComObject. In that circumstance _outerObject will still be a valid pointer // due to the internally held reference, and so GetInterface() calls past this point are still OK. } }
private void HandleBadHRESULT(int hr) { throw RdMarshal.GetExceptionForHR(hr); }
private void ConstructorObjectPrint() { Debug.Print($"ComPointer:: Created from object: pUnk: {RdMarshal.FormatPtr(_pUnk)} interface: {typeof(TComInterface).Name} - {Interface.GetHashCode()} addRef: {_addRef}"); }
private void ConstructorPointerPrint(int refCount) { Debug.Print($"ComPointer:: Created from pointer: pUnk: {RdMarshal.FormatPtr(_pUnk)} interface: {typeof(TComInterface).Name} - {Interface.GetHashCode()} addRef: {_addRef} refCount: {refCount}"); }