huahit

常用链接

统计

积分与排名

有用的编程连接

最新评论

进程间调用[合集] zz

1。WriteProcessMemory

如何更改其它程序 ListView 控件中某个 Item 的内容

作者:天津/赵春生

下载源代码

一:程序说明

  这次我将介绍如何更改其他程序ListView控件中某个Item的内容,关于类似的拙文我已经写了两篇,这是第三篇,本篇和第一篇《如何向其他程序的 ListView 控件发送 LVM_GETITEMTEXT 消息》类似,区别在于:

发送的消息不同:前者是读取pszText的内容——发送LVM_GETITEMTEXT;这次是设置pszText,应该发送LVM_SETITEMTEXT;
字符串缓冲区的作用不同:前者pItem用来接收ITEMTEXT,我们可以通过ReadProcessMemory函数来读取其内容;而在本篇中p_MyItemText则用来存放我们要设置的ITEMTEXT,并用WriteProcessMemory函数将其写入到目标程序中。
作为演示,下面的这段程序将更改TaskManager中第6个项目中第1列的内容。程序运行后的效果图:

二:具体实践
//////////////////////////////////////////////////////////////////////////////////
/*
 * Send LVM_SETITEMTEXT
 * 本程序适用于:Win2KP+SP4[Windows TaskManager(5.0.2195.6620)]
 *     WinXP+SP1[Windows TaskManager]
 * 代码在Win2000P+SP4 + VC6+SP6测试通过
*/
//////////////////////////////////////////////////////////////////////////////////

#include<windows.h>
#include<commctrl.h>

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
     
{
 HWND hwnd;
 int iItem=0;
 LVITEM lvitem, *plvitem;
 DWORD PID;
 HANDLE hProcess;
 char *p_MyItemText;//目标程序中用来存放TEXT的地址

 //The pszText member is the pointer to a null-terminated
 //  string containing the new text; it can also be NULL.
 //以上信息是从API手册中获得的,故在本例中字符串长度不能>=12,
 //以确保字符串后有NULL。
 char str_MyItemText[12]={0};
 strcpy(str_MyItemText,"天津 赵春生");
 
 
 hwnd=FindWindow("#32770","Windows 任务管理器");
 hwnd=FindWindowEx(hwnd,0,"#32770",0);
 hwnd=FindWindowEx(hwnd,0,"SysListView32",0);

 if (!hwnd)
  MessageBox(NULL,"[Windows 任务管理器] 尚未启动!","错误!",NULL);
 else
 {
  GetWindowThreadProcessId(hwnd, &PID);
   
  hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,PID);
  if (!hProcess)
   MessageBox(NULL,"获取进程句柄操作失败!","错误!",NULL);
  else
  {
   plvitem=(LVITEM*)VirtualAllocEx(hProcess, NULL, sizeof(LVITEM), MEM_COMMIT, PAGE_READWRITE);
   p_MyItemText=(char*)VirtualAllocEx(hProcess, NULL, 12, MEM_COMMIT, PAGE_READWRITE);

   if ((!plvitem)||(!p_MyItemText))
    MessageBox(NULL,"无法分配内存!","错误!",NULL);
   else
   {
    MessageBox(NULL,"本演示程序将更改TaskManager中第6个项目中第1列的内容。","提示",NULL);
    
    iItem=5;//5在这里是第六个(从零开始)
    lvitem.iSubItem=0;//同上
    lvitem.pszText=p_MyItemText;
    
    WriteProcessMemory(hProcess, p_MyItemText, &str_MyItemText, 12, NULL);
    WriteProcessMemory(hProcess, plvitem, &lvitem, sizeof(LVITEM), NULL);
    //向目标程序发送LVM_SETITEMTEXT消息
    SendMessage(hwnd, LVM_SETITEMTEXT, (WPARAM)iItem, (LPARAM)plvitem);
   }
  }
 }
 
 //释放内存
 CloseHandle(hwnd);
 CloseHandle(hProcess);
 VirtualFreeEx(hProcess, plvitem, 0, MEM_RELEASE);
 VirtualFreeEx(hProcess, p_MyItemText, 0, MEM_RELEASE);
 
 return 0;
}

住: VirtualAllocEx在WINCE下不支持,因为CE不支持在其他进程的进程空间中分配内存,所以上面这个例子在CE下是行不通的,不过可以采用其他比如共享内存等

