diff --git a/rostests/winetests/ole32/CMakeLists.txt b/rostests/winetests/ole32/CMakeLists.txt index 1f706576332..5ecea1b3405 100644 --- a/rostests/winetests/ole32/CMakeLists.txt +++ b/rostests/winetests/ole32/CMakeLists.txt @@ -22,4 +22,9 @@ add_executable(ole32_winetest ${SOURCE}) target_link_libraries(ole32_winetest uuid) set_module_type(ole32_winetest win32cui) add_importlibs(ole32_winetest oleaut32 ole32 user32 gdi32 advapi32 msvcrt kernel32) + +if(MSVC) + add_importlibs(ole32_winetest ntdll) +endif() + add_cd_file(TARGET ole32_winetest DESTINATION reactos/bin FOR all) diff --git a/rostests/winetests/ole32/clipboard.c b/rostests/winetests/ole32/clipboard.c index 4581213a782..fabd31d5f98 100644 --- a/rostests/winetests/ole32/clipboard.c +++ b/rostests/winetests/ole32/clipboard.c @@ -102,7 +102,7 @@ static HRESULT WINAPI EnumFormatImpl_QueryInterface(IEnumFORMATETC *iface, REFII if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_IEnumFORMATETC)) { IEnumFORMATETC_AddRef(iface); - *ppvObj = This; + *ppvObj = &This->IEnumFORMATETC_iface; return S_OK; } *ppvObj = NULL; @@ -135,7 +135,8 @@ static HRESULT WINAPI EnumFormatImpl_Next(IEnumFORMATETC *iface, ULONG celt, EnumFormatImpl *This = impl_from_IEnumFORMATETC(iface); ULONG count, i; - trace("next: count %d cur %d\n", celt, This->cur); + if (winetest_debug > 1) + trace("next: count %d cur %d\n", celt, This->cur); if(!rgelt) return E_INVALIDARG; @@ -207,7 +208,7 @@ static HRESULT WINAPI DataObjectImpl_QueryInterface(IDataObject *iface, REFIID r if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_IDataObject)) { IDataObject_AddRef(iface); - *ppvObj = This; + *ppvObj = &This->IDataObject_iface; return S_OK; } *ppvObj = NULL; diff --git a/rostests/winetests/ole32/compobj.c b/rostests/winetests/ole32/compobj.c index df13469e098..b4fdd4736a3 100644 --- a/rostests/winetests/ole32/compobj.c +++ b/rostests/winetests/ole32/compobj.c @@ -1563,7 +1563,7 @@ static void test_registered_object_thread_affinity(void) ok( !WaitForSingleObject(thread, 10000), "wait timed out\n" ); GetExitCodeThread(thread, &exitcode); hr = exitcode; - ok(hr == RPC_E_WRONG_THREAD, "CoRevokeClassObject called from different " + ok(hr == RPC_E_WRONG_THREAD || broken(hr == S_OK) /* win8 */, "CoRevokeClassObject called from different " "thread to where registered should return RPC_E_WRONG_THREAD instead of 0x%08x\n", hr); thread = CreateThread(NULL, 0, register_class_object_thread, NULL, 0, &tid); @@ -1973,7 +1973,12 @@ static void test_TreatAsClass(void) ok(lr == ERROR_SUCCESS, "Couldn't open CLSID key\n"); lr = RegCreateKeyExA(clsidkey, deadbeefA, 0, NULL, 0, KEY_WRITE, NULL, &deadbeefkey, NULL); - ok(lr == ERROR_SUCCESS, "Couldn't create class key\n"); + if (lr) { + win_skip("CoGetTreatAsClass() tests will be skipped (failed to create a test key, error %d)\n", + GetLastError()); + RegCloseKey(clsidkey); + return; + } hr = pCoTreatAsClass(&deadbeef, &deadbeef); ok(hr == REGDB_E_WRITEREGDB, "CoTreatAsClass gave wrong error: %08x\n", hr); diff --git a/rostests/winetests/ole32/dragdrop.c b/rostests/winetests/ole32/dragdrop.c index 4b1eeb7beb9..b3d712e794f 100644 --- a/rostests/winetests/ole32/dragdrop.c +++ b/rostests/winetests/ole32/dragdrop.c @@ -452,13 +452,16 @@ static void test_Register_Revoke(void) ok(droptarget_refs >= 1, "DropTarget refs should be at least one\n"); OleUninitialize(); - ok(droptarget_refs >= 1, "DropTarget refs should be at least one\n"); - hr = RevokeDragDrop(hwnd); - ok_ole_success(hr, "RevokeDragDrop"); - ok(droptarget_refs == 0 || - broken(droptarget_refs == 1), /* NT4 */ - "DropTarget refs should be zero not %d\n", droptarget_refs); + /* Win 8 releases the ref in OleUninitialize() */ + if (droptarget_refs >= 1) + { + hr = RevokeDragDrop(hwnd); + ok_ole_success(hr, "RevokeDragDrop"); + ok(droptarget_refs == 0 || + broken(droptarget_refs == 1), /* NT4 */ + "DropTarget refs should be zero not %d\n", droptarget_refs); + } hr = RevokeDragDrop(NULL); ok(hr == DRAGDROP_E_INVALIDHWND, "RevokeDragDrop with NULL hwnd should return DRAGDROP_E_INVALIDHWND instead of 0x%08x\n", hr); diff --git a/rostests/winetests/ole32/ole2.c b/rostests/winetests/ole32/ole2.c index a03e923b19e..ef4d0f168ce 100644 --- a/rostests/winetests/ole32/ole2.c +++ b/rostests/winetests/ole32/ole2.c @@ -1688,6 +1688,113 @@ static void test_data_cache(void) IStorage_Release(pStorage); } + +static const WCHAR CONTENTS[] = {'C','O','N','T','E','N','T','S',0}; + +/* 2 x 1 x 32 bpp dib. PelsPerMeter = 200x400 */ +static BYTE dib[] = +{ + 0x42, 0x4d, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, + + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, + 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static IStorage *create_storage( int num ) +{ + IStorage *stg; + IStream *stm; + HRESULT hr; + ULONG written; + + hr = StgCreateDocfile( NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_DELETEONRELEASE, 0, &stg ); + ok( hr == S_OK, "got %08x\n", hr); + hr = IStorage_SetClass( stg, &CLSID_Picture_Dib ); + ok( hr == S_OK, "got %08x\n", hr); + hr = IStorage_CreateStream( stg, CONTENTS, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE, 0, 0, &stm ); + ok( hr == S_OK, "got %08x\n", hr); + if (num == 1) /* Set biXPelsPerMeter = 0 */ + { + dib[0x26] = 0; + dib[0x27] = 0; + } + hr = IStream_Write( stm, dib, sizeof(dib), &written ); + ok( hr == S_OK, "got %08x\n", hr); + IStream_Release( stm ); + return stg; +} + +static void test_data_cache_dib_contents_stream(int num) +{ + HRESULT hr; + IUnknown *unk; + IPersistStorage *persist; + IDataObject *data; + IViewObject2 *view; + IStorage *stg; + FORMATETC fmt = {CF_DIB, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM med; + CLSID cls; + SIZEL sz; + + hr = CreateDataCache( NULL, &CLSID_Picture_Metafile, &IID_IUnknown, (void *)&unk ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + hr = IUnknown_QueryInterface( unk, &IID_IPersistStorage, (void *)&persist ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + hr = IUnknown_QueryInterface( unk, &IID_IDataObject, (void *)&data ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + hr = IUnknown_QueryInterface( unk, &IID_IViewObject2, (void *)&view ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + + stg = create_storage( num ); + + hr = IPersistStorage_Load( persist, stg ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + IStorage_Release( stg ); + + hr = IPersistStorage_GetClassID( persist, &cls ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + ok( IsEqualCLSID( &cls, &CLSID_Picture_Dib ), "class id mismatch\n" ); + + hr = IDataObject_GetData( data, &fmt, &med ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + if (SUCCEEDED(hr)) + { + ok( med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed ); + ReleaseStgMedium( &med ); + } + + hr = IViewObject2_GetExtent( view, DVASPECT_CONTENT, -1, NULL, &sz ); + ok( SUCCEEDED(hr), "got %08x\n", hr ); + if (num == 0) + { + ok( sz.cx == 1000, "got %d\n", sz.cx ); + ok( sz.cy == 250, "got %d\n", sz.cy ); + } + else + { + HDC hdc = GetDC( 0 ); + LONG x = 2 * 2540 / GetDeviceCaps( hdc, LOGPIXELSX ); + LONG y = 1 * 2540 / GetDeviceCaps( hdc, LOGPIXELSY ); + ok( sz.cx == x, "got %d %d\n", sz.cx, x ); + ok( sz.cy == y, "got %d %d\n", sz.cy, y ); + + ReleaseDC( 0, hdc ); + } + + IViewObject2_Release( view ); + IDataObject_Release( data ); + IPersistStorage_Release( persist ); + IUnknown_Release( unk ); +} + static void test_default_handler(void) { HRESULT hr; @@ -1970,14 +2077,90 @@ static const IUnknownVtbl UnknownVtbl = Unknown_Release }; +static HRESULT WINAPI OleRun_QueryInterface(IRunnableObject *iface, REFIID riid, void **ppv) +{ + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IRunnableObject)) { + *ppv = iface; + } + + if (*ppv) + { + IUnknown_AddRef((IUnknown *)*ppv); + return S_OK; + } + + return E_NOINTERFACE; +} + +static ULONG WINAPI OleRun_AddRef(IRunnableObject *iface) +{ + return 2; +} + +static ULONG WINAPI OleRun_Release(IRunnableObject *iface) +{ + return 1; +} + +static HRESULT WINAPI OleRun_GetRunningClass(IRunnableObject *iface, CLSID *clsid) +{ + ok(0, "unxpected\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI OleRun_Run(IRunnableObject *iface, LPBINDCTX ctx) +{ + ok(ctx == NULL, "got %p\n", ctx); + return 0xdeadc0de; +} + +static BOOL WINAPI OleRun_IsRunning(IRunnableObject *iface) +{ + ok(0, "unxpected\n"); + return FALSE; +} + +static HRESULT WINAPI OleRun_LockRunning(IRunnableObject *iface, BOOL lock, + BOOL last_unlock_closes) +{ + ok(0, "unxpected\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI OleRun_SetContainedObject(IRunnableObject *iface, BOOL contained) +{ + ok(0, "unxpected\n"); + return E_NOTIMPL; +} + +static const IRunnableObjectVtbl oleruntestvtbl = +{ + OleRun_QueryInterface, + OleRun_AddRef, + OleRun_Release, + OleRun_GetRunningClass, + OleRun_Run, + OleRun_IsRunning, + OleRun_LockRunning, + OleRun_SetContainedObject +}; + static IUnknown unknown = { &UnknownVtbl }; +static IRunnableObject testrunnable = { &oleruntestvtbl }; static void test_OleRun(void) { HRESULT hr; + /* doesn't support IRunnableObject */ hr = OleRun(&unknown); ok(hr == S_OK, "OleRun failed 0x%08x\n", hr); + + hr = OleRun((IUnknown*)&testrunnable); + ok(hr == 0xdeadc0de, "got 0x%08x\n", hr); } static void test_OleLockRunning(void) @@ -2393,6 +2576,8 @@ START_TEST(ole2) ok_ole_success(hr, "CoRevokeClassObject"); test_data_cache(); + test_data_cache_dib_contents_stream( 0 ); + test_data_cache_dib_contents_stream( 1 ); test_default_handler(); test_runnable(); test_OleRun(); diff --git a/rostests/winetests/ole32/storage32.c b/rostests/winetests/ole32/storage32.c index 360ff67187c..c3bd25bf290 100644 --- a/rostests/winetests/ole32/storage32.c +++ b/rostests/winetests/ole32/storage32.c @@ -61,6 +61,201 @@ static int strcmp_ww(LPCWSTR strw1, LPCWSTR strw2) return lstrcmpA(stra1, stra2); } +typedef struct TestLockBytes { + ILockBytes ILockBytes_iface; + LONG ref; + BYTE* contents; + ULONG size; + ULONG buffer_size; + HRESULT lock_hr; + ULONG locks_supported; + ULONG lock_called; +} TestLockBytes; + +static inline TestLockBytes *impl_from_ILockBytes(ILockBytes *iface) +{ + return CONTAINING_RECORD(iface, TestLockBytes, ILockBytes_iface); +} + +static HRESULT WINAPI TestLockBytes_QueryInterface(ILockBytes *iface, REFIID iid, + void **ppv) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + + if (!ppv) return E_INVALIDARG; + + if (IsEqualIID(&IID_IUnknown, iid) || + IsEqualIID(&IID_ILockBytes, iid)) + *ppv = &This->ILockBytes_iface; + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI TestLockBytes_AddRef(ILockBytes *iface) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + ULONG ref = InterlockedIncrement(&This->ref); + return ref; +} + +static ULONG WINAPI TestLockBytes_Release(ILockBytes *iface) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + ULONG ref = InterlockedDecrement(&This->ref); + return ref; +} + +static HRESULT WINAPI TestLockBytes_ReadAt(ILockBytes *iface, + ULARGE_INTEGER ulOffset, void *pv, ULONG cb, ULONG *pcbRead) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + ULONG dummy; + + if (!pv) return E_INVALIDARG; + + if (!pcbRead) pcbRead = &dummy; + + if (ulOffset.QuadPart >= This->size) + { + *pcbRead = 0; + return S_OK; + } + + cb = min(cb, This->size - ulOffset.QuadPart); + + *pcbRead = cb; + memcpy(pv, &This->contents[ulOffset.QuadPart], cb); + + return S_OK; +} + +static HRESULT WINAPI TestLockBytes_WriteAt(ILockBytes *iface, + ULARGE_INTEGER ulOffset, const void *pv, ULONG cb, ULONG *pcbWritten) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + HRESULT hr; + ULONG dummy; + + if (!pv) return E_INVALIDARG; + + if (!pcbWritten) pcbWritten = &dummy; + + if (ulOffset.QuadPart + cb > This->size) + { + ULARGE_INTEGER new_size; + new_size.QuadPart = ulOffset.QuadPart + cb; + hr = ILockBytes_SetSize(iface, new_size); + if (FAILED(hr)) return hr; + } + + *pcbWritten = cb; + memcpy(&This->contents[ulOffset.QuadPart], pv, cb); + + return S_OK; +} + +static HRESULT WINAPI TestLockBytes_Flush(ILockBytes *iface) +{ + return S_OK; +} + +static HRESULT WINAPI TestLockBytes_SetSize(ILockBytes *iface, + ULARGE_INTEGER cb) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + + if (This->buffer_size < cb.QuadPart) + { + ULONG new_buffer_size = max(This->buffer_size * 2, cb.QuadPart); + BYTE* new_buffer = HeapAlloc(GetProcessHeap(), 0, new_buffer_size); + if (!new_buffer) return E_OUTOFMEMORY; + memcpy(new_buffer, This->contents, This->size); + HeapFree(GetProcessHeap(), 0, This->contents); + This->contents = new_buffer; + } + + if (cb.QuadPart > This->size) + memset(&This->contents[This->size], 0, cb.QuadPart - This->size); + + This->size = cb.QuadPart; + + return S_OK; +} + +static HRESULT WINAPI TestLockBytes_LockRegion(ILockBytes *iface, + ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + This->lock_called++; + return This->lock_hr; +} + +static HRESULT WINAPI TestLockBytes_UnlockRegion(ILockBytes *iface, + ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + return This->lock_hr; +} + +static HRESULT WINAPI TestLockBytes_Stat(ILockBytes *iface, + STATSTG *pstatstg, DWORD grfStatFlag) +{ + TestLockBytes *This = impl_from_ILockBytes(iface); + static const WCHAR dummy_name[] = {'d','u','m','m','y',0}; + + if (!pstatstg) return E_INVALIDARG; + + memset(pstatstg, 0, sizeof(STATSTG)); + + if (!(grfStatFlag & STATFLAG_NONAME)) + { + pstatstg->pwcsName = CoTaskMemAlloc(sizeof(dummy_name)); + if (!pstatstg->pwcsName) return E_OUTOFMEMORY; + memcpy(pstatstg->pwcsName, dummy_name, sizeof(dummy_name)); + } + + pstatstg->type = STGTY_LOCKBYTES; + pstatstg->cbSize.QuadPart = This->size; + pstatstg->grfLocksSupported = This->locks_supported; + + return S_OK; +} + +static ILockBytesVtbl TestLockBytes_Vtbl = { + TestLockBytes_QueryInterface, + TestLockBytes_AddRef, + TestLockBytes_Release, + TestLockBytes_ReadAt, + TestLockBytes_WriteAt, + TestLockBytes_Flush, + TestLockBytes_SetSize, + TestLockBytes_LockRegion, + TestLockBytes_UnlockRegion, + TestLockBytes_Stat +}; + +static void CreateTestLockBytes(TestLockBytes **This) +{ + *This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(**This)); + + if (*This) + { + (*This)->ILockBytes_iface.lpVtbl = &TestLockBytes_Vtbl; + (*This)->ref = 1; + } +} + +static void DeleteTestLockBytes(TestLockBytes *This) +{ + ok(This->ILockBytes_iface.lpVtbl == &TestLockBytes_Vtbl, "test lock bytes %p deleted with incorrect vtable\n", This); + ok(This->ref == 1, "test lock bytes %p deleted with %i references instead of 1\n", This, This->ref); + HeapFree(GetProcessHeap(), 0, This->contents); + HeapFree(GetProcessHeap(), 0, This); +} + static void test_hglobal_storage_stat(void) { ILockBytes *ilb = NULL; @@ -3157,22 +3352,22 @@ struct lock_test BOOL todo; }; -static const int priority_locked_bytes[] = { 0x58, 0x81, 0x93, -1 }; -static const int rwex_locked_bytes[] = { 0x93, 0xa7, 0xbb, 0xcf, -1 }; -static const int rw_locked_bytes[] = { 0x93, 0xa7, -1 }; -static const int nosn_locked_bytes[] = { 0x6c, 0x93, 0xa7, 0xcf, -1 }; -static const int rwdw_locked_bytes[] = { 0x93, 0xa7, 0xcf, -1 }; -static const int wodw_locked_bytes[] = { 0xa7, 0xcf, -1 }; -static const int tr_locked_bytes[] = { 0x93, -1 }; +static const int priority_locked_bytes[] = { 0x158, 0x181, 0x193, -1 }; +static const int rwex_locked_bytes[] = { 0x193, 0x1a7, 0x1bb, 0x1cf, -1 }; +static const int rw_locked_bytes[] = { 0x193, 0x1a7, -1 }; +static const int nosn_locked_bytes[] = { 0x16c, 0x193, 0x1a7, 0x1cf, -1 }; +static const int rwdw_locked_bytes[] = { 0x193, 0x1a7, 0x1cf, -1 }; +static const int wodw_locked_bytes[] = { 0x1a7, 0x1cf, -1 }; +static const int tr_locked_bytes[] = { 0x193, -1 }; static const int no_locked_bytes[] = { -1 }; -static const int roex_locked_bytes[] = { 0x93, 0xbb, 0xcf, -1 }; +static const int roex_locked_bytes[] = { 0x193, 0x1bb, 0x1cf, -1 }; -static const int rwex_fail_ranges[] = { 0x93,0xe3, -1 }; -static const int rw_fail_ranges[] = { 0xbb,0xe3, -1 }; -static const int rwdw_fail_ranges[] = { 0xa7,0xe3, -1 }; -static const int dw_fail_ranges[] = { 0xa7,0xcf, -1 }; -static const int tr_fail_ranges[] = { 0xbb,0xcf, -1 }; -static const int pr_fail_ranges[] = { 0x80,0x81, 0xbb,0xcf, -1 }; +static const int rwex_fail_ranges[] = { 0x193,0x1e3, -1 }; +static const int rw_fail_ranges[] = { 0x1bb,0x1e3, -1 }; +static const int rwdw_fail_ranges[] = { 0x1a7,0x1e3, -1 }; +static const int dw_fail_ranges[] = { 0x1a7,0x1cf, -1 }; +static const int tr_fail_ranges[] = { 0x1bb,0x1cf, -1 }; +static const int pr_fail_ranges[] = { 0x180,0x181, 0x1bb,0x1cf, -1 }; static const int roex_fail_ranges[] = { 0x0,-1 }; static const struct lock_test lock_tests[] = { @@ -3189,8 +3384,8 @@ static const struct lock_test lock_tests[] = { { STGM_READWRITE|STGM_TRANSACTED|STGM_SHARE_DENY_WRITE, FALSE, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, rwdw_locked_bytes, rwdw_fail_ranges, FALSE }, { STGM_READ|STGM_SHARE_DENY_WRITE, FALSE, GENERIC_READ, FILE_SHARE_READ, no_locked_bytes, dw_fail_ranges, TRUE }, { STGM_READ|STGM_TRANSACTED, FALSE, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, tr_locked_bytes, tr_fail_ranges, FALSE }, - { STGM_READ|STGM_SHARE_EXCLUSIVE, FALSE, GENERIC_READ, FILE_SHARE_READ, roex_locked_bytes, roex_fail_ranges, TRUE }, - { STGM_READ|STGM_SHARE_EXCLUSIVE|STGM_TRANSACTED, FALSE, GENERIC_READ, FILE_SHARE_READ, roex_locked_bytes, roex_fail_ranges, TRUE }, + { STGM_READ|STGM_SHARE_EXCLUSIVE, FALSE, GENERIC_READ, FILE_SHARE_READ, roex_locked_bytes, roex_fail_ranges, FALSE }, + { STGM_READ|STGM_SHARE_EXCLUSIVE|STGM_TRANSACTED, FALSE, GENERIC_READ, FILE_SHARE_READ, roex_locked_bytes, roex_fail_ranges, FALSE }, }; static BOOL can_open(LPCWSTR filename, DWORD access, DWORD sharing) @@ -3317,7 +3512,7 @@ static void test_locking(void) ol.u.s.OffsetHigh = 0; ol.hEvent = NULL; - for (ol.u.s.Offset = 0x7fffff00; ol.u.s.Offset != 0x80000000; ol.u.s.Offset++) + for (ol.u.s.Offset = 0x7ffffe00; ol.u.s.Offset != 0x80000000; ol.u.s.Offset++) { if (LockFileEx(hfile, LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ol)) locked = FALSE; @@ -3329,7 +3524,7 @@ static void test_locking(void) UnlockFileEx(hfile, 0, 1, 0, &ol); - if ((ol.u.s.Offset&0xff) == *next_lock) + if ((ol.u.s.Offset&0x1ff) == *next_lock) { expect_locked = TRUE; next_lock++; @@ -3366,14 +3561,17 @@ static void test_locking(void) ol.u.s.OffsetHigh = 0; ol.hEvent = NULL; - for (ol.u.s.Offset = 0x7fffff00; ol.u.s.Offset != 0x80000000; ol.u.s.Offset++) + for (ol.u.s.Offset = 0x7ffffe00; ol.u.s.Offset != 0x80000000; ol.u.s.Offset++) { if (ol.u.s.Offset == 0x7fffff92 || (ol.u.s.Offset == 0x7fffff80 && current->stg_mode == (STGM_TRANSACTED|STGM_READWRITE)) || (ol.u.s.Offset == 0x7fffff80 && current->stg_mode == (STGM_TRANSACTED|STGM_READ))) continue; /* This makes opens hang */ - LockFileEx(hfile, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ol); + if (ol.u.s.Offset < 0x7fffff00) + LockFileEx(hfile, 0, 0, 1, 0, &ol); + else + LockFileEx(hfile, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ol); hr = StgOpenStorage(filename, NULL, current->stg_mode, NULL, 0, &stg); ok(hr == S_OK || hr == STG_E_LOCKVIOLATION || hr == STG_E_SHAREVIOLATION, "failed with unexpected hr %x\n", hr); @@ -3383,11 +3581,11 @@ static void test_locking(void) failed = FAILED(hr); - if (!expect_failed && (ol.u.s.Offset&0xff) == next_range[0]) + if (!expect_failed && (ol.u.s.Offset&0x1ff) == next_range[0]) { expect_failed = TRUE; } - else if (expect_failed && (ol.u.s.Offset&0xff) == next_range[1]) + else if (expect_failed && (ol.u.s.Offset&0x1ff) == next_range[1]) { expect_failed = FALSE; next_range += 2; @@ -3646,6 +3844,54 @@ static void test_overwrite(void) DeleteFileA(filenameA); } +static void test_custom_lockbytes(void) +{ + static const WCHAR stmname[] = { 'C','O','N','T','E','N','T','S',0 }; + TestLockBytes* lockbytes; + HRESULT hr; + IStorage* stg; + IStream* stm; + + CreateTestLockBytes(&lockbytes); + + hr = StgCreateDocfileOnILockBytes(&lockbytes->ILockBytes_iface, STGM_CREATE|STGM_READWRITE|STGM_TRANSACTED, 0, &stg); + ok(hr==S_OK, "StgCreateDocfileOnILockBytes failed %x\n", hr); + + hr = IStorage_CreateStream(stg, stmname, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); + ok(hr==S_OK, "IStorage_CreateStream failed %x\n", hr); + + IStream_Release(stm); + + hr = IStorage_Commit(stg, 0); + + IStorage_Release(stg); + + ok(!lockbytes->lock_called, "unexpected call to LockRegion\n"); + + lockbytes->locks_supported = LOCK_WRITE|LOCK_EXCLUSIVE|LOCK_ONLYONCE; + + hr = StgCreateDocfileOnILockBytes(&lockbytes->ILockBytes_iface, STGM_CREATE|STGM_READWRITE|STGM_TRANSACTED, 0, &stg); + ok(hr==S_OK, "StgCreateDocfileOnILockBytes failed %x\n", hr); + + hr = IStorage_CreateStream(stg, stmname, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); + ok(hr==S_OK, "IStorage_CreateStream failed %x\n", hr); + + IStream_Release(stm); + + hr = IStorage_Commit(stg, 0); + + IStorage_Release(stg); + + ok(lockbytes->lock_called, "expected LockRegion to be called\n"); + + lockbytes->lock_hr = STG_E_INVALIDFUNCTION; + + hr = StgCreateDocfileOnILockBytes(&lockbytes->ILockBytes_iface, STGM_CREATE|STGM_READWRITE|STGM_TRANSACTED, 0, &stg); + ok(hr==STG_E_INVALIDFUNCTION, "StgCreateDocfileOnILockBytes failed %x\n", hr); + + DeleteTestLockBytes(lockbytes); +} + START_TEST(storage32) { CHAR temp[MAX_PATH]; @@ -3694,4 +3940,5 @@ START_TEST(storage32) test_locking(); test_transacted_shared(); test_overwrite(); + test_custom_lockbytes(); } diff --git a/rostests/winetests/ole32/usrmarshal.c b/rostests/winetests/ole32/usrmarshal.c index 28d4ee63cd1..395ea4e1f45 100644 --- a/rostests/winetests/ole32/usrmarshal.c +++ b/rostests/winetests/ole32/usrmarshal.c @@ -50,13 +50,22 @@ unsigned char * __RPC_USER HMETAFILEPICT_UserMarshal (ULONG *, unsigned char *, unsigned char * __RPC_USER HMETAFILEPICT_UserUnmarshal(ULONG *, unsigned char *, HMETAFILEPICT *); void __RPC_USER HMETAFILEPICT_UserFree(ULONG *, HMETAFILEPICT *); +ULONG __RPC_USER HBRUSH_UserSize(ULONG *, ULONG, HBRUSH *); +unsigned char * __RPC_USER HBRUSH_UserMarshal(ULONG *, unsigned char *, HBRUSH *); +unsigned char * __RPC_USER HBRUSH_UserUnmarshal(ULONG *, unsigned char *, HBRUSH *); +void __RPC_USER HBRUSH_UserFree(ULONG *, HBRUSH *); + +static BOOL g_expect_user_alloc; static void * WINAPI user_allocate(SIZE_T size) { + ok(g_expect_user_alloc, "unexpected user_allocate call\n"); return CoTaskMemAlloc(size); } +static BOOL g_expect_user_free; static void WINAPI user_free(void *p) { + ok(g_expect_user_free, "unexpected user_free call\n"); CoTaskMemFree(p); } @@ -207,7 +216,6 @@ static void test_marshal_HGLOBAL(void) GlobalUnlock(hglobal); actual_size = GlobalSize(hglobal); expected_size = actual_size + 5 * sizeof(DWORD); - trace("%d: actual size %d\n", block_size, actual_size); init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); size = HGLOBAL_UserSize(&umcb.Flags, 0, &hglobal); /* native is poorly programmed and allocates 4/8 bytes more than it needs to @@ -410,7 +418,6 @@ static void test_marshal_HMETAFILEPICT(void) init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_DIFFERENTMACHINE); size = HMETAFILEPICT_UserSize(&umcb.Flags, 0, &hmfp); ok(size > 20, "size should be at least 20 bytes, not %d\n", size); - trace("size is %d\n", size); buffer = HeapAlloc(GetProcessHeap(), 0, size); init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_DIFFERENTMACHINE); buffer_end = HMETAFILEPICT_UserMarshal(&umcb.Flags, buffer, &hmfp); @@ -478,6 +485,17 @@ static void test_marshal_HMETAFILEPICT(void) HMETAFILEPICT_UserFree(&umcb.Flags, &hmfp2); } +typedef struct +{ + IUnknown IUnknown_iface; + LONG refs; +} TestUnknown; + +static inline TestUnknown *impl_from_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, TestUnknown, IUnknown_iface); +} + static HRESULT WINAPI Test_IUnknown_QueryInterface( LPUNKNOWN iface, REFIID riid, @@ -498,12 +516,14 @@ static HRESULT WINAPI Test_IUnknown_QueryInterface( static ULONG WINAPI Test_IUnknown_AddRef(LPUNKNOWN iface) { - return 2; /* non-heap-based object */ + TestUnknown *This = impl_from_IUnknown(iface); + return InterlockedIncrement(&This->refs); } static ULONG WINAPI Test_IUnknown_Release(LPUNKNOWN iface) { - return 1; /* non-heap-based object */ + TestUnknown *This = impl_from_IUnknown(iface); + return InterlockedDecrement(&This->refs); } static const IUnknownVtbl TestUnknown_Vtbl = @@ -548,7 +568,7 @@ static const IStreamVtbl TestStream_Vtbl = /* the rest can be NULLs */ }; -static IUnknown Test_Unknown = { &TestUnknown_Vtbl }; +static TestUnknown Test_Unknown = { {&TestUnknown_Vtbl}, 1 }; static IStream Test_Stream = { &TestStream_Vtbl }; ULONG __RPC_USER WdtpInterfacePointer_UserSize(ULONG *, ULONG, ULONG, IUnknown *, REFIID); @@ -588,7 +608,8 @@ static void marshal_WdtpInterfacePointer(DWORD umcb_ctx, DWORD ctx) /* Now for a non-NULL pointer. The marshalled data are two size DWORDS and then the result of CoMarshalInterface called with the LOWORD of the ctx */ - unk = &Test_Unknown; + unk = &Test_Unknown.IUnknown_iface; + Test_Unknown.refs = 1; CreateStreamOnHGlobal(h, TRUE, &stm); CoMarshalInterface(stm, &IID_IUnknown, unk, LOWORD(ctx), NULL, MSHLFLAGS_NORMAL); @@ -596,15 +617,17 @@ static void marshal_WdtpInterfacePointer(DWORD umcb_ctx, DWORD ctx) IStream_Seek(stm, zero, STREAM_SEEK_CUR, &pos); marshal_size = pos.u.LowPart; marshal_data = GlobalLock(h); - trace("marshal_size %x\n", marshal_size); +todo_wine + ok(Test_Unknown.refs == 2, "got %d\n", Test_Unknown.refs); init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, umcb_ctx); size = WdtpInterfacePointer_UserSize(&umcb.Flags, ctx, 0, unk, &IID_IUnknown); ok(size >= marshal_size + 2 * sizeof(DWORD), "marshal size %x got %x\n", marshal_size, size); - trace("WdtpInterfacePointer_UserSize returned %x\n", size); buffer = HeapAlloc(GetProcessHeap(), 0, size); init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, umcb_ctx); buffer_end = WdtpInterfacePointer_UserMarshal(&umcb.Flags, ctx, buffer, unk, &IID_IUnknown); +todo_wine + ok(Test_Unknown.refs == 2, "got %d\n", Test_Unknown.refs); wireip = buffer; ok(buffer_end == buffer + marshal_size + 2 * sizeof(DWORD), "buffer_end %p buffer %p\n", buffer_end, buffer); @@ -625,6 +648,7 @@ static void marshal_WdtpInterfacePointer(DWORD umcb_ctx, DWORD ctx) init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, umcb_ctx); WdtpInterfacePointer_UserUnmarshal(&umcb.Flags, buffer, &unk2, &IID_IUnknown); ok(unk2 != NULL, "IUnknown object didn't unmarshal properly\n"); + ok(Test_Unknown.refs == 2, "got %d\n", Test_Unknown.refs); HeapFree(GetProcessHeap(), 0, buffer); init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_INPROC); IUnknown_Release(unk2); @@ -657,7 +681,7 @@ static void test_marshal_STGMEDIUM(void) unsigned char *buffer, *buffer_end, *expect_buffer, *expect_buffer_end; ULONG size, expect_size; STGMEDIUM med, med2; - IUnknown *unk = &Test_Unknown; + IUnknown *unk = &Test_Unknown.IUnknown_iface; IStream *stm = &Test_Stream; /* TYMED_NULL with pUnkForRelease */ @@ -755,6 +779,211 @@ static void test_marshal_STGMEDIUM(void) HeapFree(GetProcessHeap(), 0, expect_buffer); } +static void test_marshal_SNB(void) +{ + static const WCHAR str1W[] = {'s','t','r','i','n','g','1',0}; + static const WCHAR str2W[] = {'s','t','r','2',0}; + unsigned char *buffer, *src, *mbuf; + MIDL_STUB_MESSAGE stub_msg; + WCHAR **ptrW, *dataW; + USER_MARSHAL_CB umcb; + RPC_MESSAGE rpc_msg; + RemSNB *wiresnb; + SNB snb, snb2; + ULONG size; + + /* 4 bytes alignment */ + snb = NULL; + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + size = SNB_UserSize(&umcb.Flags, 3, &snb); + ok(size == 16, "Size should be 16, instead of %d\n", size); + + /* NULL block */ + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + size = SNB_UserSize(&umcb.Flags, 0, &snb); + ok(size == 12, "Size should be 12, instead of %d\n", size); + + buffer = HeapAlloc(GetProcessHeap(), 0, size); + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + mbuf = SNB_UserMarshal(&umcb.Flags, buffer, &snb); + ok(mbuf == buffer + size, "got %p, %p\n", mbuf, buffer + size); + + wiresnb = (RemSNB*)buffer; + ok(wiresnb->ulCntStr == 0, "got %u\n", wiresnb->ulCntStr); + ok(wiresnb->ulCntChar == 0, "got %u\n", wiresnb->ulCntChar); + ok(*(ULONG*)wiresnb->rgString == 0, "got %u\n", *(ULONG*)wiresnb->rgString); + + snb2 = NULL; + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + SNB_UserUnmarshal(&umcb.Flags, buffer, &snb2); + ok(snb2 == NULL, "got %p\n", snb2); + + HeapFree(GetProcessHeap(), 0, buffer); + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + SNB_UserFree(&umcb.Flags, &snb2); + + /* block with actual data */ + + /* allocate source block, n+1 pointers first, then data */ + src = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR*)*3 + sizeof(str1W) + sizeof(str2W)); + ptrW = (WCHAR**)src; + dataW = *ptrW = (WCHAR*)(src + 3*sizeof(WCHAR*)); + ptrW++; + *ptrW = (WCHAR*)(src + 3*sizeof(WCHAR*) + sizeof(str1W)); + ptrW++; + *ptrW = NULL; + lstrcpyW(dataW, str1W); + dataW += lstrlenW(str1W) + 1; + lstrcpyW(dataW, str2W); + + snb = (SNB)src; + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + size = SNB_UserSize(&umcb.Flags, 0, &snb); + ok(size == 38, "Size should be 38, instead of %d\n", size); + + buffer = HeapAlloc(GetProcessHeap(), 0, size); + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + SNB_UserMarshal(&umcb.Flags, buffer, &snb); + + wiresnb = (RemSNB*)buffer; + ok(wiresnb->ulCntStr == 13, "got %u\n", wiresnb->ulCntStr); + ok(wiresnb->ulCntChar == 2, "got %u\n", wiresnb->ulCntChar); + /* payload length is stored one more time, as ULONG */ + ok(*(ULONG*)wiresnb->rgString == wiresnb->ulCntStr, "got %u\n", *(ULONG*)wiresnb->rgString); + dataW = &wiresnb->rgString[2]; + ok(!lstrcmpW(dataW, str1W), "marshalled string 0: %s\n", wine_dbgstr_w(dataW)); + dataW += sizeof(str1W)/sizeof(WCHAR); + ok(!lstrcmpW(dataW, str2W), "marshalled string 1: %s\n", wine_dbgstr_w(dataW)); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + + g_expect_user_alloc = TRUE; + snb2 = NULL; + SNB_UserUnmarshal(&umcb.Flags, buffer, &snb2); + g_expect_user_alloc = FALSE; + + ptrW = snb2; + ok(!lstrcmpW(*ptrW, str1W), "unmarshalled string 0: %s\n", wine_dbgstr_w(*ptrW)); + ptrW++; + ok(!lstrcmpW(*ptrW, str2W), "unmarshalled string 1: %s\n", wine_dbgstr_w(*ptrW)); + ptrW++; + ok(*ptrW == NULL, "expected terminating NULL ptr, got %p, start %p\n", *ptrW, snb2); + + HeapFree(GetProcessHeap(), 0, buffer); + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + + g_expect_user_free = TRUE; + SNB_UserFree(&umcb.Flags, &snb2); + g_expect_user_free = FALSE; + + HeapFree(GetProcessHeap(), 0, src); +} + +static void test_marshal_HDC(void) +{ + MIDL_STUB_MESSAGE stub_msg; + HDC hdc = GetDC(0), hdc2; + USER_MARSHAL_CB umcb; + RPC_MESSAGE rpc_msg; + unsigned char *buffer; + wireHDC wirehdc; + ULONG size; + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + size = HDC_UserSize(&umcb.Flags, 0, &hdc); + ok(size == sizeof(*wirehdc), "Wrong size %d\n", size); + + buffer = HeapAlloc(GetProcessHeap(), 0, size); + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + HDC_UserMarshal(&umcb.Flags, buffer, &hdc); + wirehdc = (wireHDC)buffer; + ok(wirehdc->fContext == WDT_INPROC_CALL, "Context should be WDT_INPROC_CALL instead of 0x%08x\n", wirehdc->fContext); + ok(wirehdc->u.hInproc == (LONG_PTR)hdc, "Marshaled value should be %p instead of %x\n", hdc, wirehdc->u.hRemote); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + HDC_UserUnmarshal(&umcb.Flags, buffer, &hdc2); + ok(hdc == hdc2, "Didn't unmarshal properly\n"); + HeapFree(GetProcessHeap(), 0, buffer); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + HDC_UserFree(&umcb.Flags, &hdc2); + ReleaseDC(0, hdc); +} + +static void test_marshal_HICON(void) +{ + static const BYTE bmp_bits[1024]; + MIDL_STUB_MESSAGE stub_msg; + HICON hIcon, hIcon2; + USER_MARSHAL_CB umcb; + RPC_MESSAGE rpc_msg; + unsigned char *buffer; + wireHICON wirehicon; + ULONG size; + + hIcon = CreateIcon(0, 16, 16, 1, 1, bmp_bits, bmp_bits); + ok(hIcon != 0, "CreateIcon failed\n"); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + size = HICON_UserSize(&umcb.Flags, 0, &hIcon); + ok(size == sizeof(*wirehicon), "Wrong size %d\n", size); + + buffer = HeapAlloc(GetProcessHeap(), 0, size); + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + HICON_UserMarshal(&umcb.Flags, buffer, &hIcon); + wirehicon = (wireHICON)buffer; + ok(wirehicon->fContext == WDT_INPROC_CALL, "Context should be WDT_INPROC_CALL instead of 0x%08x\n", wirehicon->fContext); + ok(wirehicon->u.hInproc == (LONG_PTR)hIcon, "Marshaled value should be %p instead of %x\n", hIcon, wirehicon->u.hRemote); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + HICON_UserUnmarshal(&umcb.Flags, buffer, &hIcon2); + ok(hIcon == hIcon2, "Didn't unmarshal properly\n"); + HeapFree(GetProcessHeap(), 0, buffer); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + HICON_UserFree(&umcb.Flags, &hIcon2); + DestroyIcon(hIcon); +} + +static void test_marshal_HBRUSH(void) +{ + MIDL_STUB_MESSAGE stub_msg; + HBRUSH hBrush, hBrush2; + USER_MARSHAL_CB umcb; + RPC_MESSAGE rpc_msg; + unsigned char *buffer; + LOGBRUSH logbrush; + wireHBRUSH wirehbrush; + ULONG size; + + logbrush.lbStyle = BS_SOLID; + logbrush.lbColor = RGB(0, 0, 0); + logbrush.lbHatch = 0; + + hBrush = CreateBrushIndirect(&logbrush); + ok(hBrush != 0, "CreateBrushIndirect failed\n"); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + size = HBRUSH_UserSize(&umcb.Flags, 0, &hBrush); + ok(size == sizeof(*wirehbrush), "Wrong size %d\n", size); + + buffer = HeapAlloc(GetProcessHeap(), 0, size); + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + HBRUSH_UserMarshal(&umcb.Flags, buffer, &hBrush); + wirehbrush = (wireHBRUSH)buffer; + ok(wirehbrush->fContext == WDT_INPROC_CALL, "Context should be WDT_INPROC_CALL instead of 0x%08x\n", wirehbrush->fContext); + ok(wirehbrush->u.hInproc == (LONG_PTR)hBrush, "Marshaled value should be %p instead of %x\n", hBrush, wirehbrush->u.hRemote); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, buffer, size, MSHCTX_LOCAL); + HBRUSH_UserUnmarshal(&umcb.Flags, buffer, &hBrush2); + ok(hBrush == hBrush2, "Didn't unmarshal properly\n"); + HeapFree(GetProcessHeap(), 0, buffer); + + init_user_marshal_cb(&umcb, &stub_msg, &rpc_msg, NULL, 0, MSHCTX_LOCAL); + HBRUSH_UserFree(&umcb.Flags, &hBrush2); + DeleteObject(hBrush); +} + START_TEST(usrmarshal) { CoInitialize(NULL); @@ -767,6 +996,10 @@ START_TEST(usrmarshal) test_marshal_HMETAFILEPICT(); test_marshal_WdtpInterfacePointer(); test_marshal_STGMEDIUM(); + test_marshal_SNB(); + test_marshal_HDC(); + test_marshal_HICON(); + test_marshal_HBRUSH(); CoUninitialize(); }