2016-08-31 18:48:39 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2016 Daniel Lehman
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
2017-05-26 14:52:08 +02:00
|
|
|
#include "windef.h"
|
|
|
|
#include "winbase.h"
|
|
|
|
#include "winnls.h"
|
|
|
|
|
2016-08-31 18:48:39 +02:00
|
|
|
#include "wine/test.h"
|
|
|
|
#include "winbase.h"
|
|
|
|
|
2017-03-10 16:01:40 +01:00
|
|
|
#define DEFINE_EXPECT(func) \
|
|
|
|
static BOOL expect_ ## func = FALSE, called_ ## func = FALSE
|
|
|
|
|
|
|
|
#define SET_EXPECT(func) \
|
|
|
|
do { \
|
|
|
|
expect_ ## func = TRUE; \
|
|
|
|
errno = 0xdeadbeef; \
|
|
|
|
}while(0)
|
|
|
|
|
|
|
|
#define CHECK_EXPECT2(func) \
|
|
|
|
do { \
|
|
|
|
ok(expect_ ##func, "unexpected call " #func "\n"); \
|
|
|
|
called_ ## func = TRUE; \
|
|
|
|
}while(0)
|
|
|
|
|
|
|
|
#define CHECK_EXPECT(func) \
|
|
|
|
do { \
|
|
|
|
CHECK_EXPECT2(func); \
|
|
|
|
expect_ ## func = FALSE; \
|
|
|
|
}while(0)
|
|
|
|
|
|
|
|
#define CHECK_CALLED(func) \
|
|
|
|
do { \
|
|
|
|
ok(called_ ## func, "expected " #func "\n"); \
|
|
|
|
expect_ ## func = called_ ## func = FALSE; \
|
|
|
|
}while(0)
|
|
|
|
|
|
|
|
#ifdef _WIN64
|
|
|
|
DEFINE_EXPECT(function_do_call);
|
|
|
|
DEFINE_EXPECT(function_do_clean);
|
|
|
|
#endif
|
|
|
|
|
2017-03-01 13:18:01 +01:00
|
|
|
#undef __thiscall
|
|
|
|
#ifdef __i386__
|
|
|
|
#define __thiscall __stdcall
|
|
|
|
#else
|
|
|
|
#define __thiscall __cdecl
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Emulate a __thiscall */
|
|
|
|
#ifdef __i386__
|
|
|
|
|
|
|
|
#include "pshpack1.h"
|
|
|
|
struct thiscall_thunk
|
|
|
|
{
|
|
|
|
BYTE pop_eax; /* popl %eax (ret addr) */
|
|
|
|
BYTE pop_edx; /* popl %edx (func) */
|
|
|
|
BYTE pop_ecx; /* popl %ecx (this) */
|
|
|
|
BYTE push_eax; /* pushl %eax */
|
|
|
|
WORD jmp_edx; /* jmp *%edx */
|
|
|
|
};
|
|
|
|
#include "poppack.h"
|
|
|
|
|
|
|
|
static void * (WINAPI *call_thiscall_func1)( void *func, void *this );
|
2017-03-10 16:01:40 +01:00
|
|
|
static void * (WINAPI *call_thiscall_func2)( void *func, void *this, const void *a );
|
2017-03-01 13:18:01 +01:00
|
|
|
|
|
|
|
static void init_thiscall_thunk(void)
|
|
|
|
{
|
|
|
|
struct thiscall_thunk *thunk = VirtualAlloc( NULL, sizeof(*thunk),
|
|
|
|
MEM_COMMIT, PAGE_EXECUTE_READWRITE );
|
|
|
|
thunk->pop_eax = 0x58; /* popl %eax */
|
|
|
|
thunk->pop_edx = 0x5a; /* popl %edx */
|
|
|
|
thunk->pop_ecx = 0x59; /* popl %ecx */
|
|
|
|
thunk->push_eax = 0x50; /* pushl %eax */
|
|
|
|
thunk->jmp_edx = 0xe2ff; /* jmp *%edx */
|
|
|
|
call_thiscall_func1 = (void *)thunk;
|
2017-03-10 16:01:40 +01:00
|
|
|
call_thiscall_func2 = (void *)thunk;
|
2017-03-01 13:18:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#define call_func1(func,_this) call_thiscall_func1(func,_this)
|
2017-03-10 16:01:40 +01:00
|
|
|
#define call_func2(func,_this,a) call_thiscall_func2(func,_this,a)
|
2017-03-01 13:18:01 +01:00
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
#define init_thiscall_thunk()
|
|
|
|
#define call_func1(func,_this) func(_this)
|
2017-03-10 16:01:40 +01:00
|
|
|
#define call_func2(func,_this,a) func(_this,a)
|
2017-03-01 13:18:01 +01:00
|
|
|
|
|
|
|
#endif /* __i386__ */
|
2017-03-10 16:01:40 +01:00
|
|
|
typedef unsigned char MSVCP_bool;
|
2017-03-01 13:18:01 +01:00
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
void *unk0;
|
|
|
|
BYTE unk1;
|
|
|
|
} task_continuation_context;
|
|
|
|
|
2017-03-10 16:01:40 +01:00
|
|
|
typedef struct {
|
|
|
|
void *unused;
|
|
|
|
} _ContextCallback;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
const void *vtable;
|
|
|
|
void (__cdecl *func)(void);
|
|
|
|
int unk[4];
|
|
|
|
void *unk2[3];
|
|
|
|
void *this;
|
|
|
|
} function_void_cdecl_void;
|
|
|
|
|
2017-03-15 12:06:29 +01:00
|
|
|
typedef struct {
|
|
|
|
void *task;
|
|
|
|
MSVCP_bool scheduled;
|
|
|
|
MSVCP_bool started;
|
|
|
|
} _TaskEventLogger;
|
|
|
|
|
2017-03-21 12:13:11 +01:00
|
|
|
typedef struct {
|
|
|
|
PTP_WORK work;
|
|
|
|
void (__cdecl *callback)(void*);
|
|
|
|
void *arg;
|
|
|
|
} _Threadpool_chore;
|
|
|
|
|
2016-08-31 18:48:39 +02:00
|
|
|
static unsigned int (__cdecl *p__Thrd_id)(void);
|
2017-03-01 13:18:01 +01:00
|
|
|
static task_continuation_context* (__thiscall *p_task_continuation_context_ctor)(task_continuation_context*);
|
2017-03-10 16:01:40 +01:00
|
|
|
static void (__thiscall *p__ContextCallback__Assign)(_ContextCallback*, void*);
|
|
|
|
static void (__thiscall *p__ContextCallback__CallInContext)(const _ContextCallback*, function_void_cdecl_void, MSVCP_bool);
|
|
|
|
static void (__thiscall *p__ContextCallback__Capture)(_ContextCallback*);
|
|
|
|
static void (__thiscall *p__ContextCallback__Reset)(_ContextCallback*);
|
|
|
|
static MSVCP_bool (__cdecl *p__ContextCallback__IsCurrentOriginSTA)(_ContextCallback*);
|
2017-03-15 12:06:29 +01:00
|
|
|
static void (__thiscall *p__TaskEventLogger__LogCancelTask)(_TaskEventLogger*);
|
|
|
|
static void (__thiscall *p__TaskEventLogger__LogScheduleTask)(_TaskEventLogger*, MSVCP_bool);
|
|
|
|
static void (__thiscall *p__TaskEventLogger__LogTaskCompleted)(_TaskEventLogger*);
|
|
|
|
static void (__thiscall *p__TaskEventLogger__LogTaskExecutionCompleted)(_TaskEventLogger*);
|
|
|
|
static void (__thiscall *p__TaskEventLogger__LogWorkItemCompleted)(_TaskEventLogger*);
|
|
|
|
static void (__thiscall *p__TaskEventLogger__LogWorkItemStarted)(_TaskEventLogger*);
|
2017-03-21 12:13:11 +01:00
|
|
|
static int (__cdecl *p__Schedule_chore)(_Threadpool_chore*);
|
|
|
|
static int (__cdecl *p__Reschedule_chore)(const _Threadpool_chore*);
|
|
|
|
static void (__cdecl *p__Release_chore)(_Threadpool_chore*);
|
2016-08-31 18:48:39 +02:00
|
|
|
|
2017-07-11 12:30:16 +02:00
|
|
|
static MSVCP_bool (__cdecl *p_Current_get)(WCHAR *);
|
2017-07-11 12:30:15 +02:00
|
|
|
static ULONGLONG (__cdecl *p_File_size)(WCHAR const *);
|
2017-07-10 12:59:02 +02:00
|
|
|
static int (__cdecl *p_To_byte)(const WCHAR *src, char *dst);
|
2017-05-26 14:52:08 +02:00
|
|
|
static int (__cdecl *p_To_wide)(const char *src, WCHAR *dst);
|
|
|
|
|
2016-08-31 18:48:39 +02:00
|
|
|
static HMODULE msvcp;
|
|
|
|
#define SETNOFAIL(x,y) x = (void*)GetProcAddress(msvcp,y)
|
|
|
|
#define SET(x,y) do { SETNOFAIL(x,y); ok(x != NULL, "Export '%s' not found\n", y); } while(0)
|
|
|
|
static BOOL init(void)
|
|
|
|
{
|
|
|
|
msvcp = LoadLibraryA("msvcp140.dll");
|
|
|
|
if(!msvcp)
|
|
|
|
{
|
|
|
|
win_skip("msvcp140.dll not installed\n");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
SET(p__Thrd_id, "_Thrd_id");
|
2017-03-10 16:01:40 +01:00
|
|
|
SET(p__ContextCallback__IsCurrentOriginSTA, "?_IsCurrentOriginSTA@_ContextCallback@details@Concurrency@@CA_NXZ");
|
2016-08-31 18:48:39 +02:00
|
|
|
|
2017-03-01 13:18:01 +01:00
|
|
|
if(sizeof(void*) == 8) { /* 64-bit initialization */
|
|
|
|
SET(p_task_continuation_context_ctor, "??0task_continuation_context@Concurrency@@AEAA@XZ");
|
2017-03-10 16:01:40 +01:00
|
|
|
SET(p__ContextCallback__Assign, "?_Assign@_ContextCallback@details@Concurrency@@AEAAXPEAX@Z");
|
|
|
|
SET(p__ContextCallback__CallInContext, "?_CallInContext@_ContextCallback@details@Concurrency@@QEBAXV?$function@$$A6AXXZ@std@@_N@Z");
|
|
|
|
SET(p__ContextCallback__Capture, "?_Capture@_ContextCallback@details@Concurrency@@AEAAXXZ");
|
|
|
|
SET(p__ContextCallback__Reset, "?_Reset@_ContextCallback@details@Concurrency@@AEAAXXZ");
|
2017-03-15 12:06:29 +01:00
|
|
|
SET(p__TaskEventLogger__LogCancelTask, "?_LogCancelTask@_TaskEventLogger@details@Concurrency@@QEAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogScheduleTask, "?_LogScheduleTask@_TaskEventLogger@details@Concurrency@@QEAAX_N@Z");
|
|
|
|
SET(p__TaskEventLogger__LogTaskCompleted, "?_LogTaskCompleted@_TaskEventLogger@details@Concurrency@@QEAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogTaskExecutionCompleted, "?_LogTaskExecutionCompleted@_TaskEventLogger@details@Concurrency@@QEAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogWorkItemCompleted, "?_LogWorkItemCompleted@_TaskEventLogger@details@Concurrency@@QEAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogWorkItemStarted, "?_LogWorkItemStarted@_TaskEventLogger@details@Concurrency@@QEAAXXZ");
|
2017-03-21 12:13:11 +01:00
|
|
|
SET(p__Schedule_chore, "?_Schedule_chore@details@Concurrency@@YAHPEAU_Threadpool_chore@12@@Z");
|
|
|
|
SET(p__Reschedule_chore, "?_Reschedule_chore@details@Concurrency@@YAHPEBU_Threadpool_chore@12@@Z");
|
|
|
|
SET(p__Release_chore, "?_Release_chore@details@Concurrency@@YAXPEAU_Threadpool_chore@12@@Z");
|
2017-03-01 13:18:01 +01:00
|
|
|
} else {
|
|
|
|
#ifdef __arm__
|
|
|
|
SET(p_task_continuation_context_ctor, "??0task_continuation_context@Concurrency@@AAA@XZ");
|
2017-03-10 16:01:40 +01:00
|
|
|
SET(p__ContextCallback__Assign, "?_Assign@_ContextCallback@details@Concurrency@@AAAXPAX@Z");
|
|
|
|
SET(p__ContextCallback__CallInContext, "?_CallInContext@_ContextCallback@details@Concurrency@@QBAXV?$function@$$A6AXXZ@std@@_N@Z");
|
|
|
|
SET(p__ContextCallback__Capture, "?_Capture@_ContextCallback@details@Concurrency@@AAAXXZ");
|
|
|
|
SET(p__ContextCallback__Reset, "?_Reset@_ContextCallback@details@Concurrency@@AAAXXZ");
|
2017-03-15 12:06:29 +01:00
|
|
|
SET(p__TaskEventLogger__LogCancelTask, "?_LogCancelTask@_TaskEventLogger@details@Concurrency@@QAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogScheduleTask, "?_LogScheduleTask@_TaskEventLogger@details@Concurrency@@QAEX_N@Z");
|
|
|
|
SET(p__TaskEventLogger__LogTaskCompleted, "?_LogTaskCompleted@_TaskEventLogger@details@Concurrency@@QAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogTaskExecutionCompleted, "?_LogTaskExecutionCompleted@_TaskEventLogger@details@Concurrency@@QAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogWorkItemCompleted, "?_LogWorkItemCompleted@_TaskEventLogger@details@Concurrency@@QAAXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogWorkItemStarted, "?_LogWorkItemStarted@_TaskEventLogger@details@Concurrency@@QAAXXZ");
|
2017-03-01 13:18:01 +01:00
|
|
|
#else
|
|
|
|
SET(p_task_continuation_context_ctor, "??0task_continuation_context@Concurrency@@AAE@XZ");
|
2017-03-10 16:01:40 +01:00
|
|
|
SET(p__ContextCallback__Assign, "?_Assign@_ContextCallback@details@Concurrency@@AAEXPAX@Z");
|
|
|
|
SET(p__ContextCallback__CallInContext, "?_CallInContext@_ContextCallback@details@Concurrency@@QBEXV?$function@$$A6AXXZ@std@@_N@Z");
|
|
|
|
SET(p__ContextCallback__Capture, "?_Capture@_ContextCallback@details@Concurrency@@AAEXXZ");
|
|
|
|
SET(p__ContextCallback__Reset, "?_Reset@_ContextCallback@details@Concurrency@@AAEXXZ");
|
2017-03-15 12:06:29 +01:00
|
|
|
SET(p__TaskEventLogger__LogCancelTask, "?_LogCancelTask@_TaskEventLogger@details@Concurrency@@QAEXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogScheduleTask, "?_LogScheduleTask@_TaskEventLogger@details@Concurrency@@QAEX_N@Z");
|
|
|
|
SET(p__TaskEventLogger__LogTaskCompleted, "?_LogTaskCompleted@_TaskEventLogger@details@Concurrency@@QAEXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogTaskExecutionCompleted, "?_LogTaskExecutionCompleted@_TaskEventLogger@details@Concurrency@@QAEXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogWorkItemCompleted, "?_LogWorkItemCompleted@_TaskEventLogger@details@Concurrency@@QAEXXZ");
|
|
|
|
SET(p__TaskEventLogger__LogWorkItemStarted, "?_LogWorkItemStarted@_TaskEventLogger@details@Concurrency@@QAEXXZ");
|
2017-03-01 13:18:01 +01:00
|
|
|
#endif
|
2017-03-21 12:13:11 +01:00
|
|
|
SET(p__Schedule_chore, "?_Schedule_chore@details@Concurrency@@YAHPAU_Threadpool_chore@12@@Z");
|
|
|
|
SET(p__Reschedule_chore, "?_Reschedule_chore@details@Concurrency@@YAHPBU_Threadpool_chore@12@@Z");
|
|
|
|
SET(p__Release_chore, "?_Release_chore@details@Concurrency@@YAXPAU_Threadpool_chore@12@@Z");
|
2017-03-01 13:18:01 +01:00
|
|
|
}
|
|
|
|
|
2017-07-11 12:30:16 +02:00
|
|
|
SET(p_Current_get, "_Current_get");
|
2017-07-11 12:30:15 +02:00
|
|
|
SET(p_File_size, "_File_size");
|
2017-07-10 12:59:02 +02:00
|
|
|
SET(p_To_byte, "_To_byte");
|
2017-05-26 14:52:08 +02:00
|
|
|
SET(p_To_wide, "_To_wide");
|
|
|
|
|
2017-03-01 13:18:01 +01:00
|
|
|
init_thiscall_thunk();
|
2016-08-31 18:48:39 +02:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_thrd(void)
|
|
|
|
{
|
|
|
|
ok(p__Thrd_id() == GetCurrentThreadId(),
|
|
|
|
"expected same id, got _Thrd_id %u GetCurrentThreadId %u\n",
|
|
|
|
p__Thrd_id(), GetCurrentThreadId());
|
|
|
|
}
|
|
|
|
|
2016-10-04 10:10:05 +02:00
|
|
|
static struct {
|
|
|
|
int value[2];
|
|
|
|
const char* export_name;
|
|
|
|
} vbtable_size_exports_list[] = {
|
|
|
|
{{0x20, 0x20}, "??_8?$basic_iostream@DU?$char_traits@D@std@@@std@@7B?$basic_istream@DU?$char_traits@D@std@@@1@@"},
|
|
|
|
{{0x10, 0x10}, "??_8?$basic_iostream@DU?$char_traits@D@std@@@std@@7B?$basic_ostream@DU?$char_traits@D@std@@@1@@"},
|
|
|
|
{{0x20, 0x20}, "??_8?$basic_iostream@GU?$char_traits@G@std@@@std@@7B?$basic_istream@GU?$char_traits@G@std@@@1@@"},
|
|
|
|
{{0x10, 0x10}, "??_8?$basic_iostream@GU?$char_traits@G@std@@@std@@7B?$basic_ostream@GU?$char_traits@G@std@@@1@@"},
|
|
|
|
{{0x20, 0x20}, "??_8?$basic_iostream@_WU?$char_traits@_W@std@@@std@@7B?$basic_istream@_WU?$char_traits@_W@std@@@1@@"},
|
|
|
|
{{0x10, 0x10}, "??_8?$basic_iostream@_WU?$char_traits@_W@std@@@std@@7B?$basic_ostream@_WU?$char_traits@_W@std@@@1@@"},
|
|
|
|
{{0x18, 0x18}, "??_8?$basic_istream@DU?$char_traits@D@std@@@std@@7B@"},
|
|
|
|
{{0x18, 0x18}, "??_8?$basic_istream@GU?$char_traits@G@std@@@std@@7B@"},
|
|
|
|
{{0x18, 0x18}, "??_8?$basic_istream@_WU?$char_traits@_W@std@@@std@@7B@"},
|
|
|
|
{{ 0x8, 0x10}, "??_8?$basic_ostream@DU?$char_traits@D@std@@@std@@7B@"},
|
|
|
|
{{ 0x8, 0x10}, "??_8?$basic_ostream@GU?$char_traits@G@std@@@std@@7B@"},
|
|
|
|
{{ 0x8, 0x10}, "??_8?$basic_ostream@_WU?$char_traits@_W@std@@@std@@7B@"},
|
|
|
|
{{ 0x0, 0x0}, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void test_vbtable_size_exports(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
const int *p_vbtable;
|
|
|
|
int arch_idx = (sizeof(void*) == 8);
|
|
|
|
|
|
|
|
for (i = 0; vbtable_size_exports_list[i].export_name; i++)
|
|
|
|
{
|
|
|
|
SET(p_vbtable, vbtable_size_exports_list[i].export_name);
|
|
|
|
|
|
|
|
ok(p_vbtable[0] == 0, "vbtable[0] wrong, got 0x%x\n", p_vbtable[0]);
|
|
|
|
ok(p_vbtable[1] == vbtable_size_exports_list[i].value[arch_idx],
|
|
|
|
"%d: %s[1] wrong, got 0x%x\n", i, vbtable_size_exports_list[i].export_name, p_vbtable[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-01 13:18:01 +01:00
|
|
|
static void test_task_continuation_context(void)
|
|
|
|
{
|
|
|
|
task_continuation_context tcc;
|
|
|
|
|
2017-05-19 14:30:17 +02:00
|
|
|
memset(&tcc, 0xff, sizeof(tcc));
|
2017-03-01 13:18:01 +01:00
|
|
|
call_func1(p_task_continuation_context_ctor, &tcc);
|
|
|
|
ok(!tcc.unk0, "tcc.unk0 != NULL (%p)\n", tcc.unk0);
|
|
|
|
ok(!tcc.unk1, "tcc.unk1 != 0 (%x)\n", tcc.unk1);
|
|
|
|
}
|
|
|
|
|
2017-03-10 16:01:40 +01:00
|
|
|
#ifdef _WIN64
|
|
|
|
static void __cdecl function_do_call(void *this)
|
|
|
|
{
|
|
|
|
CHECK_EXPECT(function_do_call);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __cdecl function_do_clean(void *this, MSVCP_bool b)
|
|
|
|
{
|
|
|
|
CHECK_EXPECT(function_do_clean);
|
|
|
|
ok(b, "b == FALSE\n");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void test__ContextCallback(void)
|
|
|
|
{
|
|
|
|
_ContextCallback cc = {0};
|
|
|
|
void *v = (void*)0xdeadbeef;
|
|
|
|
#ifdef _WIN64
|
|
|
|
void* function_vtbl[] = {
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
(void*)function_do_call,
|
|
|
|
NULL,
|
|
|
|
(void*)function_do_clean,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
function_void_cdecl_void function = { function_vtbl, NULL, {0}, {NULL}, &function };
|
|
|
|
function_void_cdecl_void function2 = { NULL, NULL, {0}, {NULL}, &function };
|
|
|
|
#endif
|
|
|
|
|
|
|
|
call_func2(p__ContextCallback__Assign, &cc, v);
|
|
|
|
ok(!cc.unused, "cc.unused = %p\n", cc.unused);
|
|
|
|
call_func1(p__ContextCallback__Reset, &cc);
|
|
|
|
ok(!cc.unused, "cc.unused = %p\n", cc.unused);
|
|
|
|
call_func1(p__ContextCallback__Capture, &cc);
|
|
|
|
ok(!cc.unused, "cc.unused = %p\n", cc.unused);
|
|
|
|
ok(!p__ContextCallback__IsCurrentOriginSTA(&cc), "IsCurrentOriginSTA returned TRUE\n");
|
|
|
|
|
|
|
|
cc.unused = v;
|
|
|
|
call_func2(p__ContextCallback__Assign, &cc, NULL);
|
|
|
|
ok(cc.unused == v, "cc.unused = %p\n", cc.unused);
|
|
|
|
call_func1(p__ContextCallback__Reset, &cc);
|
|
|
|
ok(cc.unused == v, "cc.unused = %p\n", cc.unused);
|
|
|
|
call_func1(p__ContextCallback__Capture, &cc);
|
|
|
|
ok(cc.unused == v, "cc.unused = %p\n", cc.unused);
|
|
|
|
ok(!p__ContextCallback__IsCurrentOriginSTA(&cc), "IsCurrentOriginSTA returned TRUE\n");
|
|
|
|
ok(cc.unused == v, "cc.unused = %p\n", cc.unused);
|
|
|
|
|
|
|
|
#ifdef _WIN64
|
|
|
|
SET_EXPECT(function_do_call);
|
|
|
|
SET_EXPECT(function_do_clean);
|
|
|
|
p__ContextCallback__CallInContext(&cc, function, FALSE);
|
|
|
|
CHECK_CALLED(function_do_call);
|
|
|
|
CHECK_CALLED(function_do_clean);
|
|
|
|
|
|
|
|
SET_EXPECT(function_do_call);
|
|
|
|
SET_EXPECT(function_do_clean);
|
|
|
|
p__ContextCallback__CallInContext(&cc, function, TRUE);
|
|
|
|
CHECK_CALLED(function_do_call);
|
|
|
|
CHECK_CALLED(function_do_clean);
|
|
|
|
|
|
|
|
SET_EXPECT(function_do_call);
|
|
|
|
SET_EXPECT(function_do_clean);
|
|
|
|
p__ContextCallback__CallInContext(&cc, function2, FALSE);
|
|
|
|
CHECK_CALLED(function_do_call);
|
|
|
|
CHECK_CALLED(function_do_clean);
|
|
|
|
|
|
|
|
SET_EXPECT(function_do_call);
|
|
|
|
SET_EXPECT(function_do_clean);
|
|
|
|
p__ContextCallback__CallInContext(&cc, function2, TRUE);
|
|
|
|
CHECK_CALLED(function_do_call);
|
|
|
|
CHECK_CALLED(function_do_clean);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-03-15 12:06:29 +01:00
|
|
|
static void test__TaskEventLogger(void)
|
|
|
|
{
|
|
|
|
_TaskEventLogger logger;
|
|
|
|
memset(&logger, 0, sizeof(logger));
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogCancelTask, &logger);
|
|
|
|
ok(!logger.task, "logger.task = %p\n", logger.task);
|
|
|
|
ok(!logger.scheduled, "logger.scheduled = %x\n", logger.scheduled);
|
|
|
|
ok(!logger.started, "logger.started = %x\n", logger.started);
|
|
|
|
|
|
|
|
call_func2(p__TaskEventLogger__LogScheduleTask, &logger, FALSE);
|
|
|
|
ok(!logger.task, "logger.task = %p\n", logger.task);
|
|
|
|
ok(!logger.scheduled, "logger.scheduled = %x\n", logger.scheduled);
|
|
|
|
ok(!logger.started, "logger.started = %x\n", logger.started);
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogTaskCompleted, &logger);
|
|
|
|
ok(!logger.task, "logger.task = %p\n", logger.task);
|
|
|
|
ok(!logger.scheduled, "logger.scheduled = %x\n", logger.scheduled);
|
|
|
|
ok(!logger.started, "logger.started = %x\n", logger.started);
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogTaskExecutionCompleted, &logger);
|
|
|
|
ok(!logger.task, "logger.task = %p\n", logger.task);
|
|
|
|
ok(!logger.scheduled, "logger.scheduled = %x\n", logger.scheduled);
|
|
|
|
ok(!logger.started, "logger.started = %x\n", logger.started);
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogWorkItemCompleted, &logger);
|
|
|
|
ok(!logger.task, "logger.task = %p\n", logger.task);
|
|
|
|
ok(!logger.scheduled, "logger.scheduled = %x\n", logger.scheduled);
|
|
|
|
ok(!logger.started, "logger.started = %x\n", logger.started);
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogWorkItemStarted, &logger);
|
|
|
|
ok(!logger.task, "logger.task = %p\n", logger.task);
|
|
|
|
ok(!logger.scheduled, "logger.scheduled = %x\n", logger.scheduled);
|
|
|
|
ok(!logger.started, "logger.started = %x\n", logger.started);
|
|
|
|
|
|
|
|
logger.task = (void*)0xdeadbeef;
|
|
|
|
logger.scheduled = TRUE;
|
|
|
|
logger.started = TRUE;
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogCancelTask, &logger);
|
|
|
|
ok(logger.task == (void*)0xdeadbeef, "logger.task = %p\n", logger.task);
|
|
|
|
ok(logger.scheduled, "logger.scheduled = FALSE\n");
|
|
|
|
ok(logger.started, "logger.started = FALSE\n");
|
|
|
|
|
|
|
|
call_func2(p__TaskEventLogger__LogScheduleTask, &logger, FALSE);
|
|
|
|
ok(logger.task == (void*)0xdeadbeef, "logger.task = %p\n", logger.task);
|
|
|
|
ok(logger.scheduled, "logger.scheduled = FALSE\n");
|
|
|
|
ok(logger.started, "logger.started = FALSE\n");
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogTaskCompleted, &logger);
|
|
|
|
ok(logger.task == (void*)0xdeadbeef, "logger.task = %p\n", logger.task);
|
|
|
|
ok(logger.scheduled, "logger.scheduled = FALSE\n");
|
|
|
|
ok(logger.started, "logger.started = FALSE\n");
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogTaskExecutionCompleted, &logger);
|
|
|
|
ok(logger.task == (void*)0xdeadbeef, "logger.task = %p\n", logger.task);
|
|
|
|
ok(logger.scheduled, "logger.scheduled = FALSE\n");
|
|
|
|
ok(logger.started, "logger.started = FALSE\n");
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogWorkItemCompleted, &logger);
|
|
|
|
ok(logger.task == (void*)0xdeadbeef, "logger.task = %p\n", logger.task);
|
|
|
|
ok(logger.scheduled, "logger.scheduled = FALSE\n");
|
|
|
|
ok(logger.started, "logger.started = FALSE\n");
|
|
|
|
|
|
|
|
call_func1(p__TaskEventLogger__LogWorkItemStarted, &logger);
|
|
|
|
ok(logger.task == (void*)0xdeadbeef, "logger.task = %p\n", logger.task);
|
|
|
|
ok(logger.scheduled, "logger.scheduled = FALSE\n");
|
|
|
|
ok(logger.started, "logger.started = FALSE\n");
|
|
|
|
}
|
|
|
|
|
2017-04-01 14:36:42 +02:00
|
|
|
static void __cdecl chore_callback(void *arg)
|
2017-03-21 12:13:11 +01:00
|
|
|
{
|
|
|
|
HANDLE event = arg;
|
|
|
|
SetEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_chore(void)
|
|
|
|
{
|
|
|
|
HANDLE event = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
|
|
_Threadpool_chore chore, old_chore;
|
|
|
|
DWORD wait;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
memset(&chore, 0, sizeof(chore));
|
|
|
|
ret = p__Schedule_chore(&chore);
|
|
|
|
ok(!ret, "_Schedule_chore returned %d\n", ret);
|
|
|
|
ok(chore.work != NULL, "chore.work == NULL\n");
|
|
|
|
ok(!chore.callback, "chore.callback != NULL\n");
|
|
|
|
p__Release_chore(&chore);
|
|
|
|
|
|
|
|
chore.work = NULL;
|
|
|
|
chore.callback = chore_callback;
|
|
|
|
chore.arg = event;
|
|
|
|
ret = p__Schedule_chore(&chore);
|
|
|
|
ok(!ret, "_Schedule_chore returned %d\n", ret);
|
|
|
|
ok(chore.work != NULL, "chore.work == NULL\n");
|
|
|
|
ok(chore.callback == chore_callback, "chore.callback = %p, expected %p\n", chore.callback, chore_callback);
|
|
|
|
ok(chore.arg == event, "chore.arg = %p, expected %p\n", chore.arg, event);
|
|
|
|
wait = WaitForSingleObject(event, 500);
|
|
|
|
ok(wait == WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait);
|
|
|
|
|
|
|
|
old_chore = chore;
|
|
|
|
ret = p__Schedule_chore(&chore);
|
|
|
|
ok(!ret, "_Schedule_chore returned %d\n", ret);
|
|
|
|
ok(old_chore.work != chore.work, "new threadpool work was not created\n");
|
|
|
|
p__Release_chore(&old_chore);
|
|
|
|
wait = WaitForSingleObject(event, 500);
|
|
|
|
ok(wait == WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait);
|
|
|
|
|
|
|
|
ret = p__Reschedule_chore(&chore);
|
|
|
|
ok(!ret, "_Reschedule_chore returned %d\n", ret);
|
|
|
|
wait = WaitForSingleObject(event, 500);
|
|
|
|
ok(wait == WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait);
|
|
|
|
|
|
|
|
p__Release_chore(&chore);
|
|
|
|
ok(!chore.work, "chore.work != NULL\n");
|
|
|
|
ok(chore.callback == chore_callback, "chore.callback = %p, expected %p\n", chore.callback, chore_callback);
|
|
|
|
ok(chore.arg == event, "chore.arg = %p, expected %p\n", chore.arg, event);
|
|
|
|
p__Release_chore(&chore);
|
|
|
|
}
|
|
|
|
|
2017-07-10 12:59:02 +02:00
|
|
|
static void test_to_byte(void)
|
|
|
|
{
|
|
|
|
static const WCHAR test_1[] = {'T', 'E', 'S', 'T', 0};
|
|
|
|
static const WCHAR test_2[] = {0x9580, 0x9581, 0x9582, 0x9583, 0}; /* some CJK characters */
|
|
|
|
static const WCHAR *tests[] = {test_1, test_2};
|
|
|
|
|
|
|
|
char dst[MAX_PATH + 4] = "ABC\0XXXXXXX";
|
|
|
|
char compare[MAX_PATH + 4] = "ABC\0XXXXXXX";
|
|
|
|
int ret,expected;
|
|
|
|
unsigned int i, j;
|
|
|
|
WCHAR longstr[MAX_PATH + 3];
|
|
|
|
|
|
|
|
ret = p_To_byte(NULL, NULL);
|
|
|
|
ok(!ret, "Got unexpected result %d\n", ret);
|
|
|
|
ret = p_To_byte(tests[0], NULL);
|
|
|
|
ok(!ret, "Got unexpected result %d\n", ret);
|
|
|
|
ret = p_To_byte(NULL, dst);
|
|
|
|
ok(!ret, "Got unexpected result %d\n", ret);
|
|
|
|
|
|
|
|
ok(!memcmp(dst, compare, sizeof(compare)), "Destination was modified: %s\n", dst);
|
|
|
|
|
|
|
|
for (i = 0; i < sizeof(tests) / sizeof(*tests); ++i)
|
|
|
|
{
|
|
|
|
ret = p_To_byte(tests[i], dst);
|
|
|
|
expected = WideCharToMultiByte(CP_ACP, 0, tests[i], -1, compare, sizeof(compare) / sizeof(*compare),
|
|
|
|
NULL, NULL);
|
|
|
|
ok(ret == expected, "Got unexpected result %d, expected %d, test case %u\n", ret, expected, i);
|
|
|
|
ok(!memcmp(dst, compare, sizeof(compare)), "Got unexpected output %s, test case %u\n", dst, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Output length is limited to MAX_PATH.*/
|
|
|
|
for (i = MAX_PATH - 2; i < MAX_PATH + 2; ++i)
|
|
|
|
{
|
|
|
|
for (j = 0; j < i; j++)
|
|
|
|
longstr[j] = 'A';
|
|
|
|
longstr[i] = 0;
|
|
|
|
memset(dst, 0xff, sizeof(dst));
|
|
|
|
memset(compare, 0xff, sizeof(compare));
|
|
|
|
|
|
|
|
ret = p_To_byte(longstr, dst);
|
|
|
|
expected = WideCharToMultiByte(CP_ACP, 0, longstr, -1, compare, MAX_PATH, NULL, NULL);
|
|
|
|
ok(ret == expected, "Got unexpected result %d, expected %d, length %u\n", ret, expected, i);
|
|
|
|
ok(!memcmp(dst, compare, sizeof(compare)), "Got unexpected output %s, length %u\n", dst, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-26 14:52:08 +02:00
|
|
|
static void test_to_wide(void)
|
|
|
|
{
|
|
|
|
/* öäü߀Ÿ.A.B in cp1252, the two . are an undefined value and delete.
|
|
|
|
* With a different system codepage it will produce different results, so do not hardcode the
|
|
|
|
* expected output but convert it with MultiByteToWideChar. */
|
|
|
|
static const char special_input[] = {0xf6, 0xe4, 0xfc, 0xdf, 0x80, 0x9f, 0x81, 0x41, 0x7f, 0x42, 0};
|
|
|
|
static const char *tests[] = {"Testtest", special_input};
|
|
|
|
WCHAR dst[MAX_PATH + 4] = {'A', 'B', 'C', 0, 'X', 'X', 'X', 'X', 'X', 'X', 'X'};
|
|
|
|
WCHAR compare[MAX_PATH + 4] = {'A', 'B', 'C', 0, 'X', 'X', 'X', 'X', 'X', 'X', 'X'};
|
|
|
|
int ret, expected;
|
|
|
|
unsigned int i;
|
|
|
|
char longstr[MAX_PATH + 3];
|
|
|
|
|
|
|
|
ret = p_To_wide(NULL, NULL);
|
|
|
|
ok(!ret, "Got unexpected result %d\n", ret);
|
|
|
|
ret = p_To_wide(tests[0], NULL);
|
|
|
|
ok(!ret, "Got unexpected result %d\n", ret);
|
|
|
|
ret = p_To_wide(NULL, dst);
|
|
|
|
ok(!ret, "Got unexpected result %d\n", ret);
|
|
|
|
ok(!memcmp(dst, compare, sizeof(compare)), "Destination was modified: %s\n", wine_dbgstr_w(dst));
|
|
|
|
|
|
|
|
for (i = 0; i < sizeof(tests) / sizeof(*tests); ++i)
|
|
|
|
{
|
|
|
|
ret = p_To_wide(tests[i], dst);
|
|
|
|
expected = MultiByteToWideChar(CP_ACP, 0, tests[i], -1, compare, sizeof(compare) / sizeof(*compare));
|
|
|
|
ok(ret == expected, "Got unexpected result %d, expected %d, test case %u\n", ret, expected, i);
|
|
|
|
ok(!memcmp(dst, compare, sizeof(compare)), "Got unexpected output %s, test case %u\n",
|
|
|
|
wine_dbgstr_w(dst), i);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Output length is limited to MAX_PATH.*/
|
|
|
|
for (i = MAX_PATH - 2; i < MAX_PATH + 2; ++i)
|
|
|
|
{
|
|
|
|
memset(longstr, 'A', sizeof(longstr));
|
|
|
|
longstr[i] = 0;
|
|
|
|
memset(dst, 0xff, sizeof(dst));
|
|
|
|
memset(compare, 0xff, sizeof(compare));
|
|
|
|
|
|
|
|
ret = p_To_wide(longstr, dst);
|
|
|
|
expected = MultiByteToWideChar(CP_ACP, 0, longstr, -1, compare, MAX_PATH);
|
|
|
|
ok(ret == expected, "Got unexpected result %d, expected %d, length %u\n", ret, expected, i);
|
|
|
|
ok(!memcmp(dst, compare, sizeof(compare)), "Got unexpected output %s, length %u\n",
|
|
|
|
wine_dbgstr_w(dst), i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-11 12:30:15 +02:00
|
|
|
static void test_File_size(void)
|
|
|
|
{
|
|
|
|
ULONGLONG val;
|
|
|
|
HANDLE file;
|
|
|
|
LARGE_INTEGER file_size;
|
|
|
|
WCHAR test_f1_W[] = {'w','i','n','e','_','t','e','s','t','_','d','i','r','/','f','1',0};
|
|
|
|
WCHAR test_f2_W[] = {'w','i','n','e','_','t','e','s','t','_','d','i','r','/','f','2',0};
|
|
|
|
WCHAR test_dir_W[] = {'w','i','n','e','_','t','e','s','t','_','d','i','r',0};
|
|
|
|
WCHAR test_ne_W[] = {'w','i','n','e','_','t','e','s','t','_','d','i','r','/','n','e',0};
|
|
|
|
WCHAR temp_path[MAX_PATH], origin_path[MAX_PATH];
|
|
|
|
|
|
|
|
memset(origin_path, 0, sizeof(origin_path));
|
|
|
|
memset(origin_path, 0, sizeof(temp_path));
|
|
|
|
GetCurrentDirectoryW(MAX_PATH, origin_path);
|
|
|
|
GetTempPathW(MAX_PATH, temp_path);
|
|
|
|
ok(SetCurrentDirectoryW(temp_path), "SetCurrentDirectoryW to temp_path failed\n");
|
|
|
|
|
|
|
|
CreateDirectoryW(test_dir_W, NULL);
|
|
|
|
|
|
|
|
file = CreateFileW(test_f1_W, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
|
|
|
|
ok(file != INVALID_HANDLE_VALUE, "create file failed: INVALID_HANDLE_VALUE\n");
|
|
|
|
file_size.QuadPart = 7;
|
|
|
|
ok(SetFilePointerEx(file, file_size, NULL, FILE_BEGIN), "SetFilePointerEx failed\n");
|
|
|
|
ok(SetEndOfFile(file), "SetEndOfFile failed\n");
|
|
|
|
CloseHandle(file);
|
|
|
|
val = p_File_size(test_f1_W);
|
|
|
|
ok(val == 7, "file_size is %s\n", wine_dbgstr_longlong(val));
|
|
|
|
|
|
|
|
file = CreateFileW(test_f2_W, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
|
|
|
|
ok(file != INVALID_HANDLE_VALUE, "create file failed: INVALID_HANDLE_VALUE\n");
|
|
|
|
CloseHandle(file);
|
|
|
|
val = p_File_size(test_f2_W);
|
|
|
|
ok(val == 0, "file_size is %s\n", wine_dbgstr_longlong(val));
|
|
|
|
|
|
|
|
val = p_File_size(test_dir_W);
|
|
|
|
ok(val == 0, "file_size is %s\n", wine_dbgstr_longlong(val));
|
|
|
|
|
|
|
|
errno = 0xdeadbeef;
|
|
|
|
val = p_File_size(test_ne_W);
|
|
|
|
ok(val == ~(ULONGLONG)0, "file_size is %s\n", wine_dbgstr_longlong(val));
|
|
|
|
ok(errno == 0xdeadbeef, "errno = %d\n", errno);
|
|
|
|
|
|
|
|
errno = 0xdeadbeef;
|
|
|
|
val = p_File_size(NULL);
|
|
|
|
ok(val == ~(ULONGLONG)0, "file_size is %s\n", wine_dbgstr_longlong(val));
|
|
|
|
ok(errno == 0xdeadbeef, "errno = %d\n", errno);
|
|
|
|
|
|
|
|
ok(DeleteFileW(test_f1_W), "expect wine_test_dir/f1 to exist\n");
|
|
|
|
ok(DeleteFileW(test_f2_W), "expect wine_test_dir/f2 to exist\n");
|
|
|
|
ok(RemoveDirectoryW(test_dir_W), "expect wine_test_dir to exist\n");
|
|
|
|
ok(SetCurrentDirectoryW(origin_path), "SetCurrentDirectoryW to origin_path failed\n");
|
|
|
|
}
|
|
|
|
|
2017-07-11 12:30:16 +02:00
|
|
|
static void test_Current_get(void)
|
|
|
|
{
|
|
|
|
WCHAR temp_path[MAX_PATH], current_path[MAX_PATH], origin_path[MAX_PATH];
|
|
|
|
BOOL ret;
|
|
|
|
memset(origin_path, 0, sizeof(origin_path));
|
|
|
|
GetCurrentDirectoryW(MAX_PATH, origin_path);
|
|
|
|
memset(temp_path, 0, sizeof(temp_path));
|
|
|
|
GetTempPathW(MAX_PATH, temp_path);
|
|
|
|
|
|
|
|
ok(SetCurrentDirectoryW(temp_path), "SetCurrentDirectoryW to temp_path failed\n");
|
|
|
|
memset(current_path, 0, sizeof(current_path));
|
|
|
|
ret = p_Current_get(current_path);
|
|
|
|
ok(ret == TRUE, "p_Current_get returned %u\n", ret);
|
|
|
|
current_path[wcslen(current_path)] = '\\';
|
|
|
|
ok(!wcscmp(temp_path, current_path), "p_Current_get(): expect: %s, got %s\n",
|
|
|
|
wine_dbgstr_w(temp_path), wine_dbgstr_w(current_path));
|
|
|
|
|
|
|
|
ok(SetCurrentDirectoryW(origin_path), "SetCurrentDirectoryW to origin_path failed\n");
|
|
|
|
memset(current_path, 0, sizeof(current_path));
|
|
|
|
ret = p_Current_get(current_path);
|
|
|
|
ok(ret == TRUE, "p_Current_get returned %u\n", ret);
|
|
|
|
ok(!wcscmp(origin_path, current_path), "p_Current_get(): expect: %s, got %s\n",
|
|
|
|
wine_dbgstr_w(origin_path), wine_dbgstr_w(current_path));
|
|
|
|
}
|
|
|
|
|
2016-08-31 18:48:39 +02:00
|
|
|
START_TEST(msvcp140)
|
|
|
|
{
|
|
|
|
if(!init()) return;
|
|
|
|
test_thrd();
|
2016-10-04 10:10:05 +02:00
|
|
|
test_vbtable_size_exports();
|
2017-03-01 13:18:01 +01:00
|
|
|
test_task_continuation_context();
|
2017-03-10 16:01:40 +01:00
|
|
|
test__ContextCallback();
|
2017-03-15 12:06:29 +01:00
|
|
|
test__TaskEventLogger();
|
2017-03-21 12:13:11 +01:00
|
|
|
test_chore();
|
2017-07-10 12:59:02 +02:00
|
|
|
test_to_byte();
|
2017-05-26 14:52:08 +02:00
|
|
|
test_to_wide();
|
2017-07-11 12:30:15 +02:00
|
|
|
test_File_size();
|
2017-07-11 12:30:16 +02:00
|
|
|
test_Current_get();
|
2016-08-31 18:48:39 +02:00
|
|
|
FreeLibrary(msvcp);
|
|
|
|
}
|