More WPF foundations/boilerplate work.

This commit is contained in:
Pietro Gagliardi 2015-11-25 14:38:40 -05:00
parent 896beb036d
commit f93d9a4c91
11 changed files with 448 additions and 38 deletions

View File

@ -8,10 +8,7 @@ HFONT hMessageFont;
HBRUSH hollowBrush;
struct uiInitError {
char *msg;
char failbuf[256];
};
// TODO this won't work if initAlloc() failed
#define initErrorFormat L"error %s: %s%s%s %I32u (0x%I32X)%s"
#define initErrorArgs wmessage, sysmsg, beforele, label, value, value, afterle

93
wpf/alloc.c Normal file
View File

@ -0,0 +1,93 @@
// 4 december 2014
#ifdef __cplusplus
#error msbuild is being dumb and making this a C++ file
#endif
#include "unmanaged.h"
#include <stdio.h>
// wrappers for allocator of choice
// panics on memory exhausted, undefined on heap corruption or other unreliably-detected malady (see http://stackoverflow.com/questions/28761680/is-there-a-windows-api-memory-allocator-deallocator-i-can-use-that-will-just-giv)
// new memory is set to zero
// passing NULL to tableRealloc() acts like tableAlloc()
// passing NULL to tableFree() is a no-op
static HANDLE heap;
int initAlloc(void)
{
heap = HeapCreate(0, 0, 0);
return heap != NULL;
}
#define UINT8(p) ((uint8_t *) (p))
#define PVOID(p) ((void *) (p))
#define EXTRA (sizeof (const char **))
#define DATA(p) PVOID(UINT8(p) + EXTRA)
#define BASE(p) PVOID(UINT8(p) - EXTRA)
#define CCHAR(p) ((const char **) (p))
#define TYPE(p) CCHAR(UINT8(p))
void uninitAlloc(void)
{
BOOL hasEntry;
PROCESS_HEAP_ENTRY phe;
DWORD le;
hasEntry = FALSE;
ZeroMemory(&phe, sizeof (PROCESS_HEAP_ENTRY));
while (HeapWalk(heap, &phe) != 0) {
// skip non-allocations
if ((phe.wFlags & PROCESS_HEAP_ENTRY_BUSY) == 0)
continue;
if (!hasEntry) {
fprintf(stderr, "[libui] leaked allocations:\n");
hasEntry = TRUE;
}
fprintf(stderr, "[libui] %p %s\n", phe.lpData, *TYPE(phe.lpData));
}
le = GetLastError();
SetLastError(le); // just in case
if (le != ERROR_NO_MORE_ITEMS)
logLastError("error walking heap in uninitAlloc()");
if (hasEntry)
complain("either you left something around or there's a bug in libui");
if (HeapDestroy(heap) == 0)
logLastError("error destroying heap in uninitAlloc()");
}
void *uiAlloc(size_t size, const char *type)
{
void *out;
out = HeapAlloc(heap, HEAP_ZERO_MEMORY, EXTRA + size);
if (out == NULL) {
fprintf(stderr, "memory exhausted in uiAlloc()\n");
abort();
}
*TYPE(out) = type;
return DATA(out);
}
void *uiRealloc(void *p, size_t size, const char *type)
{
void *out;
if (p == NULL)
return uiAlloc(size, type);
p = BASE(p);
out = HeapReAlloc(heap, HEAP_ZERO_MEMORY, p, EXTRA + size);
if (out == NULL) {
fprintf(stderr, "memory exhausted in uiRealloc()\n");
abort();
}
return DATA(out);
}
void uiFree(void *p)
{
if (p == NULL)
complain("attempt to uiFree(NULL); there's a bug somewhere");
p = BASE(p);
if (HeapFree(heap, 0, p) == 0)
logLastError("error freeing memory in uiFree()");
}

111
wpf/debug.c Normal file
View File

