From: Piotr Caban <piotr@codeweavers.com> --- dlls/msado15/msado15_private.h | 2 + dlls/msado15/rowset.c | 2 +- dlls/msado15/rowsetex.c | 266 +++++++++++++++++++++++++++++++++ dlls/msado15/tests/msado15.c | 143 +++++++++++++++++- 4 files changed, 407 insertions(+), 6 deletions(-) diff --git a/dlls/msado15/msado15_private.h b/dlls/msado15/msado15_private.h index 6d4d169671a..40120fc2f28 100644 --- a/dlls/msado15/msado15_private.h +++ b/dlls/msado15/msado15_private.h @@ -46,4 +46,6 @@ typedef enum tid_t { HRESULT get_typeinfo(tid_t tid, ITypeInfo **typeinfo); +void dbtype_free( DBTYPE, void *data ); + #endif /* _WINE_MSADO15_PRIVATE_H_ */ diff --git a/dlls/msado15/rowset.c b/dlls/msado15/rowset.c index 20f0fd0bbd6..beb75ae788e 100644 --- a/dlls/msado15/rowset.c +++ b/dlls/msado15/rowset.c @@ -68,7 +68,7 @@ struct accessor DBBINDING bindings[1]; }; -static void dbtype_free(DBTYPE type, void *data) +void dbtype_free(DBTYPE type, void *data) { if (type & DBTYPE_BYREF) { diff --git a/dlls/msado15/rowsetex.c b/dlls/msado15/rowsetex.c index a8094249807..07b2a70e288 100644 --- a/dlls/msado15/rowsetex.c +++ b/dlls/msado15/rowsetex.c @@ -33,12 +33,14 @@ struct rowsetex { IRowsetExactScroll IRowsetExactScroll_iface; IAccessor IAccessor_iface; + IRowsetFind IRowsetFind_iface; LONG refs; IRowset *rowset; IRowsetLocate *rowset_loc; IRowsetExactScroll *rowset_es; IAccessor *accessor; + IRowsetFind *rowset_find; DBTYPE bookmark_type; HACCESSOR bookmark_hacc; @@ -54,6 +56,11 @@ static inline struct rowsetex *impl_from_IAccessor(IAccessor *iface) return CONTAINING_RECORD(iface, struct rowsetex, IAccessor_iface); } +static inline struct rowsetex *impl_from_IRowsetFind(IRowsetFind *iface) +{ + return CONTAINING_RECORD(iface, struct rowsetex, IRowsetFind_iface); +} + static HRESULT WINAPI rowsetex_QueryInterface(IRowsetExactScroll *iface, REFIID riid, void **obj) { struct rowsetex *rowset = impl_from_IRowsetExactScroll(iface); @@ -83,6 +90,10 @@ static HRESULT WINAPI rowsetex_QueryInterface(IRowsetExactScroll *iface, REFIID } *obj = &rowset->IAccessor_iface; } + else if (IsEqualGUID(&IID_IRowsetFind, riid)) + { + *obj = &rowset->IRowsetFind_iface; + } else if (IsEqualGUID(&IID_IColumnsInfo, riid) || IsEqualGUID(&IID_IRowsetIndex, riid) || IsEqualGUID(&IID_IRowsetCurrentIndex, riid)) @@ -127,6 +138,8 @@ static ULONG WINAPI rowsetex_Release(IRowsetExactScroll *iface) if (rowset->bookmark_hacc) IAccessor_ReleaseAccessor(rowset->accessor, rowset->bookmark_hacc, NULL); if (rowset->accessor) IAccessor_Release(rowset->accessor); + if (rowset->rowset_find && rowset->rowset_find != NO_INTERFACE) + IRowsetFind_Release(rowset->rowset_find); free(rowset); } return refs; @@ -462,6 +475,258 @@ static const struct IAccessorVtbl accessor_vtbl = accessor_ReleaseAccessor }; +static HRESULT WINAPI find_QueryInterface(IRowsetFind *iface, REFIID riid, void **obj) +{ + struct rowsetex *rowset = impl_from_IRowsetFind(iface); + return IRowsetExactScroll_QueryInterface(&rowset->IRowsetExactScroll_iface, riid, obj); +} + +static ULONG WINAPI find_AddRef(IRowsetFind *iface) +{ + struct rowsetex *rowset = impl_from_IRowsetFind(iface); + return IRowsetExactScroll_AddRef(&rowset->IRowsetExactScroll_iface); +} + +static ULONG WINAPI find_Release(IRowsetFind *iface) +{ + struct rowsetex *rowset = impl_from_IRowsetFind(iface); + return IRowsetExactScroll_Release(&rowset->IRowsetExactScroll_iface); +} + +static HRESULT int_compare(DBCOMPAREOP compare_op, long long x, long long y) +{ + switch (compare_op) + { + case DBCOMPAREOPS_LT: + return x < y ? S_OK : S_FALSE; + case DBCOMPAREOPS_LE: + return x <= y ? S_OK : S_FALSE; + case DBCOMPAREOPS_EQ: + return x == y ? S_OK : S_FALSE; + case DBCOMPAREOPS_GE: + return x >= y ? S_OK : S_FALSE; + case DBCOMPAREOPS_GT: + return x > y ? S_OK : S_FALSE; + case DBCOMPAREOPS_NE: + return x != y ? S_OK : S_FALSE; + default: + return S_FALSE; + } +} + +static HRESULT WINAPI find_FindNextRow(IRowsetFind *iface, HCHAPTER chapter, HACCESSOR hacc, + void *find_value, DBCOMPAREOP compare_op, DBBKMARK bookmark_size, const BYTE *bookmark, + DBROWOFFSET offset, DBROWCOUNT rows, DBCOUNTITEM *obtained, HROW **hrows) +{ + BYTE tmp[sizeof(long long)], *bm = (BYTE *)bookmark, *data_buf = NULL; + struct rowsetex *rowset = impl_from_IRowsetFind(iface); + DWORD status = DBSTATUS_S_OK; + DBBINDING *bindings = NULL; + DBCOUNTITEM bindings_no; + HROW row, *prow = &row; + DBACCESSORFLAGS flags; + BOOL free_val = FALSE; + DBCOUNTITEM count = 0; + size_t data_size; + VARIANT conv; + HRESULT hr; + + TRACE("%p, %Id, %Id, %p, %ld, %Id, %p, %Id, %Id, %p, %p\n", rowset, chapter, hacc, + find_value, compare_op, bookmark_size, bookmark, offset, rows, obtained, hrows); + + if (!rowset->rowset_find) + { + HRESULT hr = IRowset_QueryInterface(rowset->rowset, + &IID_IRowsetFind, (void**)&rowset->rowset_find); + if (FAILED(hr)) + rowset->rowset_find = NO_INTERFACE; + } + + if (rowset->rowset_find != NO_INTERFACE) + { + return IRowsetFind_FindNextRow(rowset->rowset_find, chapter, hacc, find_value, + compare_op, bookmark_size, bookmark, offset, rows, obtained, hrows); + } + + if (!obtained || !hrows) return E_INVALIDARG; + if (rows != -1 && rows != 1) + { + FIXME("rows = %Id\n", rows); + return E_NOTIMPL; + } + + if (!hacc || !rowset->accessor) + return DB_E_BADACCESSORHANDLE; + /* TODO: Use custom HACCESSOR instead of calling IAccessor::GetBinding. */ + hr = IAccessor_GetBindings(rowset->accessor, hacc, &flags, &bindings_no, &bindings); + if (FAILED(hr)) return hr; + VariantInit(&conv); + if (bindings_no != 1 || !(flags & DBACCESSOR_ROWDATA) || !(bindings->dwPart & DBPART_VALUE)) + { + hr = DB_E_BADACCESSORTYPE; + goto done; + } + + if (bindings->dwPart & DBPART_STATUS) + status = *(DWORD *)((BYTE *)find_value + bindings->obStatus); + if (status != DBSTATUS_S_OK) + { + FIXME("unhandled pattern status: %ld\n", status); + hr = E_NOTIMPL; + goto done; + } + + data_size = bindings->obValue + bindings->cbMaxLen; + if (bindings->dwPart & DBPART_LENGTH && bindings->obLength + sizeof(DBLENGTH) > data_size) + data_size = bindings->obLength + sizeof(DBLENGTH); + if (bindings->dwPart & DBPART_STATUS && bindings->obStatus + sizeof(DWORD) > data_size) + data_size = bindings->obStatus + sizeof(DWORD); + data_buf = malloc(data_size); + if (!data_buf) + { + hr = E_OUTOFMEMORY; + goto done; + } + + /* Non DBBMK_FIRST bookmark is ignored if IRowsetLocate is not implemented */ + if (!rowset->rowset_loc && bookmark_size == 1 && *bookmark == DBBMK_FIRST) + { + hr = IRowset_RestartPosition(rowset->rowset, chapter); + if (FAILED(hr)) goto done; + } + if (!rowset->rowset_loc) bookmark_size = 0; + + while (1) + { + BYTE *x = data_buf + bindings->obValue; + BYTE *y = (BYTE *)find_value + bindings->obValue; + DBTYPE type = bindings->wType; + + if (bookmark_size) + { + hr = IRowsetLocate_GetRowsAt(rowset->rowset_loc, 0, chapter, + bookmark_size, bm, offset, rows, &count, &prow); + if (bm != bookmark && bm != tmp) + { + CoTaskMemFree(bm); + bm = NULL; + } + } + else + { + hr = IRowset_GetNextRows(rowset->rowset, chapter, offset, rows, &count, &prow); + } + if (FAILED(hr)) goto done; + if (!count) break; + offset = bookmark_size ? 1 : 0; + + hr = IRowset_GetData(rowset->rowset, row, hacc, data_buf); + if (FAILED(hr)) goto done; + free_val = TRUE; + if (compare_op == DBCOMPAREOPS_IGNORE) break; + + if (bindings->dwPart & DBPART_STATUS) + status = *(DWORD *)(data_buf + bindings->obStatus); + if (status == DBSTATUS_S_ISNULL) + type = DBTYPE_NULL; + else if (status != DBSTATUS_S_OK) + { + FIXME("unhandled status: %ld\n", status); + hr = E_NOTIMPL; + goto done; + } + + if (bookmark_size) + { + hr = get_bookmark(rowset, row, &bookmark_size, &bm, tmp); + if (FAILED(hr)) goto done; + } + + if (type == DBTYPE_VARIANT) + { + VARIANT *xv = (VARIANT *)x; + VARIANT *yv = (VARIANT *)y; + + type = V_VT(xv); + if (type != V_VT(yv)) + { + if (type != V_VT(&conv)) + { + VariantClear(&conv); + hr = VariantChangeType(&conv, yv, 0, type); + if (FAILED(hr)) goto done; + } + yv = &conv; + } + + x = &V_UI1(xv); + y = &V_UI1(yv); + } + + switch (type) + { + case DBTYPE_I1: + hr = int_compare(compare_op, *(char *)x, *(char *)y); + break; + case DBTYPE_I2: + hr = int_compare(compare_op, *(short *)x, *(short *)y); + break; + case DBTYPE_I4: + hr = int_compare(compare_op, *(int *)x, *(int *)y); + break; + case DBTYPE_I8: + hr = int_compare(compare_op, *(long long*)x, *(long long*)y); + break; + default: + FIXME("unhandled data type: %d\n", type); + hr = E_NOTIMPL; + break; + } + + if (hr != S_FALSE) break; + if (bindings->dwMemOwner == DBMEMOWNER_CLIENTOWNED) + dbtype_free(bindings->wType, data_buf + bindings->obValue); + free_val = FALSE; + IRowset_ReleaseRows(rowset->rowset, count, prow, NULL, NULL, NULL); + count = 0; + } + +done: + if (free_val && bindings->dwMemOwner == DBMEMOWNER_CLIENTOWNED) + dbtype_free(bindings->wType, data_buf + bindings->obValue); + if (bm != bookmark && bm != tmp) CoTaskMemFree(bm); + VariantClear(&conv); + CoTaskMemFree(bindings); + free(data_buf); + + if (SUCCEEDED(hr)) + { + if (count && !*hrows) + { + *hrows = CoTaskMemAlloc(sizeof(**hrows)); + if (*hrows) hr = E_OUTOFMEMORY; + } + if (count && *hrows) + { + IRowset_AddRefRows(rowset->rowset, 1, &row, NULL, NULL); + *hrows[0] = row; + } + } + if (count) IRowset_ReleaseRows(rowset->rowset, count, prow, NULL, NULL, NULL); + if (FAILED(hr)) return hr; + + *obtained = count; + return count ? hr : DB_S_ENDOFROWSET; +} + +static const struct IRowsetFindVtbl find_vtbl = +{ + find_QueryInterface, + find_AddRef, + find_Release, + find_FindNextRow +}; + HRESULT create_rowsetex(IUnknown *rowset, IUnknown **ret) { struct rowsetex *rowsetex; @@ -472,6 +737,7 @@ HRESULT create_rowsetex(IUnknown *rowset, IUnknown **ret) rowsetex->IRowsetExactScroll_iface.lpVtbl = &rowsetex_vtbl; rowsetex->IAccessor_iface.lpVtbl = &accessor_vtbl; + rowsetex->IRowsetFind_iface.lpVtbl = &find_vtbl; rowsetex->refs = 1; hr = IUnknown_QueryInterface(rowset, &IID_IRowset, (void **)&rowsetex->rowset); diff --git a/dlls/msado15/tests/msado15.c b/dlls/msado15/tests/msado15.c index d6881b70e80..551d18890f6 100644 --- a/dlls/msado15/tests/msado15.c +++ b/dlls/msado15/tests/msado15.c @@ -68,6 +68,7 @@ static char mdbpath[MAX_PATH]; expect_ ## func = called_ ## func = FALSE; \ }while(0) +DEFINE_EXPECT(rowset_QI_IConvertType); DEFINE_EXPECT(rowset_QI_IDBAsynchStatus); DEFINE_EXPECT(rowset_info_GetProperties); DEFINE_EXPECT(column_info_GetColumnInfo); @@ -588,6 +589,7 @@ struct test_rowset BOOL exact_scroll; BOOL locate; + BOOL find_test; int idx; }; @@ -1092,8 +1094,18 @@ static HRESULT WINAPI accessor_CreateAccessor(IAccessor *iface, DBACCESSORFLAGS static HRESULT WINAPI accessor_GetBindings(IAccessor *iface, HACCESSOR hAccessor, DBACCESSORFLAGS *pdwAccessorFlags, DBCOUNTITEM *pcBindings, DBBINDING **prgBindings) { - ok(0, "unexpected call\n"); - return E_NOTIMPL; + struct haccessor *accessor = (struct haccessor *)hAccessor; + + todo_wine ok(0, "unexpected call\n"); + + *pdwAccessorFlags = DBACCESSOR_ROWDATA; + *pcBindings = (hAccessor == 1 ? 0 : 1); + if (*pcBindings) + { + *prgBindings = CoTaskMemAlloc(sizeof(accessor->binding)); + (*prgBindings)[0] = accessor->binding; + } + return S_OK; } static HRESULT WINAPI accessor_ReleaseAccessor(IAccessor *iface, @@ -1545,6 +1557,8 @@ static HRESULT WINAPI rowset_QueryInterface(IRowsetExactScroll *iface, REFIID ri } else if (IsEqualIID(riid, &IID_IRowsetFind)) { + if (!rowset->IRowsetFind_iface.lpVtbl) + return E_NOINTERFACE; *obj = &rowset->IRowsetFind_iface; } else if (IsEqualIID(riid, &IID_IRowsetView)) @@ -1560,6 +1574,11 @@ static HRESULT WINAPI rowset_QueryInterface(IRowsetExactScroll *iface, REFIID ri { *obj = &rowset->IRowsetCurrentIndex_iface; } + else if (IsEqualIID(riid, &IID_IConvertType)) + { + CHECK_EXPECT(rowset_QI_IConvertType); + return E_NOINTERFACE; + } else if (IsEqualIID(riid, &UNK_INTERFACE) || IsEqualIID(riid, &UNK2_INTERFACE)) { trace("Unknown interface (%s)\n", wine_dbgstr_guid(riid)); @@ -1597,6 +1616,7 @@ static HRESULT WINAPI rowset_AddRefRows(IRowsetExactScroll *iface, DBCOUNTITEM c static HRESULT WINAPI rowset_GetData(IRowsetExactScroll *iface, HROW hRow, HACCESSOR hAccessor, void *pData) { + struct test_rowset *rowset = impl_from_IRowsetExactScroll( iface ); struct haccessor *haccessor = (struct haccessor *)hAccessor; DBSTATUS status = DBSTATUS_S_OK; DBLENGTH len; @@ -1621,8 +1641,16 @@ static HRESULT WINAPI rowset_GetData(IRowsetExactScroll *iface, HROW hRow, HACCE break; case 1: - ok(haccessor->binding.dwPart == (DBPART_VALUE | DBPART_STATUS), - "dwPart = %ld\n", haccessor->binding.dwPart); + if (!rowset->IRowsetFind_iface.lpVtbl) + { + todo_wine ok(haccessor->binding.dwPart == (DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH), + "dwPart = %ld\n", haccessor->binding.dwPart); + } + else + { + ok(haccessor->binding.dwPart == (DBPART_VALUE | DBPART_STATUS), + "dwPart = %ld\n", haccessor->binding.dwPart); + } ok(haccessor->binding.cbMaxLen == sizeof(VARIANT), "cbMaxLen = %Id\n", haccessor->binding.cbMaxLen); ok(haccessor->binding.wType == DBTYPE_VARIANT, "wType = %d\n", haccessor->binding.wType); @@ -3762,7 +3790,7 @@ static HRESULT WINAPI open_rowset_OpenRowset(IOpenRowset *iface, IUnknown *unk_o open_rowset_test.IRowsetView_iface.lpVtbl = &rowset_view; open_rowset_test.IChapteredRowset_iface.lpVtbl = &chaptered_rowset; open_rowset_test.IRowsetCurrentIndex_iface.lpVtbl = &rowset_current_index; - open_rowset_test.IRowsetFind_iface.lpVtbl = &rowset_find; + open_rowset_test.IRowsetFind_iface.lpVtbl = NULL; open_rowset_test.refs = 1; open_rowset_test.IViewChapter_iface.lpVtbl = &view_chapter; open_rowset_test.IViewFilter_iface.lpVtbl = &view_filter; @@ -3786,13 +3814,18 @@ static void test_ADOConnectionConstruction(void) ADOConnectionConstruction15 *conn_constr; ADORecordsetConstruction *rec_constr; IRowsetExactScroll *rowset_es; + HROW hrow, *hrows = &hrow; _Recordset *recordset; DBCOUNTITEM rows, pos; IUnknown *unk, *unk2; + IAccessor *accessor; VARIANT v, missing; _Connection *conn; + IRowsetFind *find; + DBBINDING binding; IColumnsInfo *ci; IRowset *rowset; + HACCESSOR hacc; BYTE bookmark; LONG state; HRESULT hr; @@ -3909,6 +3942,106 @@ static void test_ADOConnectionConstruction(void) ok(hr == E_INVALIDARG, "got %08lx\n", hr); IRowsetExactScroll_Release(rowset_es); + hr = IRowset_QueryInterface(rowset, &IID_IRowsetFind, (void **)&find); + ok(hr == S_OK, "got %08lx\n", hr); + + hr = IRowsetFind_QueryInterface(find, &IID_IAccessor, (void **)&accessor); + ok(hr == S_OK, "got %08lx\n", hr); + memset(&binding, 0, sizeof(binding)); + binding.iOrdinal = 1; + binding.dwPart = DBPART_VALUE; + binding.cbMaxLen = sizeof(VARIANT); + binding.wType = DBTYPE_VARIANT; + SET_EXPECT(rowset_QI_IConvertType); + hr = IAccessor_CreateAccessor(accessor, DBACCESSOR_ROWDATA, 1, &binding, 0, &hacc, NULL); + ok(hr == S_OK, "got %08lx\n", hr); + todo_wine CHECK_CALLED(rowset_QI_IConvertType); + + hr = IRowsetFind_FindNextRow(find, 0, 0, NULL, DBCOMPAREOPS_EQ, 0, NULL, 0, 1, &rows, &hrows); + ok(hr == DB_E_BADACCESSORHANDLE, "got %08lx\n", hr); + + V_VT(&v) = VT_BSTR; + V_BSTR(&v) = SysAllocString(L"1'1"); + SET_EXPECT(rowset_GetNextRows); + SET_EXPECT(rowset_GetData); + SET_EXPECT(rowset_ReleaseRows); + hr = IRowsetFind_FindNextRow(find, 0, hacc, &v, DBCOMPAREOPS_EQ, 0, NULL, 0, 1, &rows, &hrows); + CHECK_CALLED(rowset_GetNextRows); + CHECK_CALLED(rowset_GetData); + CHECK_CALLED(rowset_ReleaseRows); + ok(hr == DISP_E_TYPEMISMATCH, "got %08lx\n", hr); + VariantClear(&v); + + V_VT(&v) = VT_BSTR; + V_BSTR(&v) = SysAllocString(L"1"); + rows = 1; + SET_EXPECT(rowset_GetNextRows); + SET_EXPECT(rowset_GetData); + SET_EXPECT(rowset_ReleaseRows); + hr = IRowsetFind_FindNextRow(find, 0, hacc, &v, DBCOMPAREOPS_EQ, 0, NULL, 0, 1, &rows, &hrows); + CHECK_CALLED(rowset_GetNextRows); + CHECK_CALLED(rowset_GetData); + CHECK_CALLED(rowset_ReleaseRows); + ok(hr == DB_S_ENDOFROWSET, "got %08lx\n", hr); + ok(!rows, "rows = %Id\n", rows); + VariantClear(&v); + + SET_EXPECT(rowset_RestartPosition); + hr = IRowset_RestartPosition(rowset, 0); + CHECK_CALLED(rowset_RestartPosition); + ok(hr == S_OK, "got %08lx\n", hr); + V_VT(&v) = VT_BSTR; + V_BSTR(&v) = SysAllocString(L"123"); + rows = 123; + hrow = 0xdeadbeef; + SET_EXPECT(rowset_GetNextRows); + SET_EXPECT(rowset_GetData); + SET_EXPECT(rowset_AddRefRows); + SET_EXPECT(rowset_ReleaseRows); + hr = IRowsetFind_FindNextRow(find, 0, hacc, &v, DBCOMPAREOPS_EQ, 0, NULL, 0, 1, &rows, &hrows); + CHECK_CALLED(rowset_GetNextRows); + CHECK_CALLED(rowset_GetData); + CHECK_CALLED(rowset_AddRefRows); + CHECK_CALLED(rowset_ReleaseRows); + ok(hr == S_OK, "got %08lx\n", hr); + ok(rows == 1, "rows = %Id\n", rows); + ok(hrow == 1, "hrow = %Id\n", hrow); + + SET_EXPECT(rowset_GetNextRows); + SET_EXPECT(rowset_GetData); + SET_EXPECT(rowset_AddRefRows); + SET_EXPECT(rowset_ReleaseRows); + hr = IRowsetFind_FindNextRow(find, 0, hacc, &v, DBCOMPAREOPS_EQ, 0, NULL, 0, 1, &rows, &hrows); + CHECK_CALLED(rowset_GetNextRows); + CHECK_CALLED(rowset_GetData); + CHECK_CALLED(rowset_AddRefRows); + CHECK_CALLED(rowset_ReleaseRows); + ok(hr == S_OK, "got %08lx\n", hr); + ok(rows == 1, "rows = %Id\n", rows); + ok(hrow == 2, "hrow = %Id\n", hrow); + + V_BSTR(&v)[0] = '2'; + bookmark = DBBMK_FIRST; + SET_EXPECT(rowset_GetRowsAt); + SET_EXPECT(rowset_GetData); + SET_EXPECT(rowset_ReleaseRows); + hr = IRowsetFind_FindNextRow(find, 0, hacc, &v, DBCOMPAREOPS_EQ, + sizeof(bookmark), &bookmark, 0, 1, &rows, &hrows); + CHECK_CALLED(rowset_GetRowsAt); + CHECK_CALLED(rowset_GetData); + CHECK_CALLED(rowset_ReleaseRows); + ok(hr == DB_S_ENDOFROWSET, "got %08lx\n", hr); + ok(!rows, "rows = %Id\n", rows); + ok(hrow == 2, "hrow = %Id\n", hrow); + VariantClear(&v); + + SET_EXPECT(accessor_ReleaseAccessor); + hr = IAccessor_ReleaseAccessor(accessor, hacc, NULL); + ok(hr == S_OK, "got %08lx\n", hr); + CHECK_CALLED(accessor_ReleaseAccessor); + IAccessor_Release(accessor); + IRowsetFind_Release(find); + SET_EXPECT(accessor_ReleaseAccessor); ok(!_Recordset_Release(recordset), "_Recordset not released\n"); CHECK_CALLED(accessor_ReleaseAccessor); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10742