2。WM_COPYDATA

转一篇文章,PPC下的:

Introduction
Most PocketPC programming is done in MFC using wizards. If instead you like to program in plain Win32/C++, you'll have found there to be not enough documentation or examples. This article and the accompanying source code provide a complete minimal example - a Notepad clone. It covers:

The DocList control (i.e. front page list of files, like in Pocket Word)
The "Card" architecture (that's what PocketPC calls an opened document window)
The Soft Input Panel (SIP, the keyboard/transcriber)
I haven't tried to wrap up the Windows functionality into my own API. That's because I think people want to learn the Windows API itself, not my own API. And also, the Windows API for PocketPC is rather clean and simple already. PocketPC is actually the only native implementation of Win32: it doesn't rest upon an internal layer like NT/Win2K/XP, and it doesn't rest upon 16bit like Win95/98/ME.

Program structure
PocketPC C++ programs are written using Microsoft eMbedded Visual Tools 3.0 (free). You should also download a free STL port for eVC++, by Giuseppe Govi. Although this article doesn't use it.

To write clean programs for PocketPC, you must (as Yoda says) unlearn what you have learnt:

Keep a small memory footprint.
There will never be more than one instance of your app running at a time; nor will there be more than one document open at a time.
Therefore, global variables are good. Per-app variables can be global. Per-document variables can be global.
Because the variables are declared statically, not created dynamically, it means that memory-allocation need not be done at runtime. This is a good thing.
Incidentally, the development environment often fails to build unless the "WCE Configuration" toolbar is visible. (This is the toolbar with drop-downs to select target OS, target device, target processor).

Preliminaries
These are the header files and app-specific global variables. Also, under Project > Settings > Linker > Input, you must link against doclist.lib, aygshell.lib and note_prj.lib.

#include <windows.h>
#include <aygshell.h>   // for SIP
#include <doclist.h>    // for DocList control
#include <projects.h>   // for Folder drop-down
#include <newmenu.h>    // for New menu
#include <commctrl.h>
#include <shellapi.h>
#ifndef IDM_NEWMENUMAX        // newmenu.h was already
#include <newmenu.h>    // included in PPC2000
#endif

HINSTANCE hInstance;
HWND hmain=0;       // Main window
HWND hmbar=0;       // Menu-bar
HWND hdlc=0;        // DocList control
HWND hcard=0;       // Document window
HWND hed=0;         // Edit window inside Doc-window
wchar_t dlcfolder[MAX_PATH]={0}; // current DLC folder
SHACTIVATEINFO shai={sizeof(SHACTIVATEINFO),0,0,0,0,0};
bool close_on_ok=false;

The window layout: there is a main window hmain. It owns a menu-bar hmbar which sits underneath. In the client area is a DocList control hdlc and a document-window hcard occupying the same area; only one will be visible at a time. Our app is a Notepad-clone, so we have an edit-window hed inside.

The variable dlcfolder holds the currently-selected folder by the DocList. That's because, when it comes to saving a new document, this will tell us which folder to put it in.

The SHACTIVATEINFO shai is used by the system to maintain the SIP's state: there are standard calls we must make in WM_ACTIVATE and WM_SETTINGCHANGED, involving this structure, for the SIP to behave properly.

And close_on_ok is for the following reason. If the user clicked on an associated document within File Explorer and so opened our application, then the OK button should close us and return to File Explorer. But if the user opened us from the Start menu, then closing the document should return the user to the main DocList control. This flag records which mode of closure was most recently indicated.

// At the start of WinMain:

hmain = FindWindow(L"LuMainClass",L"My App");
if (hmain!=0)
{
  SetForegroundWindow((HWND)((ULONG)hmain|1));
  // the |1 will activate any owned windows
  if (arg!=0 && wcslen(arg)!=0)
  {
    COPYDATASTRUCT cs;
    cs.dwData=0;
    cs.cbData=2+2*wcslen(arg);
    cs.lpData=arg;
    SendMessage(hmain,WM_COPYDATA,NULL,(LPARAM)&cs);
  }
  else SendMessage(hmain,WM_APP,0,0);
  return 0;
}
To save on memory, only one instance of our app should ever be loaded. When we're loaded, we check if an instance already exists. If we're loaded with a filename argument to open, we have to communicate this filename to the other instance: hence WM_COPYDATA. We use WM_APP to signal a general wakeup, whereupon some refreshes are performed.

// within WinMain, after registering classes:

SHInitExtraControls();

hmain = CreateWindow(L"LuMainClass", L"My App", WS_VISIBLE,
    CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
    NULL, NULL, hInstance, NULL);
ShowWindow(hmain,nCmdShow);
UpdateWindow(hmain);
if (arg!=0 && wcslen(arg)!=0)
{ COPYDATASTRUCT cs; cs.dwData=0;
  cs.cbData=2+2*wcslen(arg); cs.lpData=arg;
  SendMessage(hmain,WM_COPYDATA,NULL,(LPARAM)&cs);
}

Using SHInitExtraControls() allows us to use CAPEDIT (like a normal EDIT but it capitalizes the first letter) and also WC_SIPPREF (automatic management of the SIP).

Managing the windows
We start with creation and destruction of the application window. Note that most of our child windows need not be destroyed, since that happens automatically. The exception is the menu-bar, which must be destroyed explicitly.

case WM_CREATE:
{ SHMENUBARINFO mbi; ZeroMemory(&mbi,sizeof(mbi));
  mbi.cbSize=sizeof(mbi);
  mbi.hwndParent = hwnd;
  mbi.nToolBarId = 101;
  mbi.hInstRes = hInstance;
  SHCreateMenuBar(&mbi);
  hmbar = mbi.hwndMB;

  SHGetSpecialFolderPath(hwnd,dlcfolder,CSIDL_PERSONAL,TRUE);
  DOCLISTCREATE dlc; ZeroMemory(&dlc,sizeof(dlc));
  dlc.dwStructSize=sizeof(dlc);
  dlc.hwndParent=hwnd;
  dlc.pstrFilter = L"Text\0*.txt\0";
  dlc.wId=102;
  hdlc = DocList_Create(&dlc);

  hcard = CreateWindow(L"LuCardClass",L"",WS_CHILD,0,0,0,0,
           hwnd,(HMENU)103,hInstance,0);
  hed = CreateWindow(L"CAPEDIT",L"",WS_CHILD|WS_VISIBLE
           |WS_HSCROLL|WS_VSCROLL|ES_AUTOHSCROLL
           |ES_AUTOVSCROLL|ES_MULTILINE|WS_TABSTOP,0,0,0,0,
           hcard,(HMENU)104,hInstance,0);
  CreateWindow(WC_SIPPREF,L"",WS_CHILD,0,0,0,0,
           hcard,(HMENU)105,hInstance,0);

  MakeLayout(mlShowHide|mlRefreshDocList|mlResize,0);

  int nitems=DocList_GetItemCount(hdlc);
  if (nitems==0)
  { SendMessage(hwnd,WM_COMMAND,IDM_NEWMENUMAX+1,0);
  }
} return 0;

case WM_HIBERNATE:
{ if (!doc.open) SetWindowText(hed,L"");
} return 0;

case WM_DESTROY:
{ if (doc.open && doc.changed) doc.Save();
  CommandBar_Destroy(hmbar);
  PostQuitMessage(0);
} return 0;
The WM_DESTROY message is an odd case. It is never called during the normal operation of a program. That's because, if the user clicks the Close button, it doesn't close but merely minimizes the app (so that, subsequently, the app can be opened up instantly). If the system is running low on memory, it sends a WM_HIBERNATE message asking applications to clean themselves up. If it's running really low, it sends a WM_CLOSE which (in DefWindowProc) calls DestroyWindow, so finally triggering our WM_DESTROY.

The resources for the menubar are as follows. I'm writing it here because it doesn't seem to be documented elsewhere.

// A resource file for a menubar
#include <windows.h>
#include <commctrl.h>
#include <aygshell.h>

101 RCDATA MOVEABLE
BEGIN
  101, 3,
  I_IMAGENONE, IDM_SHAREDNEW, TBSTATE_ENABLED,
       TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_SHNEW, 0, 0,
  I_IMAGENONE, 202, TBSTATE_ENABLED,
       TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, 202, 0, 1,
  I_IMAGENONE, 203, TBSTATE_ENABLED,
       TBSTYLE_AUTOSIZE, 203, 0, NOMENU,
END
This RCDATA is for a menubar. The first row (101,3) says to look up MENU#101, and that there will be three items in this menubar. In each row, the second item (IDM_SHAREDNEW, 203, 203) is the WM_COMMAND id that will be sent upon clickage. Here, "Shared New" means that it's a menu that the system will fill up with all the standard items (Task, Note, ...) and then will offer to us to fill up the rest.

STRINGTABLE DISCARDABLE
BEGIN
   201 "DummyNew"
   202 "Tools"
   203 "Edit"
END
Within each row of RCDATA, the third-last item (IDS_SHNEW, 202, 203) is an index into the STRINGTABLE for the screen-names of each item. IDS_SHNEW is a system-defined string.

101 MENU DISCARDABLE
BEGIN
  POPUP ""
  BEGIN
      MENUITEM "DummNewEntry" -1
  END
  POPUP ""
  BEGIN
      MENUITEM "Exit" 301
  END
END
The final item in each row of RCDATA says which menu popup to choose from the MENU#101 resource. The first item (0) is actually ignored, since it is overridden by IDM_SHAREDNEW. The second item (1) points to popup index 1. And the third item (NOMENU) says that there is no popup here.

The interaction between IDM_SHAREDNEW and the popup number is a bit idiosyncratic:

IDM_SHAREDNEW, popup#0 -- uses the global menu, but without an arrow
IDM_SHAREDNEW, NOMENU -- global menu, with uparrow, and 'new' acts like a button not a menu.
cmd#201, popup#0 -- just our own menu, exactly like the others
cmd#201, NOMENU -- has the uparrow, and a button, but neither does anything
MakeLayout
The function MakeLayout is as follows. It's job is to rearrange and refresh windows as necessary according to the current state.

// MakeLayout - this shows/hides the edit/doclist as approp-
// riate, resizes according to SIP, refreshes the DocList.
//
const DWORD mlShowHide=0, mlRefreshDocList=1, mlResize=2;

void MakeLayout(DWORD flags, LPARAM wmsc_lparam=0)
{ ShowWindow(hcard, doc.open?SW_SHOW:SW_HIDE);
  ShowWindow(hdlc, doc.open?SW_HIDE:SW_SHOW);
  // the 'edit' button
  TBBUTTONINFO tbbi; ZeroMemory(&tbbi,sizeof(tbbi));
  tbbi.cbSize=sizeof(tbbi); tbbi.dwMask=TBIF_STATE;
  tbbi.fsState= (doc.open&&doc.readonly)?TBSTATE_ENABLED
                                        :TBSTATE_HIDDEN;
  SendMessage(hmbar,TB_SETBUTTONINFO,203,(LPARAM)&tbbi);
  // readonly in the card itself
  SendMessage(hed,EM_SETREADONLY,doc.readonly?TRUE:FALSE,0);
  if (doc.open && !doc.readonly) SetFocus(hed);
Like most PocketPC apps, by default we open documents in 'readonly' mode (indicated by our edit control having ES_READONLY). The user can click the "Edit" button in the menubar to switch to editing mode.

  // OK/X. If OK is hidden, then X will be visible
  SHDoneButton(hmain, doc.open?SHDB_SHOW:SHDB_HIDE);
Windows can have either an X, or an OK, or nothing at all in their top right. Notionally the OK is "stronger" than the X: thus, if both are enabled, only the OK will be visible. It's normally to show OK when you're editing a document, and X when you're in DocList mode.

  // Now resize the windows to accommodate SIP. If this
  // is called from WM_SETTINGSCHANGE, then use the same
  // lParam for efficency; otherwise set wmsc_lparam=0
  if (flags&mlResize)
  { SIPINFO si;
    SHSipInfo(SPI_GETSIPINFO,wmsc_lparam,&si,0);
    int x=si.rcVisibleDesktop.left;
    int y=si.rcVisibleDesktop.top;
    int w=si.rcVisibleDesktop.right-x;
    int h=si.rcVisibleDesktop.bottom-y;
    RECT rc; GetWindowRect(hmbar,&rc);
    int mh=rc.bottom-rc.top;
    if ((si.fdwFlags&SIPF_ON)) h+=1; else h-=mh-1;
    MoveWindow(hmain,x,y,w,h,FALSE);
    GetClientRect(hmain,&rc);
    MoveWindow(hcard,0,0,rc.right,rc.bottom,FALSE);
    MoveWindow(hdlc,0,0,rc.right,rc.bottom,TRUE);
    MoveWindow(hed,0,0,rc.right,rc.bottom,FALSE);
  }
  // We need to refresh after files have been changed,
  // to reshow their time/size
  if (!doc.open && (flags&mlRefreshDocList))
  { DocList_Refresh(hdlc);
  }
}
It is costly to get the SIP info and also to refresh the DocList. That's why we control both by flags, to be done only if necessary.

Activation and commands
The responses to WM_ACTIVATE and WM_SETTINGCHANGE are standard, required by the system to handle the SIP. Also, we call MakeLayout, to accommodate the new size of the SIP:

case WM_ACTIVATE:
{ SHHandleWMActivate(hwnd, wParam, lParam, &shai, FALSE);
} return 0;

case WM_SETTINGCHANGE:
{ SHHandleWMSettingChange(hwnd, wParam, lParam, &shai);
  MakeLayout(mlResize,lParam);
} return 0;
Also, when our app was running in the background but another instance was launched, then we have to wake up the old instance: either WM_APP if we just need to activate it, or WM_COPYDATA if we need to get it to open a document.

case WM_APP:
{ // we are a background instance being reactivated
  close_on_ok=false;
  // maybe have to refresh the doclist.
  if (!doc.open) MakeLayout(mlRefreshDocList);
} return 0;

case WM_COPYDATA:
{ // we are a background instance being told to open a doc.
  if (doc.open && doc.changed) doc.Save();
  close_on_ok=true;
  COPYDATASTRUCT *cs = (COPYDATASTRUCT*)lParam;
  doc.Open((wchar_t*)cs->lpData);
  MakeLayout(mlShowHide);
} return 0;
And this is the code to respond to clicks on the menubar and on the OK button:

case WM_COMMAND:
{ int id=LOWORD(wParam);
  if (id==IDOK && doc.open) // OK button
  { if (doc.open && doc.changed) doc.Save();
    SHSipPreference(hmain,SIP_FORCEDOWN);
    doc.open=false; MakeLayout(mlShowHide|mlRefreshDocList);
    if (close_on_ok) ShowWindow(hwnd,SW_MINIMIZE);
  }
The OK button means that the user has finished editing the current document. That's why we save it. We also force down the SIP. That's because we know the next thing that the user sees will be the DocList control, and we know that this doesn't need the SIP: without the force-down, the SIP would wait two seconds before hiding.

Note also the close_on_ok. If the user had opened up most recently from an outside source (e.g. within File Explorer, clicked on an app associated with our app), then a subsequent click on OK should return straight to File Explorer.

In the following, we respond to a menu item Tools > Exit, by terminating. I include this for debugging purposes. The menu item should be removed in the release version of the code.

  // ... continued from WM_COMMAND

  else if (id==301) // tools.exit
  { DestroyWindow(hwnd);
  }
  else if (id==203) // tools.edit
  { if (doc.open && doc.readonly)
    { doc.readonly=false; MakeLayout(mlShowHide);
    }
  }
  else if (id==IDM_NEWMENUMAX+1) // new.text
  { if (doc.open && doc.changed) doc.Save();
    doc.New(); MakeLayout(mlShowHide);
  }
} return 0;


case WM_NOTIFY:
{ NMHDR *nmhdr = (NMHDR*)lParam;
  if (nmhdr->code==NMN_GETAPPREGKEY)
  { // Called by the system for us to fill in our New menu.
    NMNEWMENU *nmnew = (NMNEWMENU*)lParam;
    AppendMenu(nmnew->hMenu, MF_ENABLED,
               IDM_NEWMENUMAX+1, L"Text");
    AppendMenu(nmnew->hMenu, MF_SEPARATOR, 0, 0);
  }
  else if (nmhdr->code==DLN_ITEMACTIVATED)
  { DLNHDR *dln = (DLNHDR*)lParam;
    doc.Open(dln->pszPath);
    MakeLayout(mlShowHide);
  }
  else if (nmhdr->code==DLN_FOLDER)
  { DLNHDR *dln = (DLNHDR*)lParam;
    GetPathForFolder(dln->pszPath,dlcfolder);
  }
} return 0;
The response to DLN_FOLDER means that the user has clicked on a folder from the DocList's drop-down folder list. We call our own function (below) to retrieve this folder's full path. That's so that, when we subsequently save a new document, we can save it into the place the user was looking at. We need a separate function because dln->pszPath merely gives the folder name, not its full path.

The are bugs in the DocList control. It's meant to display folders from My Documents and every storage card, and it does, but it only allows the user to select folders from the first storage card or My Documents - secondary storage cards don't work. This is problematic on some devices which have a permanent built-in primary storage card, and external SDs count as secondary! Another problem is that, if a folder "Name" exists in two different locations, it collapses them into just one entry -- unless they happen to have different capitalizations, in which case it collapses them for some purposes (selection) but not for other purposes (display).

// GetPathForFolder(const wchar_t *folder, wchar_t *path):
// Given a folder name we return its path
//   All Folders  -->  \My Documents
//   Business     -->  \My Documents\Business
//   MySdFolder   -->  \Storage Card\MySdFolder
// Assume path>=MAX_PATH.

struct EnumProjectsInfo
{ const wchar_t *folder; wchar_t *path;
};

BOOL CALLBACK EnumCallback(PAstruct *pa, LPARAM lp)
{ wchar_t *fn;
  if (pa->m_IDtype!=FILE_ID_TYPE_OID) fn=pa->m_szPathname;
  else
  { CEOIDINFO cinf;
    CeOidGetInfo(pa->m_fileOID,&cinf);
    fn = cinf.infDirectory.szDirName;
  }
  const wchar_t *c=fn, *lastslash=c;
  while (*c!=0) {if (*c=='\\') lastslash=c+1; c++;}
  EnumProjectsInfo *epi = (EnumProjectsInfo*)lp;
  if (wcsicmp(epi->folder,lastslash)!=0) return TRUE;
  // TRUE means keep enumerating
  wcscpy(epi->path,fn); return FALSE;
}

void GetPathForFolder(const wchar_t *folder, wchar_t *path)
{ // assume 'path' is at least MAX_PATH.
  *path=0;
  EnumProjectsInfo epi; epi.folder=folder; epi.path=path;
  EnumProjectsEx(EnumCallback,0,PRJ_ENUM_ALL_DEVICES,
                      (LPARAM)&epi);
  if (*path==0) SHGetSpecialFolderPath(NULL,epi.path,
                      CSIDL_PERSONAL,FALSE);
  }
}
The document
Our application is a version of Notepad. So, our document window is basically an EDIT control. (Actually, a CAPEDIT, which is unique to PocketPC and which capitalizes the first character). Here's how we represent our document:

struct TDocument
{ wchar_t fn[MAX_PATH];
  bool open, changed, readonly;
  bool unicode;

  TDocument() : open(false) {*fn=0;}
  void New();
  bool Open(const wchar_t *ofn);
  void Save();
} doc;
Note at the bottom: we declare a global static variable doc, for our document. Global variables make PocketPC applications much easier and cleaner. Most of the fields are obvious:

open indicates whether there is an open document (and hence whether the rest of the flags are meaningful)
readonly is because existing documents are normally opened in read-only mode; the user has to click Edit to start altering them
changed says whether any changes have been made (and hence have to be saved)
fn is the name of the file, if we opened an existing one, empty otherwise.
unicode says whether this file should be saved to disk in Unicode format. Unicode is native for PocketPC. But if we had opened an ASCII document, it would be rude to convert it.
LRESULT CALLBACK CardWndProc(HWND hwnd, UINT msg,
                             WPARAM wParam, LPARAM lParam)
{ if (msg==WM_COMMAND)
  { int id=LOWORD(wParam), code=HIWORD(wParam);
    if (id==104 && code==EN_CHANGE) doc.changed=true;
  }
  return DefWindowProc(hwnd,msg,wParam,lParam);
}
Most of the fields are set by the New/Open/Save methods, but changed is also set when there have been any changes to the edit control, above.