@ -0,0 +1,111 @@
// 25 february 2015
#include "unmanaged.h"
// uncomment the following line to enable debug messages
#define tableDebug
// uncomment the following line to halt on a debug message
#define tableDebugStop
#ifdef tableDebug
#include <stdio.h>
HRESULT logLastError(const char *context)
{
DWORD le;
WCHAR *msg;
BOOL parenthesize = FALSE;
BOOL localFreeFailed = FALSE;
DWORD localFreeLastError;
le = GetLastError();
fprintf(stderr, "%s: ", context);
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, le, 0, (LPWSTR) (&msg), 0, NULL) != 0) {
fprintf(stderr, "%S (", msg);
if (LocalFree(msg) != NULL) {
localFreeFailed = TRUE;
localFreeLastError = GetLastError();
}
parenthesize = TRUE;
}
fprintf(stderr, "GetLastError() == %I32u", le);
if (parenthesize)
fprintf(stderr, ")");
if (localFreeFailed)
fprintf(stderr, "; local free of system message failed with last error %I32u", localFreeLastError);
fprintf(stderr, "\n");
#ifdef tableDebugStop
DebugBreak();
#endif
SetLastError(le);
// a function does not have to set a last error
// if the last error we get is actually 0, then HRESULT_FROM_WIN32(0) will return S_OK (0 cast to an HRESULT, since 0 <= 0), which we don't want
// prevent this by returning E_FAIL, so the rest of the Table code doesn't barge onward
if (le == 0)
return E_FAIL;
return HRESULT_FROM_WIN32(le);
}
HRESULT logHRESULT(const char *context, HRESULT hr)
{
WCHAR *msg;
BOOL parenthesize = FALSE;
BOOL localFreeFailed = FALSE;
DWORD localFreeLastError;
fprintf(stderr, "%s: ", context);
// this isn't technically documented, but everyone does it, including Microsoft (see the implementation of _com_error::ErrorMessage() in a copy of comdef.h that comes with the Windows DDK)
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD) hr, 0, (LPWSTR) (&msg), 0, NULL) != 0) {
fprintf(stderr, "%S (", msg);
if (LocalFree(msg) != NULL) {
localFreeFailed = TRUE;
localFreeLastError = GetLastError();
}
parenthesize = TRUE;
}
fprintf(stderr, "HRESULT == 0x%I32X", hr);
if (parenthesize)
fprintf(stderr, ")");
if (localFreeFailed)
fprintf(stderr, "; local free of system message failed with last error %I32u", localFreeLastError);
fprintf(stderr, "\n");
#ifdef tableDebugStop
DebugBreak();
#endif
return hr;
}
HRESULT logMemoryExhausted(const char *reason)
{
fprintf(stderr, "memory exhausted %s\n", reason);
#ifdef tableDebugStop
DebugBreak();
#endif
return E_OUTOFMEMORY;
}
#else
HRESULT logLastError(const char *reason)
{
DWORD le;
le = GetLastError();
// we shouldn't need to do this, but let's do this anyway just to be safe
SetLastError(le);
if (le == 0)
return E_FAIL;
return HRESULT_FROM_WIN32(le);
}
HRESULT logHRESULT(const char *reason, HRESULT hr)
{
return hr;
}
HRESULT logMemoryExhausted(const char *reason)
{
return E_OUTOFMEMORY;
}
#endif

View File

