blob: bf3328e6e6dee91e632c14455178e23a7212e773 [file]
/* **********************************************************
* Copyright (c) 2013-2014 Google, Inc. All rights reserved.
* Copyright (c) 2007-2008 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2002-2003 Massachusetts Institute of Technology */
/* Copyright (c) 2002 Hewlett-Packard Company */
// DynamoRIOView.cpp : implementation of the CDynamoRIOView class
//
#include "stdafx.h"
#include "DynamoRIO.h"
#include "DynamoRIODoc.h"
#include "DynamoRIOView.h"
#include "DynamoRIO.h" // for global GetActiveView
#include "LoggingDlg.h"
#include <assert.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CDynamoRIOView
IMPLEMENT_DYNCREATE(CDynamoRIOView, CFormView)
BEGIN_MESSAGE_MAP(CDynamoRIOView, CFormView)
//{{AFX_MSG_MAP(CDynamoRIOView)
#ifndef DRSTATS_DEMO
ON_BN_CLICKED(IDC_CHANGE_LOGGING, OnChangeLogging)
ON_BN_CLICKED(IDC_LOGDIR_EXPLORE, OnLogDirExplore)
#endif
ON_COMMAND(ID_EDIT_COPYSTATS, OnEditCopystats)
ON_WM_VSCROLL()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)
// process list
ON_CBN_SELCHANGE(IDC_PROCESS_LIST, OnSelchangeList)
ON_CBN_DROPDOWN(IDC_PROCESS_LIST, OnDropdownList)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CDynamoRIOView construction/destruction
VOID CALLBACK TimerProc(
HWND /*hwnd*/, // handle of window for timer messages
UINT /*uMsg*/, // WM_TIMER message
UINT_PTR idEvent, // timer identifier
DWORD /*dwTime*/ // current system time
)
{
CDynamoRIOView * view = CDynamoRIOApp::GetActiveView();
if (view == NULL) {
KillTimer(NULL, idEvent);
return;
}
BOOL cont = view->Refresh();
if (!cont) {
KillTimer(NULL, idEvent);
}
}
void CDynamoRIOView::ZeroStrings()
{
m_Exited = _T("");
m_ClientStats = _T("");
#ifndef DRSTATS_DEMO
m_LogLevel = _T("0");
m_LogMask = _T("0x0000");
m_LogDir = _T("");
#endif
}
CDynamoRIOView::CDynamoRIOView()
: CFormView(CDynamoRIOView::IDD)
{
//{{AFX_DATA_INIT(CDynamoRIOView)
//}}AFX_DATA_INIT
// TODO: add construction code here
ZeroStrings();
m_stats = NULL;
m_clientMap = NULL;
m_clientView = NULL;
m_clientStats = NULL;
m_list_pos = 0;
m_selected_pid = 0;
m_StatsViewLines = 0;
OSVERSIONINFOW version;
CDynamoRIOApp::GetWindowsVersion(&version);
if (version.dwPlatformId == VER_PLATFORM_WIN32_NT && version.dwMajorVersion == 4)
m_windows_NT = TRUE;
else
m_windows_NT = FALSE;
}
void CDynamoRIOView::ClearData()
{
if (m_stats != NULL) {
free_dynamorio_stats(m_stats);
m_stats = NULL;
}
if (m_clientMap != NULL) {
assert(m_clientView != NULL);
UnmapViewOfFile(m_clientView);
CloseHandle(m_clientMap);
m_clientMap = m_clientView = m_clientStats = NULL;
}
}
CDynamoRIOView::~CDynamoRIOView()
{
ClearData();
}
void CDynamoRIOView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDynamoRIOView)
DDX_Control(pDX, IDC_PROCESS_LIST, m_ProcessList);
DDX_Control(pDX, IDC_STATS, m_StatsCtl);
DDX_Control(pDX, IDC_STATS_SCROLLBAR, m_StatsSB);
DDX_Text(pDX, IDC_CLIENTSTATS, m_ClientStats);
DDX_Text(pDX, IDC_EXITED, m_Exited);
#ifndef DRSTATS_DEMO
DDX_Text(pDX, IDC_LOGLEVEL_VALUE, m_LogLevel);
DDX_Text(pDX, IDC_LOGMASK_VALUE, m_LogMask);
DDX_Text(pDX, IDC_LOGDIR, m_LogDir);
#endif
//}}AFX_DATA_MAP
}
BOOL CDynamoRIOView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CFormView::PreCreateWindow(cs);
}
void CDynamoRIOView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
OnDropdownList();
// 100 flashes too much with long stats list
// ::SetTimer(NULL, NULL, 100, TimerProc);
::SetTimer(NULL, NULL, 200, TimerProc);
}
/////////////////////////////////////////////////////////////////////////////
// CDynamoRIOView printing
BOOL CDynamoRIOView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
return DoPreparePrinting(pInfo);
}
void CDynamoRIOView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
}
void CDynamoRIOView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
}
void CDynamoRIOView::OnPrint(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add customized printing code here
}
/////////////////////////////////////////////////////////////////////////////
// CDynamoRIOView diagnostics
#ifdef _DEBUG
void CDynamoRIOView::AssertValid() const
{
CFormView::AssertValid();
}
void CDynamoRIOView::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}
CDynamoRIODoc* CDynamoRIOView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDynamoRIODoc)));
return (CDynamoRIODoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CDynamoRIOView message handlers
/////////////////////////////////////////////////////////////////////////////
BOOL
pw_callback_under_dr(process_info_t *pi, void **param)
{
TCHAR *resstr;
TCHAR reschar;
int res;
TCHAR buf[MAXIMUM_PATH];
DWORD version;
BOOL under_dr;
CDynamoRIOView *view = (CDynamoRIOView *) param;
version = 0;
res = under_dynamorio_ex(pi->ProcessID, &version);
switch (res) {
case DLL_PROFILE : resstr=_T("SC profile"); reschar=_T('P'); break;
case DLL_RELEASE : resstr=_T("SC release"); reschar=_T('R'); break;
case DLL_DEBUG : resstr=_T("SC debug"); reschar=_T('D'); break;
case DLL_CUSTOM : resstr=_T("SC custom"); reschar=_T('C'); break;
case DLL_NONE : resstr=_T("native"); reschar=_T('N'); break;
case DLL_UNKNOWN :
default : resstr=_T("<error>"); reschar=_T('?');
}
under_dr = !(res == DLL_NONE || res == DLL_UNKNOWN);
if (under_dr) {
_stprintf(buf, _T("%5d %c %") UNICODE_PRINTF,
pi->ProcessID, reschar, pi->ProcessName);
view->m_ProcessList.InsertString(view->m_list_pos, buf);
view->m_ProcessList.SetItemData(view->m_list_pos, pi->ProcessID);
view->m_list_pos++;
}
return TRUE;
}
void CDynamoRIOView::EnumerateInstances()
{
// clear all old data
ClearData();
m_ProcessList.ResetContent();
m_list_pos = 0;
process_walk(&pw_callback_under_dr, (void **)this);
// now insert the 0th entry, the default selection
if (m_ProcessList.GetCount() > 0) {
m_ProcessList.InsertString(0, _T("<select an instance>"));
} else {
m_ProcessList.InsertString(0, _T("<no instances to view>"));
}
m_ProcessList.SetItemData(0, 0);
m_ProcessList.SetCurSel(0);
CString pname;
m_ProcessList.GetLBText(m_ProcessList.GetCurSel(), pname);
GetDocument()->SetTitle(pname);
}
void CDynamoRIOView::OnSelchangeList()
{
TCHAR buf[128];
ClearData();
m_selected_pid = (DWORD) m_ProcessList.GetItemData(m_ProcessList.GetCurSel());
// find the client stats shared memory that corresponds to this process
HANDLE statsCountMap =
OpenFileMapping(FILE_MAP_READ, FALSE,
m_windows_NT ? TEXT(CLIENT_SHMEM_KEY_NT) :
TEXT(CLIENT_SHMEM_KEY));
if (statsCountMap != NULL) {
PVOID statsCountView = MapViewOfFile(statsCountMap, FILE_MAP_READ, 0, 0, 0);
assert(statsCountView != NULL);
int statsCount = *((int *)statsCountView);
int num = 0;
// see flakiness comments above on main stats count: we go to +20 as workaround
while (num < statsCount+20) {
_stprintf(buf, _T("%")ASCII_PRINTF _T(".%03d"),
m_windows_NT ? CLIENT_SHMEM_KEY_NT : CLIENT_SHMEM_KEY, num);
m_clientMap = OpenFileMapping(FILE_MAP_READ, FALSE, buf);
if (m_clientMap != NULL) {
m_clientView = MapViewOfFile(m_clientMap, FILE_MAP_READ, 0, 0, 0);
assert(m_clientView != NULL);
m_clientStats = (client_stats *) m_clientView;
if (m_clientStats->pid == m_selected_pid)
break;
UnmapViewOfFile(m_clientView);
CloseHandle(m_clientMap);
m_clientMap = m_clientView = m_clientStats = NULL;
}
num++;
}
UnmapViewOfFile(statsCountView);
CloseHandle(statsCountMap);
}
m_StatsSB.SetScrollPos(0);
Refresh();
}
void CDynamoRIOView::OnDropdownList()
{
EnumerateInstances();
}
BOOL CDynamoRIOView::SelectProcess(int pid)
{
EnumerateInstances();
TCHAR prefix[16];
_stprintf(prefix, _T("%5d"), pid);
int num = m_ProcessList.FindString(-1, prefix);
if (num == CB_ERR)
return FALSE;
m_ProcessList.SetCurSel(num);
OnSelchangeList();
return TRUE;
}
BOOL CDynamoRIOView::UpdateProcessList()
{
EnumerateInstances();
CString first;
m_ProcessList.GetLBText(0, first);
if (first.Compare(_T("<select an instance>")) != 0)
return FALSE;
// Incredibly, VC6.0 says "error unreachable code" if I use an else here
return TRUE;
}
#ifdef X64
/* For x64 we don't go all the way to %19 (would have to resize the window or
* really shrink the names). Instead we add 3 more digits which gets us
* up to hundreds of billions, and we shrunk stat names by 3 to 47.
*/
/* SZFC is 2 literals => _T only gets first, so we manually construct: */
# define CLIENT_STAT_PFMT _T("%13")_T(INT64_FORMAT)_T("u")
# define DR_STAT_PFMT _T("%10")_T(INT64_FORMAT)_T("u")
#else
/* 13 is beyond 32-bit reach but this lines everything up and is consistent w/ x64 */
# define CLIENT_STAT_PFMT _T("%13")_T(SZFC)
# define DR_STAT_PFMT _T("%10")_T(SZFC)
#endif
uint CDynamoRIOView::PrintStat(TCHAR *c, uint i, BOOL filter)
{
if (filter) {
#if 0
// Filter out persisted cache stat for now
// FIXME: have "show 0 values" checkbox
// FIXME: need to count up stats that match filter and use that
// for scrollbar max, else get flicker and empty space at bottom
if (strncmp(m_stats->stats[i].name, "Persisted caches",
strlen("Persisted caches")) == 0)
return 0;
#endif
}
return _stprintf(c, _T("%*.*") ASCII_PRINTF _T(" = ") DR_STAT_PFMT _T("\r\n"),
BUFFER_SIZE_ELEMENTS(m_stats->stats[i].name),
BUFFER_SIZE_ELEMENTS(m_stats->stats[i].name),
m_stats->stats[i].name, m_stats->stats[i].value);
}
uint CDynamoRIOView::PrintClientStats(TCHAR *c, TCHAR *max)
{
if (m_clientStats == NULL)
return 0;
#define CLIENTSTAT_NAME_MAX_SHOW CLIENTSTAT_NAME_MAX_LEN
uint i;
TCHAR *start = c;
char (*names)[CLIENTSTAT_NAME_MAX_LEN] =
(char (*)[CLIENTSTAT_NAME_MAX_LEN]) m_clientStats->data;
stats_int_t *vals = (stats_int_t *)
((char*)m_clientStats->data +
m_clientStats->num_stats*CLIENTSTAT_NAME_MAX_LEN*sizeof(char));
/* account for struct alignment */
vals = (stats_int_t *) ALIGN_FORWARD(vals, sizeof(stats_int_t));
for (i=0; i<m_clientStats->num_stats; i++) {
if (c >= max - CLIENTSTAT_NAME_MAX_SHOW*2 - 3)
break;
c += _stprintf(c, _T("%*.*") ASCII_PRINTF _T(" = ") CLIENT_STAT_PFMT _T("\r\n"),
CLIENTSTAT_NAME_MAX_SHOW, CLIENTSTAT_NAME_MAX_SHOW,
names[i], vals[i]);
assert(c < max);
}
return (uint) (c - start);
}
// FIXME: resize stats boxes w/ dialog resize:
// http://www.codeguru.com/forum/showthread.php?t=79384
BOOL CDynamoRIOView::Refresh()
{
if (m_selected_pid <= 0) {
ZeroStrings();
return TRUE;
}
// We have to grab new stats every refresh
dr_statistics_t *new_stats = get_dynamorio_stats(m_selected_pid);
if (new_stats == NULL) {
if (m_stats != NULL) {
// Leave the stats for an exited process in place, for viewing
// and copying
} else
return TRUE;
} else {
if (m_stats != NULL)
free_dynamorio_stats(m_stats);
m_stats = new_stats;
}
uint i;
#define MAX_VISIBLE_STATS 75
#define STATS_BUFSZ (MAX_VISIBLE_STATS*(sizeof(single_stat_t)*2/*cover %10u, etc.*/))
TCHAR buf[STATS_BUFSZ];
TCHAR *c = buf;
buf[0] = _T('\0');
// If I just use a CString hooked up via DDX, re-setting the
// text causes the top of the visible box to show the top of
// the stats string; plus the scrollbar goes to the top,
// unless held in place; probably any reset of the string
// does an auto-reset of the display and scrollbar position.
// To have all the text in there do this:
// int scroll_pos = m_StatsCtl.GetScrollPos(SB_VERT);
// m_StatsCtl.SetWindowText(buf);
// m_StatsCtl.LineScroll(scroll_pos);
// But then there's a lot of flickering: too much.
// We only put visible text lines in the CEdit box, to reduce the
// flicker. It's too hard to do it w/ the CEdit's own scrollbar,
// which sets itself based on the actual text, so we have a
// separate one.
// HACK: I don't know how to find out how many lines will
// fit w/o putting actual text in there
if (m_StatsViewLines == 0) {
for (i = 0; i < m_stats->num_stats &&
i < MAX_VISIBLE_STATS /* can't be more than this */; i++) {
if (c >= &buf[STATS_BUFSZ-STAT_NAME_MAX_LEN*2])
break;
c += PrintStat(c, i, FALSE/*no filter*/);
assert(c < &buf[STATS_BUFSZ-1]);
}
m_StatsCtl.SetWindowText(buf);
UpdateData(FALSE); // write to screen
// I tried having only one screenful of string there, and
// setting the scrollbar range to be larger, but it doesn't
// seem to support that.
RECT rect;
m_StatsCtl.GetRect(&rect);
CPoint pos(rect.right, rect.bottom);
m_StatsViewLines = HIWORD(m_StatsCtl.CharFromPos(pos));
assert(m_StatsViewLines > 0);
m_StatsSB.SetScrollRange(0, m_stats->num_stats-1, TRUE/*redraw*/);
c = buf;
SCROLLINFO info;
info.cbSize = sizeof(SCROLLINFO);
info.fMask = SIF_PAGE;
info.nPage = m_StatsViewLines;
m_StatsSB.SetScrollInfo(&info);
}
int scroll_pos = m_StatsSB.GetScrollPos();
DWORD shown = 0;
uint printed;
for (i = scroll_pos;
i < m_stats->num_stats && shown < m_StatsViewLines; i++) {
if (c >= &buf[STATS_BUFSZ-STAT_NAME_MAX_LEN*2])
break;
printed = PrintStat(c, i, TRUE/*filter*/);
c += printed;
assert(c < &buf[STATS_BUFSZ-1]);
if (printed > 0)
shown++;
}
m_StatsCtl.SetWindowText(buf);
// num_stats could have changed so update
m_StatsSB.SetScrollRange(0, m_stats->num_stats-1, TRUE/*redraw*/);
if (new_stats == NULL)
m_Exited = _T(" Exited"); // line right end up with Running
else
m_Exited = _T("Running");
#ifndef DRSTATS_DEMO
m_LogLevel.Format(_T("%d"), m_stats->loglevel);
m_LogMask.Format(_T("0x%05X"), m_stats->logmask);
m_LogDir.Format(_T("%") ASCII_PRINTF, m_stats->logdir);
#endif
if (m_clientStats != NULL) {
#define CLIENTSTATS_BUFSZ USHRT_MAX
TCHAR buf[CLIENTSTATS_BUFSZ];
PrintClientStats(buf, &buf[CLIENTSTATS_BUFSZ-1]);
m_ClientStats.Format(_T("%s"), buf);
} else
m_ClientStats.Format(_T(""));
UpdateData(FALSE); // write to screen
return TRUE;
}
#ifndef DRSTATS_DEMO
void CDynamoRIOView::OnChangeLogging()
{
if (m_stats == NULL) {
MessageBox(_T("No instance is selected"), _T("Error"), MB_OK | MYMBFLAGS);
return;
}
if (m_stats->loglevel == 0) {
MessageBox(_T("If the application began with log level 0, its logging\n")
_T("cannot be changed.\n"),
_T("Notification"), MB_OK | MYMBFLAGS);
return;
}
CLoggingDlg dlg(m_stats->loglevel, m_stats->logmask);
int res = dlg.DoModal();
if (res == IDCANCEL)
return;
int level = dlg.GetLevel();
int mask = dlg.GetMask();
// FIXME: need to write via drmarker
m_stats->loglevel = level;
m_stats->logmask = mask;
m_LogLevel.Format(_T("%d"), m_stats->loglevel);
m_LogMask.Format(_T("0x%04X"), m_stats->logmask);
UpdateData(FALSE); // write to screen
}
void CDynamoRIOView::OnLogDirExplore()
{
if (m_stats == NULL) {
MessageBox(_T("No instance is selected"), _T("Error"), MB_OK | MYMBFLAGS);
return;
}
UpdateData(TRUE); // get data from controls
if (m_LogDir.Find(_T("<none")) == 0) {
MessageBox(_T("There is no log dir because the loglevel was 0 when the application started"),
_T("Error"), MB_OK | MYMBFLAGS);
return;
}
HINSTANCE res = ShellExecute(m_hWnd, _T("explore"),
m_LogDir, NULL, NULL, SW_SHOWNORMAL);
if ((int)res <= 32) {
TCHAR msg[MAX_PATH*2];
_stprintf(msg, _T("Error exploring %s"), m_LogDir);
MessageBox(msg, _T("Error"), MB_OK | MYMBFLAGS);
}
}
#endif /* DRSTATS_DEMO */
void CDynamoRIOView::OnEditCopystats()
{
if (m_ProcessList.GetCurSel() == 0) {
MessageBox(_T("No instance selected"), _T("Error"), MB_OK | MYMBFLAGS);
return;
}
if (!OpenClipboard()) {
MessageBox(_T("Error opening clipboard"), _T("Error"),
MB_OK | MYMBFLAGS);
return;
}
EmptyClipboard();
#define CLIPBOARD_BUFSZ USHRT_MAX
TCHAR buf[CLIPBOARD_BUFSZ];
TCHAR *pos = buf;
if (m_selected_pid > 0) {
if (m_stats != NULL) {
pos += _stprintf(pos, _T("Process id = %d\r\n"),
m_stats->process_id);
pos += _stprintf(pos, _T("Process name = %") ASCII_PRINTF
_T("\r\n"), m_stats->process_name);
pos += _stprintf(pos, _T("Status = %s\r\n"),
m_Exited.GetBuffer(0));
#ifndef DRSTATS_DEMO
pos += _stprintf(pos, _T("Log mask = %s\r\n"),
m_LogMask.GetBuffer(0));
pos += _stprintf(pos, _T("Log level = %s\r\n"),
m_LogLevel.GetBuffer(0));
pos += _stprintf(pos, _T("Log file = %s\r\n"),
m_LogDir.GetBuffer(0));
#endif
pos += _stprintf(pos, _T("\r\nSTATS\r\n"));
uint i;
for (i = 0; i < m_stats->num_stats; i++) {
if (pos >= &buf[STATS_BUFSZ-STAT_NAME_MAX_LEN*2])
break;
// FIXME: Filter here too
pos += PrintStat(pos, i, TRUE/*filter*/);
assert(pos < &buf[STATS_BUFSZ-1]);
}
} else {
CString pname;
m_ProcessList.GetLBText(m_ProcessList.GetCurSel(), pname);
_stprintf(pos, _T("%s"), pname);
pos += _tcslen(pos);
}
}
if (m_clientStats != NULL) {
pos += PrintClientStats(pos, &buf[STATS_BUFSZ-1]);
}
size_t len = _tcslen(buf);
// Allocate a global memory object for the text.
HGLOBAL hglbCopy = GlobalAlloc(GMEM_DDESHARE,
(len + 1) * sizeof(TCHAR));
if (hglbCopy == NULL) {
CloseClipboard();
return;
}
// Lock the handle and copy the text to the buffer.
LPTSTR lptstrCopy = (LPTSTR) GlobalLock(hglbCopy);
memcpy(lptstrCopy, buf, (len + 1) * sizeof(TCHAR));
lptstrCopy[len] = (TCHAR) 0; // null TCHARacter
GlobalUnlock(hglbCopy);
// Place the handle on the clipboard.
SetClipboardData(
#ifdef UNICODE
CF_UNICODETEXT,
#else
CF_TEXT,
#endif
hglbCopy);
CloseClipboard();
}
void CDynamoRIOView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
{
int minpos;
int maxpos;
if (pScrollBar == NULL)
return;
pScrollBar->GetScrollRange(&minpos, &maxpos);
maxpos = pScrollBar->GetScrollLimit();
int curpos = pScrollBar->GetScrollPos();
// Determine the new position
switch (nSBCode) {
case SB_TOP: // Scroll to far top.
curpos = minpos;
break;
case SB_BOTTOM: // Scroll to far bottom.
curpos = maxpos;
break;
case SB_ENDSCROLL: // End scroll.
break;
case SB_LINEUP: // Scroll up.
if (curpos > minpos)
curpos--;
break;
case SB_LINEDOWN: // Scroll down.
if (curpos < maxpos)
curpos++;
break;
case SB_PAGEUP: // Scroll one page up.
{
// Get the page size.
SCROLLINFO info;
pScrollBar->GetScrollInfo(&info, SIF_ALL);
if (curpos > minpos)
curpos = max(minpos, curpos - (int) info.nPage);
}
break;
case SB_PAGEDOWN: // Scroll one page down.
{
// Get the page size.
SCROLLINFO info;
pScrollBar->GetScrollInfo(&info, SIF_ALL);
if (curpos < maxpos)
curpos = min(maxpos, curpos + (int) info.nPage);
}
break;
case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
curpos = nPos; // of the scroll box at the end of the drag operation.
break;
case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
curpos = nPos; // position that the scroll box has been dragged to.
break;
}
// Set the new position of the thumb (scroll box).
pScrollBar->SetScrollPos(curpos);
CView::OnVScroll(nSBCode, nPos, pScrollBar);
// FIXME: we could try to more smoothly scroll w/ the last-copied m_stats
// instead of getting all new values
Refresh();
}