void TDocument::New()
{ *fn=0; SetWindowText(hed,L"");
  open=true; changed=false; readonly=false; unicode=true;
}

bool TDocument::Open(const wchar_t *ofn)
{ HANDLE hf=CreateFile(ofn,GENERIC_READ,0,
             NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
  if (hf==INVALID_HANDLE_VALUE) return false;
  DWORD size=GetFileSize(hf,0); char *buf=new char[size+2];
  DWORD red; ReadFile(hf,buf,size,&red,NULL);
  buf[size]=0; buf[size+1]=0;
  CloseHandle(hf);
The interesting functionality in Open is to detect whether the file is Unicode or not. If it starts with the Unicode Byte-Order-Mark (BOM), we know it's Unicode. Otherwise, we'll guess, based on whether the first 256 bytes look ASCII-ish or not. That's because many systems save Unicode files without the BOM.

  // Unicode detector:
  wchar_t *unc=0;
  if (buf[0]==-1 && buf[1]==-2) unc=(wchar_t*)(buf+2);
  else
  { for (unsigned int i=3; i<256 && i<size && unc==0; i+=2)
    { if (buf[i]==0) unc=(wchar_t*)buf;
    }
  }
  unicode = (unc!=0);   
  //
  if (unicode) SetWindowText(hed,unc);
  else
  { unc = new wchar_t[size];
    MultiByteToWideChar(CP_ACP,0,buf,-1,unc,size);
    SetWindowText(hed,unc);
    delete[] unc;
  }
  wcscpy(fn,ofn);
  open=true; changed=false; readonly=true;
  return true;
}
As for Save(), its complication is that it might have to auto-generate a filename for a newly created document. That's the way of things in the PocketPC: trouble the user as little as possible with pesky things like FileOpen or FileSave. We make use of a trivial function isalphanum(), which is defined below:

void TDocument::Save()
{ if (!open || !changed) return;
  int len=GetWindowTextLength(hed); if (len==0) return;
  wchar_t *unc=new wchar_t[len+1];
  GetWindowText(hed,unc,len+1);

  // To construct the filename from the doc's content...
  if (*fn==0)
  { wcscpy(fn,dlcfolder); wchar_t *d=fn+wcslen(fn);
    *d='\\'; d++; bool any=false;
    int i=0; wchar_t *c=unc;
    for (; !isalphanum(*c) && *c!=0 && i<32; i++) {}
    for (; isalphanum(c[i]) && c[i]!=0 && i<32; i++)
    { *d=c[i]; d++; any=true;
    }
    if (!any) {wcscpy(d,L"unnamed"); d+=wcslen(d);}
    wcscpy(d,L".txt");
    for (i=1; GetFileAttributes(fn)!=0xFFFFFFFF; i++)
    { wsprintf(d,L" (%i).txt",i);
    }
  }
There's a bug in PocketPC's handling of SD cards. Every now and again, like one time in 400, it simply fails to save a file onto the card -- even though all the functions report success. I don't know what to do about it. One idea is to use FILE_FLAG_WRITE_THROUGH to avoid lazy write caching. Another is to re-open the file after saving it (maybe even 30 seconds after saving it) to check whether it worked.

  HANDLE hf = CreateFile(fn,GENERIC_WRITE,0,NULL,
                  CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  if (unicode)
  { const char byteorder[2]={-1,-2};
    DWORD writ; WriteFile(hf,byteorder,2,&writ,NULL);
    WriteFile(hf,unc,sizeof(wchar_t)*len,&writ,NULL);
  }
  else
  { int size = WideCharToMultiByte(CP_ACP,WC_DEFAULTCHAR,
                                   unc,-1,0,0,NULL,NULL);
    char *buf = new char[size];
    WideCharToMultiByte(CP_ACP,WC_DEFAULTCHAR,
                        unc,-1,buf,size,NULL,NULL);
    DWORD writ; WriteFile(hf,buf,size,&writ,NULL);
    delete[] buf;
  }
  CloseHandle(hf);
  delete[] unc;
  changed=false;
}

bool isalphanum(const wchar_t c)
{ if (c>='a' && c<='z') return true;
  if (c>='A' && c<='Z') return true;
  if (c>='0' && c<='9') return true;
  if (c==' ' || c=='-') return true;
  return false;
}
The function isalphanum() assists in auto-constructing save filenames. Our idea: considering up to the first 32 characters, use the first span of alphanumeric characters as the filename.

Postscript
If anyone posts a complaint that the code doesn't compile, with a stdafx.h error, they had better post anonymously -- otherwise I will track them down and bite their heads off. To avoid decapitation, read your compiler documentation on Project Settings > Compiler > Precompiled Headers.

History
10 June 2003 - Now, also compiles under PPC2000 (as per Mike Dimmick's suggestion) and under PPC2003 (with eVC4).
About ljw1004

 Lucian is a researcher at Microsoft in Redmond. As a break from computer science theory, he likes to write practical programs every now and then. Especially screen savers!
Click here to view ljw1004's online profile.
 



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=546354

posted on 2006-07-19 13:14 无为斋 阅读(540) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理