@ -2,11 +2,58 @@
#ifdef __cplusplus
#error msbuild is being dumb and making this a C++ file
#endif
#include "winapi.h"
#include "../ui.h"
// TODO to make sure wpfInit() is exported properly
#include "wpf.h"
#include "unmanaged.h"
// TODO this won't work if initAlloc() failed
#define initErrorFormat L"error %s: %s%s%s %I32u (0x%I32X)%s"
#define initErrorArgs wmessage, sysmsg, beforele, label, value, value, afterle
static const char *initerr(const char *message, const WCHAR *label, DWORD value)
{
WCHAR *sysmsg;
BOOL hassysmsg;
WCHAR *beforele;
WCHAR *afterle;
int n;
WCHAR *wmessage;
WCHAR *wstr;
const char *str;
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, value, 0, (LPWSTR) (&sysmsg), 0, NULL) != 0) {
hassysmsg = TRUE;
beforele = L" (";
afterle = L")";
} else {
hassysmsg = FALSE;
sysmsg = L"";
beforele = L"";
afterle = L"";
}
wmessage = toUTF16(message);
n = _scwprintf(initErrorFormat, initErrorArgs);
wstr = (WCHAR *) uiAlloc((n + 1) * sizeof (WCHAR), "WCHAR[]");
snwprintf(wstr, n + 1, initErrorFormat, initErrorArgs);
str = toUTF8(wstr);
uiFree(wstr);
if (hassysmsg)
if (LocalFree(sysmsg) != NULL)
logLastError("error freeing system message in loadLastError()");
uiFree(wmessage);
return str;
}
static const char *loadLastError(const char *message)
{
return initerr(message, L"GetLastError() ==", GetLastError());
}
static const char *loadHRESULT(const char *message, HRESULT hr)
{
return initerr(message, L"HRESULT", (DWORD) hr);
}
// On the subject of CoInitialize(), or why this isn't in main.cpp:
// If we don't set up the current thread otherwise, the first time .net tries to call out to unmanaged code, it will automatically set up a MTA for COM.
// This is not what we want; we need a STA instead.
// Since we're not in control of main(), we can't stick a [STAThread] on it, so we have to do it ourselves.
@ -15,26 +62,36 @@
// 2) To avoid mixing Windows API headers with .net
// See also http://stackoverflow.com/questions/24348205/how-do-i-solve-this-com-issue-in-c
extern void initWPF(void);
//extern void uninitWPF(void);
uiInitOptions options;
void wpfInit(void)
const char *uiInit(uiInitOptions *o)
{
HRESULT hr;
options = *o;
if (initAlloc() == 0)
return loadLastError("error initializing memory allocations");
// TODO https://msdn.microsoft.com/en-us/library/5s8ee185%28v=vs.71%29.aspx use CoInitializeEx()?
hr = CoInitialize(NULL);
if (hr != S_OK && hr != S_FALSE)
DebugBreak();
return loadHRESULT("initializing COM", hr);
// now do the rest of initialization on the managed side
initWPF();
return NULL;
}
/*TODO
void uiUninit(void)
{
uninitWPF();
CoUninitialize();
uninitAlloc();
}
void uiFreeInitError(const char *err)
{
uiFree((void *) err);
}
*/

View File

@ -70,13 +70,19 @@
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<!-- TODO readd headers? -->
<ClCompile Include="alloc.c">
<CompileAsManaged>false</CompileAsManaged>
</ClCompile>
<ClCompile Include="debug.c">
<CompileAsManaged>false</CompileAsManaged>
</ClCompile>
<ClCompile Include="init.c">
<CompileAsManaged>false</CompileAsManaged>
</ClCompile>
<ClCompile Include="winapi.h" />
<ClCompile Include="..\ui.h" />
<ClCompile Include="main.cpp" />
<ClCompile Include="text.cpp" />
<ClCompile Include="..\common\areaevents.c">
<CompileAsManaged>false</CompileAsManaged>
@ -99,8 +105,6 @@
<ClCompile Include="..\common\types.c">
<CompileAsManaged>false</CompileAsManaged>
</ClCompile>
<ClCompile Include="..\common\uipriv.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

25
wpf/main.cpp Normal file
View File

@ -0,0 +1,25 @@
// 25 november 2015
#include "uipriv_wpf.hpp"
static gcroot<Application ^> *app;
void initWPF(void)
{
app = new gcroot<Application ^>();
*app = gcnew Application();
}
void uninitWPF(void)
{
delete app;
}
void uiMain(void)
{
(*app)->Run();
}
void uiQuit(void)
{
(*app)->Shutdown();
}

34
wpf/text.cpp Normal file
View File

@ -0,0 +1,34 @@
// 25 november 2015
#include "uipriv_wpf.hpp"
#include <string.h>
using namespace System::Text;
using namespace System::Runtime::InteropServices;
// TODO export?
String ^fromUTF8(const char *str)
{
array<Byte> ^bytes;
// TODO avoid the cast
Marshal::Copy(IntPtr((char *) str), bytes, 0, strlen(str));
return Encoding::UTF8->GetString(bytes);
}
// see http://stackoverflow.com/questions/27526093/convert-systemstring-to-stdstring-in-utf8-which-later-converted-to-char-as
char *uiWindowsCLRStringToText(gcroot<System::String ^> str)
{
array<Byte> ^bytes;
char *cstr;
bytes = Encoding::UTF8->GetBytes(str);
cstr = (char *) uiAlloc((bytes->Length + 1) * sizeof (char), "char[]");
Marshal::Copy(bytes, 0, IntPtr(cstr), bytes->Length);
cstr[bytes->Length] = '\0';
return cstr;
}
void uiFreeText(char *c)
{
uiFree(c);
}

