Edit
Attach
Printable
topic end
<!-- * Set TOPICTITLE = #define private public - Elementary, my dear Watson! (03 Jul 2007) --> <style type="text/css"> pre {background-color:#ffeecc;} </style> %STARTINCLUDE% <a name="03"></a> ---+++ [[DefinePrivatePublic20070703][Elementary, my dear Watson!]] (03 Jul 2007) <summary> When an application crashes under Windows Vista, it will contact Microsoft's Windows Error Reporting servers; only if those servers request more data about the problem, crashdump files will be generated. Bad luck if you're a developer talking to an enraged customer who just lost his data, and you first have to go through a lengthy process of registering your application on [[http://winqual.microsoft.com][Winqual]], mapping the right application versions, and waiting until the first customer crashdumps show up on the server. Chances are that your customer may have taken his money elsewhere in the meantime while waiting for you to fix his problem. </summary> In [[http://www.clausbrod.de/cgi-bin/view.pl/Blog/DefinePrivatePublic20070627][Think globally, dump locally]], I listed the following workarounds: * Using the =ForceQueue= registry entry to force WER to always produce a crashdump (at the price of losing UI interaction) * Disable the Internet connection before the crash occurs * (Ab-)Using the =CorporateWERServer= registry entry * Deploy Microsoft Operations Manager 2007, including [[http://opsmgr2007.wikidot.com/system:agentless-exception-monitoring][Agentless Exception Monitoring]] (which replaces the older "Corporate Error Reporting" servers) * Copy Dr. Watson from XP to the Vista system and install it as system JIT debugger * Install a top-level crash filter in your application using =SetUnhandledExceptionFilter= In [[http://www.clausbrod.de/cgi-bin/view.pl/Blog/DefinePrivatePublic20070630][Don't dump Vista just yet...]], I presented a little-known Task Manager option which creates a user dump file from any running process. And in this installment of the seemingly never-ending series on Vista crashdumps, we'll explore yet another option. What if we had a system JIT debugger which can be easily installed on a customer system and automatically produces minidump files for any crashing application? Basically a homebrew version of good ol' Dr. Watson, stripped down to bare essentials? <img src="%ATTACHURLPATH%/mydearwatson_install.png" alt="mydearwatson_install.png" width="400" height="142" align="right" /> The following code illustrates this approach. This skeleton application is called =mydearwatson= and installs as a JIT debugger. When an app crashes, it attaches to it and asks the user what kind of minidump (normal or full dump) should be generated. The resulting crashdump file goes to the current user's desktop folder, readily available for sending it off to the developer of the application. <pre> <font color="#0000ff">// mydearwatson</font> <font color="#0000ff">//</font> <font color="#0000ff">// Minimal JIT debugger; attaches to a crashing process </font> <font color="#0000ff">// and generates minidump information. Intended to be used</font> <font color="#0000ff">// as the rough skeleton for a poor man's Dr. Watson </font> <font color="#0000ff">// replacement on Vista.</font> <font color="#0000ff">//</font> <font color="#0000ff">// Written by Claus Brod, <a href="http://www.clausbrod.de/Blog">http://www.clausbrod.de/Blog</a></font> <font color="#a020f0">#include </font><font color="#ff00ff"><windows.h></font> <font color="#a020f0">#include </font><font color="#ff00ff"><DbgHelp.h></font> <font color="#a020f0">#pragma comment(lib, </font><font color="#ff00ff">"DbgHelp.lib"</font><font color="#a020f0">)</font> <font color="#a020f0">#include </font><font color="#ff00ff"><Psapi.h></font> <font color="#a020f0">#include </font><font color="#ff00ff"><shlobj.h></font> <font color="#a020f0">#include </font><font color="#ff00ff"><atlbase.h></font> <font color="#a020f0">#include </font><font color="#ff00ff"><stdio.h></font> <font color="#a020f0">#include </font><font color="#ff00ff"><string.h></font> <font color="#a020f0">#define MSGHDR </font><font color="#ff00ff">"mydearwatson</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font><font color="#a020f0"> \</font> <font color="#a020f0"> </font><font color="#ff00ff">"(C) 2007 Claus Brod, <a href="http://www.clausbrod.de/Blog">http://www.clausbrod.de/Blog</a></font><font color="#6a5acd">\n\n</font><font color="#ff00ff">"</font> <font color="#2e8b57"><b>bool</b></font> uninstall(<font color="#2e8b57"><b>void</b></font>) { CRegKey key; <font color="#804040"><b>if</b></font> (ERROR_SUCCESS != key.Open(HKEY_LOCAL_MACHINE, <font color="#ff00ff">"Software</font><font color="#6a5acd">\\</font><font color="#ff00ff">Microsoft</font><font color="#6a5acd">\\</font><font color="#ff00ff">Windows NT</font><font color="#6a5acd">\\</font><font color="#ff00ff">CurrentVersion</font><font color="#6a5acd">\\</font><font color="#ff00ff">AeDebug</font><font color="#6a5acd">\\</font><font color="#ff00ff">"</font>, KEY_READ | KEY_WRITE)) <font color="#804040"><b>return</b></font> <font color="#ff00ff">false</font>; <font color="#0000ff">// check for old debugger registration</font> <font color="#2e8b57"><b>char</b></font> debuggerCommandLine[MAX_PATH+<font color="#ff00ff">256</font>]; ULONG nChars = _countof(debuggerCommandLine); LONG ret = key.QueryStringValue(<font color="#ff00ff">"PreMydearwatsonDebugger"</font>, debuggerCommandLine, &nChars); <font color="#804040"><b>if</b></font> (ret == ERROR_SUCCESS) { ret = key.SetStringValue(<font color="#ff00ff">"Debugger"</font>, debuggerCommandLine); <font color="#804040"><b>if</b></font> (ret == ERROR_SUCCESS) { ret = key.DeleteValue(<font color="#ff00ff">"PreMydearwatsonDebugger"</font>); } } <font color="#804040"><b>return</b></font> ret == ERROR_SUCCESS; } <font color="#2e8b57"><b>bool</b></font> install(<font color="#2e8b57"><b>char</b></font> *programName) { CRegKey key; <font color="#804040"><b>if</b></font> (ERROR_SUCCESS != key.Open(HKEY_LOCAL_MACHINE, <font color="#ff00ff">"Software</font><font color="#6a5acd">\\</font><font color="#ff00ff">Microsoft</font><font color="#6a5acd">\\</font><font color="#ff00ff">Windows NT</font><font color="#6a5acd">\\</font><font color="#ff00ff">CurrentVersion</font><font color="#6a5acd">\\</font><font color="#ff00ff">AeDebug</font><font color="#6a5acd">\\</font><font color="#ff00ff">"</font>, KEY_READ | KEY_WRITE)) <font color="#804040"><b>return</b></font> <font color="#ff00ff">false</font>; <font color="#2e8b57"><b>char</b></font> debuggerCommandLine[MAX_PATH+<font color="#ff00ff">256</font>]; ULONG nChars = _countof(debuggerCommandLine); <font color="#804040"><b>if</b></font> (ERROR_SUCCESS == key.QueryStringValue(<font color="#ff00ff">"Debugger"</font>, debuggerCommandLine, &nChars)) { _strlwr_s(debuggerCommandLine, _countof(debuggerCommandLine)); <font color="#804040"><b>if</b></font> (!strstr(debuggerCommandLine, <font color="#ff00ff">"mydearwatson"</font>)) { <font color="#0000ff">// save command line for previously installed debugger</font> key.SetStringValue(<font color="#ff00ff">"PreMydearwatsonDebugger"</font>, debuggerCommandLine); } } <font color="#2e8b57"><b>char</b></font> debuggerPath[MAX_PATH]; strcpy_s(debuggerPath, programName); <font color="#0000ff">// preset with default</font> ::GetModuleFileName(GetModuleHandle(<font color="#ff00ff">0</font>), debuggerPath, _countof(debuggerPath)); _snprintf_s(debuggerCommandLine, _countof(debuggerCommandLine), _TRUNCATE, <font color="#ff00ff">"</font><font color="#6a5acd">\"</font><font color="#6a5acd">%s</font><font color="#6a5acd">\"</font><font color="#ff00ff"> -p </font><font color="#6a5acd">%%</font><font color="#ff00ff">ld -e </font><font color="#6a5acd">%%</font><font color="#ff00ff">ld"</font>, debuggerPath); <font color="#804040"><b>return</b></font> ERROR_SUCCESS == key.SetStringValue(<font color="#ff00ff">"Debugger"</font>, debuggerCommandLine); } <font color="#2e8b57"><b>char</b></font> *getMinidumpPath(<font color="#2e8b57"><b>void</b></font>) { <font color="#2e8b57"><b>static</b></font> <font color="#2e8b57"><b>char</b></font> dumpPath[MAX_PATH]; <font color="#804040"><b>if</b></font> (dumpPath[<font color="#ff00ff">0</font>] == <font color="#ff00ff">0</font>) { SHGetSpecialFolderPath(<font color="#ff00ff">NULL</font>, dumpPath, CSIDL_DESKTOPDIRECTORY, FALSE); } <font color="#804040"><b>return</b></font> dumpPath; } <font color="#2e8b57"><b>char</b></font> *getMinidumpFilename(DWORD pid) { <font color="#2e8b57"><b>static</b></font> <font color="#2e8b57"><b>char</b></font> minidumpFilename[MAX_PATH]; <font color="#804040"><b>if</b></font> (!minidumpFilename[<font color="#ff00ff">0</font>]) { _snprintf_s(minidumpFilename, MAX_PATH, _TRUNCATE, <font color="#ff00ff">"</font><font color="#6a5acd">%s</font><font color="#6a5acd">\\</font><font color="#ff00ff">mydearwatson_pid</font><font color="#6a5acd">%d</font><font color="#ff00ff">.mdmp"</font>, getMinidumpPath(), pid); } <font color="#804040"><b>return</b></font> minidumpFilename; } <font color="#2e8b57"><b>bool</b></font> dumpHelper(HANDLE hDumpFile, HANDLE processHandle, DWORD pid, HANDLE threadHandle, DWORD tid, EXCEPTION_RECORD *exc_record, MINIDUMP_TYPE miniDumpType) { <font color="#2e8b57"><b>bool</b></font> ret = <font color="#ff00ff">false</font>; CONTEXT threadContext; threadContext.ContextFlags = CONTEXT_ALL; <font color="#804040"><b>if</b></font> (::GetThreadContext(threadHandle, &threadContext)) { __try { MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; exceptionInfo.ThreadId = tid; EXCEPTION_POINTERS exc_ptr; exc_ptr.ExceptionRecord = exc_record; exc_ptr.ContextRecord = &threadContext; exceptionInfo.ExceptionPointers = &exc_ptr; exceptionInfo.ClientPointers = FALSE; <font color="#804040"><b>if</b></font> (MiniDumpWriteDump(processHandle, pid, hDumpFile, miniDumpType, &exceptionInfo, <font color="#ff00ff">NULL</font>, <font color="#ff00ff">NULL</font>)) ret = <font color="#ff00ff">true</font>; } __except(EXCEPTION_EXECUTE_HANDLER) { } } <font color="#804040"><b>return</b></font> ret; } <font color="#2e8b57"><b>struct</b></font> HandleOnStack { HANDLE m_h; HandleOnStack(HANDLE h) : m_h(h) { } ~HandleOnStack() { <font color="#804040"><b>if</b></font> (m_h && m_h != INVALID_HANDLE_VALUE) CloseHandle(m_h); } <font color="#804040"><b>operator</b></font> HANDLE() { <font color="#804040"><b>return</b></font> m_h; } }; <font color="#2e8b57"><b>bool</b></font> createMinidump(HANDLE processHandle, DWORD pid, DWORD tid, EXCEPTION_RECORD *exc_record) { HandleOnStack hDumpFile(CreateFile(getMinidumpFilename(pid), GENERIC_WRITE, <font color="#ff00ff">0</font>, <font color="#ff00ff">NULL</font>, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, <font color="#ff00ff">NULL</font>)); <font color="#804040"><b>if</b></font> (hDumpFile == INVALID_HANDLE_VALUE) <font color="#804040"><b>return</b></font> <font color="#ff00ff">false</font>; HandleOnStack threadHandle(OpenThread(THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, <font color="#ff00ff">0</font>, tid)); <font color="#804040"><b>if</b></font> (!threadHandle) <font color="#804040"><b>return</b></font> <font color="#ff00ff">false</font>; <font color="#804040"><b>if</b></font> (-<font color="#ff00ff">1</font> == SuspendThread(threadHandle)) <font color="#804040"><b>return</b></font> <font color="#ff00ff">false</font>; MINIDUMP_TYPE miniDumpType = MiniDumpNormal; <font color="#804040"><b>if</b></font> (IDYES == MessageBox(<font color="#ff00ff">NULL</font>, MSGHDR <font color="#ff00ff">"By default, minimal crashdump information is generated.</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font> <font color="#ff00ff">"Do you want full crashdump information instead?"</font>, <font color="#ff00ff">"mydearwatson"</font>, MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2)) { miniDumpType = MiniDumpWithFullMemory; } <font color="#2e8b57"><b>bool</b></font> ret = dumpHelper(hDumpFile, processHandle, pid, threadHandle, tid, exc_record, miniDumpType); <font color="#804040"><b>if</b></font> (ret) { <font color="#2e8b57"><b>char</b></font> buf[<font color="#ff00ff">1024</font>]; _snprintf_s(buf, _countof(buf), _TRUNCATE, MSGHDR <font color="#ff00ff">"Minidump information has been written to</font><font color="#6a5acd">\n</font><font color="#6a5acd">%s</font><font color="#ff00ff">.</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, getMinidumpFilename(pid)); MessageBox(<font color="#ff00ff">NULL</font>, buf, <font color="#ff00ff">"mydearwatson"</font>, MB_OK|MB_ICONINFORMATION); } ResumeThread(threadHandle); <font color="#804040"><b>return</b></font> ret; } <font color="#2e8b57"><b>int</b></font> debuggerLoop(DWORD pid, HANDLE eventHandle) { <font color="#0000ff">// attach to debuggee</font> <font color="#804040"><b>if</b></font> (!DebugActiveProcess(pid)) { fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">"Could not attach to process </font><font color="#6a5acd">%d</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, pid); <font color="#804040"><b>return</b></font> <font color="#ff00ff">1</font>; } HANDLE processHandle = <font color="#ff00ff">0</font>; <font color="#804040"><b>while</b></font> (<font color="#ff00ff">1</font>) { DEBUG_EVENT de; <font color="#804040"><b>if</b></font> (WaitForDebugEvent(&de, INFINITE)) { <font color="#804040"><b>switch</b></font>(de.dwDebugEventCode) { <font color="#804040"><b>case</b></font> CREATE_PROCESS_DEBUG_EVENT: processHandle = de.u.CreateProcessInfo.hProcess; printf(<font color="#ff00ff">"Attaching to process </font><font color="#6a5acd">%x</font><font color="#ff00ff">...</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, processHandle); <font color="#804040"><b>break</b></font>; <font color="#804040"><b>case</b></font> EXCEPTION_DEBUG_EVENT: printf(<font color="#ff00ff">"Exception reported: code=</font><font color="#6a5acd">%x</font><font color="#ff00ff">, dwFirstChance=</font><font color="#6a5acd">%d</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, de.u.Exception.ExceptionRecord.ExceptionCode, de.u.Exception.dwFirstChance); <font color="#804040"><b>if</b></font> (de.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) { SetEvent(eventHandle); } <font color="#804040"><b>else</b></font> { createMinidump(processHandle, de.dwProcessId, de.dwThreadId, &de.u.Exception.ExceptionRecord); ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); <font color="#0000ff">// required?</font> DebugActiveProcessStop(pid); printf(<font color="#ff00ff">"Detached from process, terminating debugger...</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>); <font color="#804040"><b>return</b></font> <font color="#ff00ff">0</font>; } <font color="#804040"><b>break</b></font>; <font color="#804040"><b>default</b></font>: <font color="#0000ff">// printf("debug event code = %d\n", de.dwDebugEventCode);</font> <font color="#804040"><b>break</b></font>; } ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); } } <font color="#0000ff">// while (1)</font> <font color="#804040"><b>return</b></font> <font color="#ff00ff">1</font>; } <font color="#2e8b57"><b>void</b></font> usage(<font color="#2e8b57"><b>char</b></font> *programName) { fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">"To install as JIT debugger:</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>); fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">" </font><font color="#6a5acd">%s</font><font color="#ff00ff"> -i</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, programName); fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">" </font><font color="#6a5acd">%s</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, programName); fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">"To uninstall:</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>); fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">" </font><font color="#6a5acd">%s</font><font color="#ff00ff"> -u</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, programName); fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">"Call as JIT debugger:</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>); fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">" </font><font color="#6a5acd">%s</font><font color="#ff00ff"> -p pid -e eventhandle</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>, programName); } <font color="#2e8b57"><b>int</b></font> main(<font color="#2e8b57"><b>int</b></font> argc, <font color="#2e8b57"><b>char</b></font> *argv[]) { DWORD pid = <font color="#ff00ff">0</font>; HANDLE eventHandle = (HANDLE)<font color="#ff00ff">0</font>; <font color="#2e8b57"><b>bool</b></font> uninstallationMode = <font color="#ff00ff">false</font>; <font color="#2e8b57"><b>bool</b></font> installationMode = <font color="#ff00ff">false</font>; <font color="#804040"><b>if</b></font> (argc == <font color="#ff00ff">1</font>) { <font color="#804040"><b>if</b></font> (IDYES == MessageBox(<font color="#ff00ff">NULL</font>, MSGHDR <font color="#ff00ff">"Do you want to install mydearwatson as the system JIT debugger?"</font>, <font color="#ff00ff">"mydearwatson"</font>, MB_YESNO|MB_ICONQUESTION)) { installationMode = <font color="#ff00ff">true</font>; } } <font color="#804040"><b>for</b></font> (<font color="#2e8b57"><b>int</b></font> i=<font color="#ff00ff">1</font>; i<argc; i++) { <font color="#804040"><b>if</b></font> (!_stricmp(argv[i], <font color="#ff00ff">"-p"</font>)) { pid = atol(argv[i+<font color="#ff00ff">1</font>]); } <font color="#804040"><b>if</b></font> (!_stricmp(argv[i], <font color="#ff00ff">"-e"</font>)) { eventHandle = (HANDLE)atol(argv[i+<font color="#ff00ff">1</font>]); } <font color="#804040"><b>if</b></font> (!_stricmp(argv[i], <font color="#ff00ff">"-i"</font>)) { installationMode = <font color="#ff00ff">true</font>; uninstallationMode = <font color="#ff00ff">false</font>; } <font color="#804040"><b>if</b></font> (!_stricmp(argv[i], <font color="#ff00ff">"-u"</font>)) { uninstallationMode = <font color="#ff00ff">true</font>; installationMode = <font color="#ff00ff">false</font>; } } <font color="#804040"><b>if</b></font> (installationMode) { <font color="#804040"><b>if</b></font> (!install(argv[<font color="#ff00ff">0</font>])) { fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">"Could not register as a JIT debugger.</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>); <font color="#804040"><b>return</b></font> <font color="#ff00ff">1</font>; } <font color="#804040"><b>return</b></font> <font color="#ff00ff">0</font>; } <font color="#804040"><b>if</b></font> (uninstallationMode) { <font color="#804040"><b>if</b></font> (!uninstall()) { fprintf(<font color="#ff00ff">stderr</font>, <font color="#ff00ff">"Could not uninstall.</font><font color="#6a5acd">\n</font><font color="#ff00ff">"</font>); <font color="#804040"><b>return</b></font> <font color="#ff00ff">1</font>; } <font color="#804040"><b>return</b></font> <font color="#ff00ff">0</font>; } <font color="#804040"><b>if</b></font> (!pid || !eventHandle) { usage(argv[<font color="#ff00ff">0</font>]); <font color="#804040"><b>return</b></font> <font color="#ff00ff">2</font>; } <font color="#804040"><b>return</b></font> debuggerLoop(pid, eventHandle); } </pre> To compile and build this code, open a Visual Studio command prompt and enter <pre> cl mydearwatson.cpp </pre> To install =mydearwatson=, run the executable in elevated mode and confirm the installation message box. Now configure Windows Error Reporting to always ask the user before submitting crash data: "Problem Reports and Solutions/Change Settings/Ask me to check if a problem occurs". Alternatively, create a registry value called =Auto= (=REG_SZ=) in =HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug= and set its value to "1". <img src="%ATTACHURLPATH%/mydearwatson_minidump.jpg" alt="mydearwatson_minidump.jpg" width="400" height="179" align="right" /> The next time an application crashes, the usual WER dialog will appear; click the "Debug" option in that dialog. Another message box will be displayed asking what kind of crashdump information should be written. Make your choice, and the crashdump file will magically appear on your desktop. To uninstall, run =mydearwatson= with the =-u= option. =mydearwatson= tries to remember which JIT debugger was installed before, and will reinstall that JIT debugger. The mechanism for doing this is far from perfect, though. If you look at the code, you'll notice that it basically implements a minimal debugger, using Win32 debugging APIs such as =DebugActiveProcess= or =WaitForDebugEvent=. I've never written a debugger before, so I'd assume there are a few subtleties and bugs hidden in this code, but it did work for me on both XP and Vista systems. Test results most welcome. <br clear="all" /> <!-- * Discuss installation as JIT debugger * Discuss debugger loop --> --- %STOPINCLUDE% %COMMENT{type="below" nonotify="on"}% ---
to top
End of topic
Skip to action links
|
Back to top
Edit
|
Attach image or document
|
Printable version
|
Raw text
|
Refresh
|
More topic actions
Revisions: | r1.4 |
>
|
r1.3
|
>
|
r1.2
|
Total page history
|
Backlinks
You are here:
Blog
>
DefinePrivatePublic20070703
r1.4 - 19 Sep 2007 - 22:04 -
ClausBrod
to top
Blog
This site
2017
:
12
-
11
-
10
2016
:
10
-
7
-
3
2015
:
11
-
10
-
9
-
4
-
1
2014
:
5
2013
:
9
-
8
-
7
-
6
-
5
2012
:
2
-
10
2011
:
1
-
8
-
9
-
10
-
12
2010
:
11
-
10
-
9
-
4
2009
:
11
-
9
-
8
-
7
-
6
-
5
-
4
-
3
2008
:
5
-
4
-
3
-
1
2007:
12
-
8
-
7
-
6
-
5
-
4
-
3
-
1
2006:
4
-
3
-
2
-
1
2005:
12
-
6
-
5
-
4
2004:
12
-
11
-
10
C++
CoCreate Modeling
COM & .NET
Java
Mac
Lisp
OpenSource
Scripting
Windows
Stuff
Changes
Index
Search
Maintenance
Impressum
Datenschutzerklärung
Home
Webs
Atari
Blog
Claus
CoCreateModeling
Klassentreffen
Main
Sandbox
Sommelier
TWiki
Xplm
Jump:
Copyright © 1999-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki?
Send feedback