diff --git a/src/System.Windows.Forms/tests/InteropTests/NativeTests/AxTestControl.cpp b/src/System.Windows.Forms/tests/InteropTests/NativeTests/AxTestControl.cpp new file mode 100644 index 00000000000..9f2911dbb37 --- /dev/null +++ b/src/System.Windows.Forms/tests/InteropTests/NativeTests/AxTestControl.cpp @@ -0,0 +1,130 @@ +#include "AxTestControl.h" + +#pragma warning(push) +#pragma warning(disable: 4355) // 'this' : used in base member initializer list + +CAxTestControl::CAxTestControl() : m_ctlButton(_T("Button"), this, 1), m_count(0) +{ + m_bWindowOnly = TRUE; +} + +#pragma warning(pop) + +HRESULT CAxTestControl::FinalConstruct() +{ + return S_OK; +} + +void CAxTestControl::FinalRelease() +{ +} + +LRESULT CAxTestControl::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) +{ + LRESULT lRes = CComControl::OnSetFocus(uMsg, wParam, lParam, bHandled); + if (m_bInPlaceActive && !IsChild(::GetFocus())) + m_ctlButton.SetFocus(); + return lRes; +} + +LRESULT CAxTestControl::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) +{ + RECT rc; + GetWindowRect(&rc); + rc.right -= rc.left; + rc.bottom -= rc.top; + rc.top = rc.left = 0; + m_ctlButton.Create(m_hWnd, rc); + return 0; +} + +STDMETHODIMP CAxTestControl::SetObjectRects(LPCRECT prcPos, LPCRECT prcClip) +{ + IOleInPlaceObjectWindowlessImpl::SetObjectRects(prcPos, prcClip); + int cx = prcPos->right - prcPos->left; + int cy = prcPos->bottom - prcPos->top; + ::SetWindowPos(m_ctlButton.m_hWnd, NULL, 0, 0, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE); + return S_OK; +} + +void CAxTestControl::OnTextChanged() +{ + m_ctlButton.SetWindowText(CString(m_bstrText)); + Fire_OnTextChanged(m_bstrText); +} + +LRESULT CAxTestControl::OnButtonClicked(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) +{ + Fire_OnButtonClick(++m_count); + Fire_OnClick(); + return 0; +} + +HRESULT CAxTestControl::Fire_OnTextChanged(BSTR text) +{ + HRESULT hr = S_OK; + int cConnections = m_vec.GetSize(); + + for (int iConnection = 0; iConnection < cConnections; iConnection++) + { + CComPtr punkConnection = m_vec.GetAt(iConnection); + IDispatch* pConnection = static_cast(punkConnection.p); + + if (pConnection) + { + CComVariant avarParams[1]; + avarParams[0] = text; + CComVariant varResult; + DISPPARAMS params = { avarParams, NULL, 1, 0 }; + hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL); + } + } + + return hr; +} + +HRESULT CAxTestControl::Fire_OnButtonClick(LONG count) +{ + HRESULT hr = S_OK; + int cConnections = m_vec.GetSize(); + + for (int iConnection = 0; iConnection < cConnections; iConnection++) + { + CComPtr punkConnection = m_vec.GetAt(iConnection); + IDispatch* pConnection = static_cast(punkConnection.p); + + if (pConnection) + { + CComVariant avarParams[1]; + avarParams[0] = count; + CComVariant varResult; + DISPPARAMS params = { avarParams, NULL, 1, 0 }; + hr = pConnection->Invoke(2, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL); + } + } + + return hr; +} + +HRESULT CAxTestControl::Fire_OnClick() +{ + HRESULT hr = S_OK; + int cConnections = m_vec.GetSize(); + + for (int iConnection = 0; iConnection < cConnections; iConnection++) + { + CComPtr punkConnection = m_vec.GetAt(iConnection); + IDispatch* pConnection = static_cast(punkConnection.p); + + if (pConnection) + { + CComVariant varResult; + DISPPARAMS params = { NULL, NULL, 0, 0 }; + hr = pConnection->Invoke(DISPID_CLICK, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL); + } + } + + return hr; +} + +OBJECT_ENTRY_AUTO(__uuidof(AxTestControl), CAxTestControl); diff --git a/src/System.Windows.Forms/tests/InteropTests/NativeTests/AxTestControl.h b/src/System.Windows.Forms/tests/InteropTests/NativeTests/AxTestControl.h new file mode 100644 index 00000000000..fafeba4496d --- /dev/null +++ b/src/System.Windows.Forms/tests/InteropTests/NativeTests/AxTestControl.h @@ -0,0 +1,104 @@ +#pragma once + +#ifndef STRICT +#define STRICT +#endif + +#define _USRDLL +#define _ATL_APARTMENT_THREADED +#define _ATL_NO_AUTOMATIC_NAMESPACE +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS +#define ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW + +#include +#include +#include + +#include + +using namespace ATL; + +class ATL_NO_VTABLE CAxTestControl : + public CComObjectRootEx, + public CStockPropImpl, + public IPersistStreamInitImpl, + public IOleControlImpl, + public IOleObjectImpl, + public IOleInPlaceActiveObjectImpl, + public IViewObjectExImpl, + public IOleInPlaceObjectWindowlessImpl, + public IConnectionPointContainerImpl, + public IConnectionPointImpl, + public CComCoClass, + public CComControl +{ +public: + CContainedWindow m_ctlButton; + CComBSTR m_bstrText; + DWORD m_count; + + CAxTestControl(); + HRESULT FinalConstruct(); + void FinalRelease(); + + DECLARE_PROTECT_FINAL_CONSTRUCT() + DECLARE_NO_REGISTRY() + + DECLARE_OLEMISC_STATUS(0 + | OLEMISC_RECOMPOSEONRESIZE + | OLEMISC_ACTSLIKEBUTTON + | OLEMISC_CANTLINKINSIDE + | OLEMISC_INSIDEOUT + | OLEMISC_ACTIVATEWHENVISIBLE + | OLEMISC_SETCLIENTSITEFIRST + ) + + BEGIN_COM_MAP(CAxTestControl) + COM_INTERFACE_ENTRY(IAxTestControl) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(IViewObjectEx) + COM_INTERFACE_ENTRY(IViewObject2) + COM_INTERFACE_ENTRY(IViewObject) + COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY(IOleInPlaceObject) + COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY(IOleInPlaceActiveObject) + COM_INTERFACE_ENTRY(IOleControl) + COM_INTERFACE_ENTRY(IOleObject) + COM_INTERFACE_ENTRY(IPersistStreamInit) + COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit) + COM_INTERFACE_ENTRY(IConnectionPointContainer) + END_COM_MAP() + + BEGIN_PROP_MAP(CAxTestControl) + PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4) + PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4) + PROP_ENTRY_TYPE("Text", DISPID_TEXT, CLSID_NULL, VT_BSTR) + END_PROP_MAP() + + BEGIN_CONNECTION_POINT_MAP(CAxTestControl) + CONNECTION_POINT_ENTRY(__uuidof(_IAxTestControlEvents)) + END_CONNECTION_POINT_MAP() + + BEGIN_MSG_MAP(CAxTestControl) + MESSAGE_HANDLER(WM_CREATE, OnCreate) + MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) + COMMAND_CODE_HANDLER(BN_CLICKED, OnButtonClicked) + CHAIN_MSG_MAP(CComControl) + ALT_MSG_MAP(1) + END_MSG_MAP() + + LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + STDMETHOD(SetObjectRects)(LPCRECT prcPos, LPCRECT prcClip); + + // IViewObjectEx + DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE) + + // IAxTestControl + void OnTextChanged(); + LRESULT OnButtonClicked(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled); + HRESULT Fire_OnTextChanged(BSTR text); + HRESULT Fire_OnButtonClick(LONG count); + HRESULT Fire_OnClick(); +}; diff --git a/src/System.Windows.Forms/tests/InteropTests/NativeTests/CMakeLists.txt b/src/System.Windows.Forms/tests/InteropTests/NativeTests/CMakeLists.txt index 0b3caf6a27f..a0946407a01 100644 --- a/src/System.Windows.Forms/tests/InteropTests/NativeTests/CMakeLists.txt +++ b/src/System.Windows.Forms/tests/InteropTests/NativeTests/CMakeLists.txt @@ -32,6 +32,7 @@ add_compile_options($<$:/we4055>) # 'conversion' : from add_library(NativeTests SHARED AccessibleObjectTests.cpp + AxTestControl.cpp DllGetClassObject.cpp DispatchImpl.cpp RawErrorInfoUsageTest.cpp diff --git a/src/System.Windows.Forms/tests/InteropTests/NativeTests/Contract.idl b/src/System.Windows.Forms/tests/InteropTests/NativeTests/Contract.idl index 168f76d8b13..4bc45f7e2d3 100644 --- a/src/System.Windows.Forms/tests/InteropTests/NativeTests/Contract.idl +++ b/src/System.Windows.Forms/tests/InteropTests/NativeTests/Contract.idl @@ -1,3 +1,4 @@ +#include "olectl.h" import "oaidl.idl"; import "ocidl.idl"; @@ -11,6 +12,21 @@ interface IBasicTest : IDispatch [propput] HRESULT Int_Property([in] int val); }; +[ + object, + uuid(424ddab6-a4b1-422f-93b9-7e254b28dbf0), + dual, + nonextensible, + pointer_default(unique) +] +interface IAxTestControl : IDispatch +{ + [propput, bindable, requestedit, id(DISPID_TEXT)] + HRESULT Text([in] BSTR strText); + [propget, bindable, requestedit, id(DISPID_TEXT)] + HRESULT Text([out, retval] BSTR* pstrText); +}; + [ uuid(0971AD7E-3D4A-4C44-B0A3-A518AC88DFE1) ] @@ -35,4 +51,25 @@ library NativeTests [default] interface IBasicTest; interface ISupportErrorInfo; } + + [ + uuid(726D447D-8F66-4330-9531-F591011DC7B7) + ] + dispinterface _IAxTestControlEvents + { + properties: + methods: + [id(1)] void OnTextChanged([in] BSTR text); + [id(2)] void OnButtonClick([in] LONG count); + [id(DISPID_CLICK)] void OnClick(); + }; + + [ + uuid(4EABA135-7C8A-4DB8-B732-2A6AE511D7EB) + ] + coclass AxTestControl + { + [default] interface IAxTestControl; + [default, source] dispinterface _IAxTestControlEvents; + }; } diff --git a/src/System.Windows.Forms/tests/InteropTests/NativeTests/DllGetClassObject.cpp b/src/System.Windows.Forms/tests/InteropTests/NativeTests/DllGetClassObject.cpp index 6e0a11420e9..3a4b374cc77 100644 --- a/src/System.Windows.Forms/tests/InteropTests/NativeTests/DllGetClassObject.cpp +++ b/src/System.Windows.Forms/tests/InteropTests/NativeTests/DllGetClassObject.cpp @@ -8,6 +8,9 @@ #include "RawErrorInfoUsageTest.h" #include "StandardErrorInfoUsageTest.h" +#include "AxTestControl.h" + +class CNativeTestsModule : public ATL::CAtlDllModuleT {} _AtlModule; STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID FAR* ppv) { @@ -17,5 +20,8 @@ STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID FA if (rclsid == __uuidof(StandardErrorInfoUsageTest)) return ClassFactoryBasic::Create(riid, ppv); + if (rclsid == __uuidof(AxTestControl)) + return _AtlModule.DllGetClassObject(rclsid, riid, ppv); + return CLASS_E_CLASSNOTAVAILABLE; } diff --git a/src/System.Windows.Forms/tests/InteropTests/NativeTests/NativeTests.X.manifest b/src/System.Windows.Forms/tests/InteropTests/NativeTests/NativeTests.X.manifest index 5fdd87552cb..712825d41b0 100644 --- a/src/System.Windows.Forms/tests/InteropTests/NativeTests/NativeTests.X.manifest +++ b/src/System.Windows.Forms/tests/InteropTests/NativeTests/NativeTests.X.manifest @@ -15,6 +15,10 @@ + + diff --git a/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj b/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj index 0f381e8d2fd..44c740daf1d 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj +++ b/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj @@ -76,4 +76,15 @@ PreserveNewest + + + + + + + + + + + diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHost.AtlMfcTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHost.AtlMfcTests.cs new file mode 100644 index 00000000000..86d6f06828f --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHost.AtlMfcTests.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Runtime.InteropServices; +using Windows.Win32.System.ApplicationInstallationAndServicing; + +namespace System.Windows.Forms.Tests; + +public class AxHostAtlTests +{ + #region Utilities + + // taken from \src\System.Windows.Forms\tests\InteropTests\PropertyGridTests.cs + + [DllImport("kernel32", SetLastError = true)] + private static extern void ReleaseActCtx(IntPtr hActCtx); + + private unsafe void ExecuteWithActivationContext(string applicationManifest, Action action) + { + ACTCTXW context = new(); + HANDLE handle; + fixed (char* p = applicationManifest) + { + context.cbSize = (uint)sizeof(ACTCTXW); + context.lpSource = p; + + handle = PInvoke.CreateActCtx(&context); + } + + if (handle == IntPtr.Zero) + { + throw new Win32Exception(); + } + + try + { + nuint cookie; + if (!PInvoke.ActivateActCtx(handle, &cookie)) + { + throw new Win32Exception(); + } + + try + { + action(); + } + finally + { + if (!PInvoke.DeactivateActCtx(0, cookie)) + { + throw new Win32Exception(); + } + } + } + finally + { + ReleaseActCtx(handle); + } + } + + #endregion + + [WinFormsFact] + public void AxHost_AtlControl_CreateAndSetText() + { + if (RuntimeInformation.ProcessArchitecture != Architecture.X86) + { + return; + } + + ExecuteWithActivationContext("App.manifest", () => + { + using Form form = new(); + using AxNativeAtlControl control = new(); + + int textChangedEventCount = 0; + string textChangedEventArg = null; + const string testText = "Hello World"; + + control.AxTextChanged += (string text) => + { + textChangedEventCount++; + textChangedEventArg = text; + }; + + form.Shown += (object sender, EventArgs e) => + { + control.AxText = testText; + form.Close(); + }; + + ((ISupportInitialize)control).BeginInit(); + form.Controls.Add(control); + ((ISupportInitialize)control).EndInit(); + form.ShowDialog(); + + Assert.Equal(1, textChangedEventCount); + Assert.Equal(testText, textChangedEventArg); + }); + } + + private unsafe class AxNativeAtlControl : AxHost + { + private const int DISPID_TEXT = -517; + private const int DISPID_CLICK = -600; + + [ComImport] + [Guid("424ddab6-a4b1-422f-93b9-7e254b28dbf0")] + [InterfaceType(ComInterfaceType.InterfaceIsDual)] + private interface IAxTestControl + { + [DispId(DISPID_TEXT)] string Text { set; get; } + } + + [ComImport] + [Guid("726D447D-8F66-4330-9531-F591011DC7B7")] + [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] + private interface IAxTestControlEvents + { + [DispId(1)] void OnTextChanged([MarshalAs(UnmanagedType.BStr)] string text); + [DispId(2)] void OnButtonClick(int count); + [DispId(DISPID_CLICK)] void OnClick(); + } + + private sealed class AxNativeAtlControlEventHandler(AxNativeAtlControl control) : IAxTestControlEvents + { + private readonly AxNativeAtlControl _control = control; + void IAxTestControlEvents.OnTextChanged(string text) => _control.AxTextChanged?.Invoke(text); + void IAxTestControlEvents.OnButtonClick(int count) => _control.AxButtonClick?.Invoke(count); + void IAxTestControlEvents.OnClick() => _control.AxClick?.Invoke(); + } + + public event Action AxTextChanged; + public event Action AxButtonClick; + public event Action AxClick; + + private ConnectionPointCookie cookie; + public AxNativeAtlControl() : base("4EABA135-7C8A-4DB8-B732-2A6AE511D7EB") { } + protected override void CreateSink() => this.cookie = new(GetOcx(), new AxNativeAtlControlEventHandler(this), typeof(IAxTestControlEvents)); + protected override void DetachSink() => this.cookie.Disconnect(); + + public string AxText + { + get => ((IAxTestControl)GetOcx()).Text; + set => ((IAxTestControl)GetOcx()).Text = value; + } + } +}