67
wpf/ui_wpf.hpp Normal file
View File

@ -0,0 +1,67 @@
// 7 april 2015
/*
This file assumes that you have included <vccrt.h> and "ui.h" beforehand, as well as #using <System.dll> (but not necessarily beforehand). It provides API-specific functions for interfacing with foreign controls in Windows.
*/
#ifndef __LIBUI_UI_WINDOWS_H__
#define __LIBUI_UI_WINDOWS_H__
#ifndef __cplusplus_cli
#error Sorry; ui_wpf.hpp can currently only be used from C++/CLI code.
#endif
extern "C" {
typedef struct uiWindowsSizing uiWindowsSizing;
typedef struct uiWindowsControl uiWindowsControl;
struct uiWindowsControl {
uiControl c;
};
_UI_EXTERN uintmax_t uiWindowsControlType(void);
#define uiWindowsControl(this) ((uiWindowsControl *) uiIsA((this), uiWindowsControlType(), 1))
// TODO document
#define uiWindowsDefineControlWithOnDestroy(type, typefn, handle, onDestroy) \
static uintmax_t _ ## type ## Type = 0; \
uintmax_t typefn(void) \
{ \
if (_ ## type ## Type == 0) \
_ ## type ## Type = uiRegisterType(#type, uiWindowsControlType(), sizeof (type)); \
return _ ## type ## Type; \
} \
static void _ ## type ## CommitDestroy(uiControl *c) \
{ \
type *hthis = type(c); \
onDestroy; \
delete hthis->handle; \
} \
static uintptr_t _ ## type ## Handle(uiControl *c) \
{ \
return (uintptr_t) (type(c)->handle); \
} \
static void _ ## type ## ContainerUpdateState(uiControl *c) \
{ \
/* do nothing */ \
}
#define uiWindowsDefineControl(type, typefn, handle) \
uiWindowsDefineControlWithOnDestroy(type, typefn, handle, (void) hthis;)
#define uiWindowsFinishNewControl(variable, type) \
uiControl(variable)->CommitDestroy = _ ## type ## CommitDestroy; \
uiControl(variable)->Handle = _ ## type ## Handle; \
uiControl(variable)->ContainerUpdateState = _ ## type ## ContainerUpdateState; \
uiWindowsFinishControl(uiControl(variable));
// This is a function used to set up a control.
// Don't call it directly; use uiWindowsFinishNewControl() instead.
_UI_EXTERN void uiWindowsFinishControl(uiControl *c);
// TODO document
_UI_EXTERN char *uiWindowsCLRStringToText(gcroot<System::String ^> str);
}
#endif

17
wpf/uipriv_wpf.hpp Normal file
View File

@ -0,0 +1,17 @@
// 25 november 2015
#include <vcclr.h>
#include "../ui.h"
#include "ui_wpf.hpp"
#include "../common/uipriv.h"
#include "unmanaged.h"
#using <System.dll>
#using <WindowsBase.dll>
#using <PresentationCore.dll>
#using <PresentationFramework.dll>
using namespace System;
using namespace System::ComponentModel;
using namespace System::Windows;
// text.cpp
String ^fromUTF8(const char *);

23
wpf/unmanaged.h Normal file
View File

@ -0,0 +1,23 @@
// 25 november 2015
#ifdef __cplusplus
extern "C" {
#else
#include "winapi.h"
#include "../ui.h"
#include "../common/uipriv.h"
#endif
// main.cpp
extern void initWPF(void);
extern void uninitWPF(void);
// alloc.c
extern int initAlloc(void);
extern void uninitAlloc(void);
// init.c
extern uiInitOptions options;
#ifdef __cplusplus
}
#endif

View File

@ -55,21 +55,3 @@ void wpfWindowOnClosing(wpfWindow *w, void (*f)(wpfWindow *w, void *data), void
w->onClosing = f;
w->onClosingData = data;
}
static gcroot<Application ^> app;
// wpfInit() is in sta.c; see that or details.
extern "C" void initWPF(void)
{
app = gcnew Application();
}
void wpfRun(void)
{
app->Run();
}
void wpfQuit(void)
{
app->Shutdown();
}