无言无语

全是废话

 

 

“我们对于完美客服体验的理解是,用户其实并不希望与我们直接对话。每次客户联系我们,我们都视为工作中的失误。我已经说了好多年了,人们应该与他们的朋友交谈,而不是与商家。因此,我们充分利用各种客服信息来探究客户联系我们的真正原因。什么地方出现问题了?那个人为什么要打电话?为什么他们花费时间与我们交谈而不是与家人交谈?我们如何解决这个问题?”

—— Jeff Bezos

 

盜賊上毒宏

 

/use [nomod] 致伤药膏 V; [mod:ctrl] 致命药膏 VII; [mod:shift] 麻痹药膏; [mod:alt] 减速药膏

左鍵主手 右鍵副手

/use [button:1] 16; [button:2] 17

取消確認

/click StaticPopup1Button1

 

 

没想到过了这么久,帐号居然还在哦

 

箴言

 

1. Keep simple things simple and complex things possible
让简单的事情维持简单, 并让复杂的事情变得可办到

2. Give defaults while you give choices
提供选择时一并提供预设值.

3. Give choices while the one obvious way depends
当莫衷一是时就提供选择.

 

反调试技术(以OD为例附核心原代码)

 

反调试技术(以OD为例附核心原代码)

【标 题】:反调试技术(以OD为例附核心原代码)
【作 者】:★天&緣★
【时 间】:2007-11-16 18:14
【链 接】:http://www.wgum.net/viewthread.php?tid=980

知其然,知其所以然,希望大家觉得有用,大家可以用在自己程序中查看自己的程序是否被调试..同时为了更好的了解一些游戏无法用OD调试的原因

Read the rest of this entry »

 

从内存中加载并启动一个exe

 

从内存中加载并启动一个exe
作者:webfly 日期:2008-01-11
字体大小: 小 中 大

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://bedlamiten.blogbus.com/logs/5680991.html

windows似乎只提供了一种启动进程的方法:即必须从一个可执行文件中加载并启动。
而下面这段代码就是提供一种可以直接从内存中启动一个exe的变通办法。
用途嘛, 也许可以用来保护你的exe,你可以对要保护的 exe 进行任意切分、加密、存储,只要运行时能将exe的内容正确拼接到一块内存中,就可以直接从内存中启动,而不必不安全地去生成一个临时文件再从临时文件启动进程。另外这段代码也提供了一种自己写exe外壳的简单途径,如果能配合其它各种外壳技术就更好地保护你的exe文件。

原理很简单:就是“借尸还魂”,启动一个僵尸进程(NT下可以是自身程序启动的另一个进程),然后在它运行前将其整个替换成内存中的exe内容,待正式运行后执行的就是你的目标代码了。

不过代码中还有一些不尽人意的地方,比如在98下运行会留一个僵尸程序的壳在硬盘上(其实那个僵尸程序本身就是一个完整的可执行程序,直接运行的话只显示一条错误信息然后就退出了)。另外由于客观条件限制,代码没有经过充分测试,只在XP下进行了一些初步测试:普通exe都能正常运行,upx压缩过的exe 绝大多数情况下都能运行,只有在不能卸载僵尸外壳时才有问题(upx压缩过的exe没有重定向表,无法加载到其它地址运行)。

如果有bug望告之,如果有更好的方法特别是能解决98下的遗留尾巴的话希望不吝赐教。

{ ******************************************************* }
{ *                 从内存中加载并运行exe               * }
{ ******************************************************* }
{ * 参数:                                                }
{ * Buffer: 内存中的exe地址                               }
{ * Len: 内存中exe占用长度                                }
{ * CmdParam: 命令行参数(不包含exe文件名的剩余命令行参数)}
{ * ProcessId: 返回的进程Id                               }
{ * 返回值: 如果成功则返回进程的Handle(ProcessHandle),   }
{            如果失败则返回INVALID_HANDLE_VALUE           }
{ ******************************************************* }

unit PEUnit;

interface

uses windows;

function MemExecute(const ABuffer; Len: Integer; CmdParam: string; var ProcessId: Cardinal): Cardinal;

implementation

{$R ExeShell.res}   // 外壳程序模板(98下使用)

type
 TImageSectionHeaders = array [0..0] of TImageSectionHeader;
 PImageSectionHeaders = ^TImageSectionHeaders;

{ 计算对齐后的大小 }
function GetAlignedSize(Origin, Alignment: Cardinal): Cardinal;
begin
 result := (Origin + Alignment – 1) div Alignment * Alignment;
end;

{ 计算加载pe并对齐需要占用多少内存,未直接使用OptionalHeader.SizeOfImage作为结果是因为据说有的编译器生成的exe这个值会填0 }
function CalcTotalImageSize(MzH: PImageDosHeader; FileLen: Cardinal; peH: PImageNtHeaders;
   peSecH: PImageSectionHeaders): Cardinal;
var
 i: Integer;
begin
 {计算pe头的大小}
 result := GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders, PeH.OptionalHeader.SectionAlignment);

 {计算所有节的大小}
 for i := 0 to peH.FileHeader.NumberOfSections – 1 do
   if peSecH[i].PointerToRawData + peSecH[i].SizeOfRawData > FileLen then  // 超出文件范围
   begin
     result := 0;
     exit;
   end
   else if peSecH[i].VirtualAddress <> 0 then  //计算对齐后某节的大小
     if peSecH[i].Misc.VirtualSize <> 0 then
       result := GetAlignedSize(peSecH[i].VirtualAddress + peSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment)
     else
       result := GetAlignedSize(peSecH[i].VirtualAddress + peSecH[i].SizeOfRawData, PeH.OptionalHeader.SectionAlignment)
   else if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then
     result := result + GetAlignedSize(peSecH[i].SizeOfRawData, peH.OptionalHeader.SectionAlignment)
   else
     result := result + GetAlignedSize(peSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment);

end;

{ 加载pe到内存并对齐所有节 }
function AlignPEToMem(const Buf; Len: Integer; var PeH: PImageNtHeaders;
   var PeSecH: PImageSectionHeaders; var Mem: Pointer; var ImageSize: Cardinal): Boolean;
var
 SrcMz: PImageDosHeader;            // DOS头
 SrcPeH: PImageNtHeaders;           // PE头
 SrcPeSecH: PImageSectionHeaders;   // 节表
 i: Integer;
 l: Cardinal;
 Pt: Pointer;
begin
 result := false;
 SrcMz := @Buf;
 if Len < sizeof(TImageDosHeader) then exit;
 if SrcMz.e_magic <> IMAGE_DOS_SIGNATURE then exit;
 if Len < SrcMz._lfanew+Sizeof(TImageNtHeaders) then exit;
 SrcPeH := pointer(Integer(SrcMz)+SrcMz._lfanew);
 if (SrcPeH.Signature <> IMAGE_NT_SIGNATURE) then exit;
 if (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_DLL <> 0) or
     (SrcPeH.FileHeader.Characteristics and IMAGE_FILE_EXECUTABLE_IMAGE = 0)
     or (SrcPeH.FileHeader.SizeOfOptionalHeader <> SizeOf(TImageOptionalHeader)) then exit;
 SrcPeSecH := Pointer(Integer(SrcPeH)+SizeOf(TImageNtHeaders));
 ImageSize := CalcTotalImageSize(SrcMz, Len, SrcPeH, SrcPeSecH);
 if ImageSize = 0 then
   exit;
 Mem := VirtualAlloc(nil, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);  // 分配内存
 if Mem <> nil then
 begin
   // 计算需要复制的PE头字节数
   l := SrcPeH.OptionalHeader.SizeOfHeaders;
   for i := 0 to SrcPeH.FileHeader.NumberOfSections – 1 do
     if (SrcPeSecH[i].PointerToRawData <> 0) and (SrcPeSecH[i].PointerToRawData < l) then
       l := SrcPeSecH[i].PointerToRawData;
   Move(SrcMz^, Mem^, l);
   PeH := Pointer(Integer(Mem) + PImageDosHeader(Mem)._lfanew);
   PeSecH := Pointer(Integer(PeH) + sizeof(TImageNtHeaders));

   Pt := Pointer(Cardinal(Mem) + GetAlignedSize(PeH.OptionalHeader.SizeOfHeaders, PeH.OptionalHeader.SectionAlignment));
   for i := 0 to PeH.FileHeader.NumberOfSections – 1 do
   begin
     // 定位该节在内存中的位置
     if PeSecH[i].VirtualAddress <> 0 then
       Pt := Pointer(Cardinal(Mem) + PeSecH[i].VirtualAddress);

     if PeSecH[i].SizeOfRawData <> 0 then
     begin
       // 复制数据到内存
       Move(Pointer(Cardinal(SrcMz) + PeSecH[i].PointerToRawData)^, pt^, PeSecH[i].SizeOfRawData);
       if peSecH[i].Misc.VirtualSize < peSecH[i].SizeOfRawData then
         pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].SizeOfRawData, PeH.OptionalHeader.SectionAlignment))
       else
         pt := pointer(Cardinal(pt) + GetAlignedSize(peSecH[i].Misc.VirtualSize, peH.OptionalHeader.SectionAlignment));
       // pt 定位到下一节开始位置
     end
     else
       pt := pointer(Cardinal(pt) + GetAlignedSize(PeSecH[i].Misc.VirtualSize, PeH.OptionalHeader.SectionAlignment));
   end;
   result := True;
 end;
end;

type
 TVirtualAllocEx = function (hProcess: THandle; lpAddress: Pointer;
                                 dwSize, flAllocationType: DWORD; flProtect: DWORD): Pointer; stdcall;

var
 MyVirtualAllocEx: TVirtualAllocEx = nil;

function IsNT: Boolean;
begin
 result := Assigned(MyVirtualAllocEx);
end;

{ 生成外壳程序命令行 }
function PrepareShellExe(CmdParam: string; BaseAddr, ImageSize: Cardinal): string;
var
 r, h, sz: Cardinal;
 p: Pointer;
 fid, l: Integer;
 buf: Pointer;
 peH: PImageNtHeaders;
 peSecH: PImageSectionHeaders;
begin
 if IsNT then
 { NT 系统下直接使用自身程序作为外壳进程 }
   result := ParamStr(0)+CmdParam
 else begin
 // 由于98系统下无法重新分配外壳进程占用内存,所以必须保证运行的外壳程序能容纳目标进程并且加载地址一致
 // 此处使用的方法是从资源中释放出一个事先建立好的外壳程序,然后通过修改其PE头使其运行时能加载到指定地址并至少能容纳目标进程
   r := FindResource(HInstance, ‘SHELL_EXE’, RT_RCDATA);
   h := LoadResource(HInstance, r);
   p := LockResource(h);
   l := SizeOfResource(HInstance, r);
   GetMem(Buf, l);
   Move(p^, Buf^, l);   // 读到内存
   FreeResource(h);
   peH := Pointer(Integer(Buf) + PImageDosHeader(Buf)._lfanew);
   peSecH := Pointer(Integer(peH) + sizeof(TImageNtHeaders));
   peH.OptionalHeader.ImageBase := BaseAddr;    // 修改PE头重的加载基址
   if peH.OptionalHeader.SizeOfImage < ImageSize then  // 目标比外壳大,修改外壳程序运行时占用的内存
   begin
     sz := Imagesize – peH.OptionalHeader.SizeOfImage;
     Inc(peH.OptionalHeader.SizeOfImage, sz);    // 调整总占用内存数
     Inc(peSecH[peH.FileHeader.NumberOfSections-1].Misc.VirtualSize, sz);   // 调整最后一节占用内存数
   end;

   // 生成外壳程序文件名, 为本程序改后缀名得到的
   // 由于不想 uses SysUtils (一旦 use 了程序将增大80K左右), 而且偷懒,所以只支持最多运行11个进程,后缀名为.dat, .da0~.da9
   result := ParamStr(0);
   result := copy(result, 1, length(result) – 4) + ‘.dat’;
   r := 0;
   while r < 10 do
   begin
     fid := CreateFile(pchar(result), GENERIC_READ or GENERIC_WRITE, 0, nil, Create_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
     if fid < 0 then
     begin
       result := copy(result, 1, length(result)-3)+’da’+Char(r+Byte(‘0′));
       inc(r);
     end
     else begin
       //SetFilePointer(fid, Imagesize, nil, 0);
       //SetEndOfFile(fid);
       //SetFilePointer(fid, 0, nil, 0);
       WriteFile(fid, Buf^, l, h, nil);  // 写入文件
       CloseHandle(fid);
       break;
     end;
   end;
   result := result + CmdParam;  // 生成命令行
   FreeMem(Buf);
 end;
end;

{ 是否包含可重定向列表 }
function HasRelocationTable(peH: PImageNtHeaders): Boolean;
begin
 result := (peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress <> 0)
     and (peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size <> 0);
end;

type
 PImageBaseRelocation= ^TImageBaseRelocation;
 TImageBaseRelocation = packed record
   VirtualAddress: cardinal;
   SizeOfBlock: cardinal;
 end;

{ 重定向PE用到的地址 }
procedure DoRelocation(peH: PImageNtHeaders; OldBase, NewBase: Pointer);
var
 Delta: Cardinal;
 p: PImageBaseRelocation;
 pw: PWord;
 i: Integer;
begin
 Delta := Cardinal(NewBase) – peH.OptionalHeader.ImageBase;
 p := pointer(cardinal(OldBase) + peH.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
 while (p.VirtualAddress + p.SizeOfBlock <> 0) do
 begin
   pw := pointer(Integer(p) + Sizeof(p^));
   for i := 1 to (p.SizeOfBlock – Sizeof(p^)) div 2 do
   begin
     if pw^ and $F000 = $3000 then
       Inc(PCardinal(Cardinal(OldBase) + p.VirtualAddress + (pw^ and $0FFF))^, Delta);
     inc(pw);
   end;
   p := Pointer(pw);
 end;
end;

type
 TZwUnmapViewOfSection = function (Handle, BaseAdr: Cardinal): Cardinal; stdcall;

{ 卸载原外壳占用内存 }
function UnloadShell(ProcHnd, BaseAddr: Cardinal): Boolean;
var
 M: HModule;
 ZwUnmapViewOfSection: TZwUnmapViewOfSection;
begin
 result := False;
 m := LoadLibrary(‘ntdll.dll’);
 if m <> 0 then
 begin
   ZwUnmapViewOfSection := GetProcAddress(m, ‘ZwUnmapViewOfSection’);
   if assigned(ZwUnmapViewOfSection) then
     result := (ZwUnmapViewOfSection(ProcHnd, BaseAddr) = 0);
   FreeLibrary(m);
 end;
end;

{ 创建外壳进程并获取其基址、大小和当前运行状态 }
function CreateChild(Cmd: string; var Ctx: TContext; var ProcHnd, ThrdHnd, ProcId, BaseAddr, ImageSize: Cardinal): Boolean;
var
 si: TStartUpInfo;
 pi: TProcessInformation;
 Old: Cardinal;
 MemInfo: TMemoryBasicInformation;
 p: Pointer;
begin
 FillChar(si, Sizeof(si), 0);
 FillChar(pi, SizeOf(pi), 0);
 si.cb := sizeof(si);
 result := CreateProcess(nil, PChar(Cmd), nil, nil, False, Create_SUSPENDED, nil, nil, si, pi);  // 以挂起方式运行进程
 if result then
 begin
   ProcHnd := pi.hProcess;
   ThrdHnd := pi.hThread;
   ProcId := pi.dwProcessId;

   { 获取外壳进程运行状态,[ctx.Ebx+8]内存处存的是外壳进程的加载基址,ctx.Eax存放有外壳进程的入口地址 }
   ctx.ContextFlags := CONTEXT_FULL;
   GetThreadContext(ThrdHnd, ctx);
   ReadProcessMemory(ProcHnd, Pointer(ctx.Ebx+8), @BaseAddr, SizeOf(Cardinal), Old);  // 读取加载基址
   p := Pointer(BaseAddr);

   { 计算外壳进程占有的内存 }
   while VirtualQueryEx(ProcHnd, p, MemInfo, Sizeof(MemInfo)) <> 0 do
   begin
     if MemInfo.State = MEM_FREE then
       break;
     p := Pointer(Cardinal(p) + MemInfo.RegionSize);
   end;
   ImageSize := Cardinal(p) – Cardinal(BaseAddr);
 end;
end;

{ 创建外壳进程并用目标进程替换它然后执行 }
function AttachPE(CmdParam: string; peH: PImageNtHeaders; peSecH: PImageSectionHeaders;
   Ptr: Pointer; ImageSize: Cardinal; var ProcId: Cardinal): Cardinal;
var
 s: string;
 Addr, Size: Cardinal;
 ctx: TContext;
 Old: Cardinal;
 p: Pointer;
 Thrd: Cardinal;
begin
 result := INVALID_HANDLE_VALUE;
 s := PrepareShellExe(CmdParam, peH.OptionalHeader.ImageBase, ImageSize);
 if CreateChild(s, ctx, result, Thrd, ProcId, Addr, Size) then
 begin
   p := nil;
   if (peH.OptionalHeader.ImageBase = Addr) and (Size >= ImageSize) then  // 外壳进程可以容纳目标进程并且加载地址一致
   begin
     p := Pointer(Addr);
     VirtualProtectEx(result, p, Size, PAGE_EXECUTE_READWRITE, Old);
   end
   else if IsNT then  // 98 下失败
   begin
     if UnloadShell(result, Addr) then  // 卸载外壳进程占有内存
       // 重新按目标进程加载基址和大小分配内存
       p := MyVirtualAllocEx(Result, Pointer(peH.OptionalHeader.ImageBase), ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
     if (p = nil) and hasRelocationTable(peH) then  // 分配内存失败并且目标进程支持重定向
     begin
       // 按任意基址分配内存
       p := MyVirtualAllocEx(result, nil, ImageSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
       if p <> nil then
         DoRelocation(peH, Ptr, p);  // 重定向
     end;
   end;
   if p <> nil then
   begin
     WriteProcessMemory(Result, Pointer(ctx.Ebx+8), @p, Sizeof(DWORD), Old);  // 重置目标进程运行环境中的基址
     peH.OptionalHeader.ImageBase := Cardinal(p);
     if WriteProcessMemory(Result, p, Ptr, ImageSize, Old) then  // 复制PE数据到目标进程
     begin
       ctx.ContextFlags := CONTEXT_FULL;
       if Cardinal(p) = Addr then
         ctx.Eax := peH.OptionalHeader.ImageBase + peH.OptionalHeader.AddressOfEntryPoint  // 重置运行环境中的入口地址
       else
         ctx.Eax := Cardinal(p) + peH.OptionalHeader.AddressOfEntryPoint;
       SetThreadContext(Thrd, ctx);  // 更新运行环境
       ResumeThread(Thrd);           // 执行
       CloseHandle(Thrd);
     end
     else begin  // 加载失败,杀掉外壳进程
       TerminateProcess(Result, 0);
       CloseHandle(Thrd);
       CloseHandle(Result);
       Result := INVALID_HANDLE_VALUE;
     end;
   end
   else begin // 加载失败,杀掉外壳进程
     TerminateProcess(Result, 0);
     CloseHandle(Thrd);
     CloseHandle(Result);
     Result := INVALID_HANDLE_VALUE;
   end;
 end;
end;

function MemExecute(const ABuffer; Len: Integer; CmdParam: string; var ProcessId: Cardinal): Cardinal;
var
 peH: PImageNtHeaders;
 peSecH: PImageSectionHeaders;
 Ptr: Pointer;
 peSz: Cardinal;
begin
 result := INVALID_HANDLE_VALUE;
 if alignPEToMem(ABuffer, Len, peH, peSecH, Ptr, peSz) then
 begin
   result := AttachPE(CmdParam, peH, peSecH, Ptr, peSz, ProcessId);
   VirtualFree(Ptr, peSz, MEM_DECOMMIT);
   //VirtualFree(Ptr, 0, MEM_RELEASE);
 end;
end;

initialization
 MyVirtualAllocEx := GetProcAddress(GetModuleHandle(‘Kernel32.dll’), ‘VirtualAllocEx’);

end.

写了一个简单程序测试通过:)

program Test;

//{$APPTYPE CONSOLE}

uses
 SysUtils,
 Classes,
 PEUnit in ‘PEUnit.pas’;

var
 ABuffer: array of byte;
 Stream: TFileStream;
 ProcessId: Cardinal;
begin
 Stream:=TFileStream.Create(‘Target.exe’, fmOpenRead);
 try
   SetLength(ABuffer, Stream.Size);
   Stream.ReadBuffer(ABuffer[0], Stream.Size);
   MemExecute(ABuffer[0], Stream.Size, ”, ProcessId);
 finally
   Stream.Free;
 end;
end.

 

PASCAL版本的sscanf

 

unit sscanf;

interface
uses SysUtils;

type
  EFormatError = class(ExCeption);

  function Sscanf(const s: string; const fmt : string;
                      const Pointers : array of Pointer) : Integer;
implementation

{ Sscanf parses an input string. The parameters …
    s – input string to parse
    fmt – ‘C’ scanf-like format string to control parsing
      %d – convert a Long Integer
      %f – convert an Extended Float
      %s – convert a string (delimited by spaces)
      other char – increment s pointer past "other char"
      space – does nothing
    Pointers – array of pointers to have values assigned

    result – number of variables actually assigned

    for example with …
      Sscanf(‘Name. Bill   Time. 7:32.77   Age. 8′,
             ‘. %s . %d:%f . %d’, [@Name, @hrs, @min, @age]);

    You get …
      Name = Bill  hrs = 7  min = 32.77  age = 8                }

function Sscanf(const s: string; const fmt : string;
                      const Pointers : array of Pointer) : Integer;
var
  i,j,n,m : integer;
  s1      : string;
  L       : LongInt;
  X       : Extended;

  function GetInt : Integer;
  begin
    s1 := ”;
    while (s[n] = ‘ ‘)  and (Length(s) > n) do inc(n);
    while (s[n] in ['0'..'9', '+', '-'])
      and (Length(s) >= n) do begin
      s1 := s1+s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;

  function GetFloat : Integer;
  begin
    s1 := ”;
    while (s[n] = ‘ ‘)  and (Length(s) > n) do inc(n);
    while (s[n] in ['0'..'9', '+', '-', '.', 'e', 'E'])
      and (Length(s) >= n) do begin
      s1 := s1+s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;

  function GetString : Integer;
  begin
    s1 := ”;
    while (s[n] = ‘ ‘)  and (Length(s) > n) do inc(n);
    while (s[n] <> ‘ ‘) and (Length(s) >= n) do
    begin
      s1 := s1+s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;

  function ScanStr(c : Char) : Boolean;
  begin
    while (s[n] <> c) and (Length(s) > n) do inc(n);
    inc(n);

    If (n <= Length(s)) then Result := True
    else Result := False;
  end;

  function GetFmt : Integer;
  begin
    Result := -1;

    while (TRUE) do begin
      while (fmt[m] = ‘ ‘) and (Length(fmt) > m) do inc(m);
      if (m >= Length(fmt)) then break;

      if (fmt[m] = ‘%’) then begin
        inc(m);
        case fmt[m] of
          ‘d’: Result := vtInteger;
          ‘f’: Result := vtExtended;
          ’s’: Result := vtString;
        end;
        inc(m);
        break;
      end;

      if (ScanStr(fmt[m]) = False) then break;
      inc(m);
    end;
  end;

begin
  n := 1;
  m := 1;
  Result := 0;

  for i := 0 to High(Pointers) do begin
    j := GetFmt;

    case j of
      vtInteger : begin
        if GetInt > 0 then begin
          L := StrToInt(s1);
          Move(L, Pointers[i]^, SizeOf(LongInt));
          inc(Result);
        end
        else break;
      end;

      vtExtended : begin
        if GetFloat > 0 then begin
          X := StrToFloat(s1);
          Move(X, Pointers[i]^, SizeOf(Extended));
          inc(Result);
        end
        else break;
      end;

      vtString : begin
        if GetString > 0 then begin
          Move(s1, Pointers[i]^, Length(s1)+1);
          inc(Result);
        end
        else break;
      end;

      else break;
    end;
  end;
end;

end.

{
    Version 1.0 is written by unknown Delphier
    Version 2.0 is written by Lujiaxing(Delphier)

    distributed on GPL Lisence                                   }

{ numScanf parses an input string. The parameters …
    s – input string to parse
    fmt – ‘C’ scanf-like format string to control parsing
      %d – convert a Long Integer
      %f – convert an Extended Float
      other char – increment s pointer past "other char"
      space – does nothing
    Pointers – array of pointers to have values assigned

    result – number of variables actually assigned

    for example with …
      numScanf(‘Time. 7:32.77   Age. 8′,
             ‘. %d:%f . %d’, [@hrs, @min, @age]);

    You get …
      hrs = 7  min = 32.77  age = 8                              }

function numScanf(const s: string; const fmt : string;
                      const Pointers : array of Pointer) : Integer;
var
  i,j,n,m : integer;
  s1      : string;
  L       : LongInt;
  X       : Extended;

  function GetInt : Integer;
  begin
    s1 := ”;
    while (Length(s) > n) do begin
      if not (s[n] = ‘ ‘) then break;
      inc(n);
    end;
    while (s[n] in ['0'..'9', '+', '-'])
        and (Length(s) >= n) do begin
      //如果第二次遇到了加减号,则认为数字结束,Lujiaxing
      if (s[n]=’+') or (s[n]=’-') and (n>1) then break;
      s1 := s1+s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;

  function GetFloat : Integer;
  begin
    s1 := ”;
    while (Length(s) > n) do begin
      if not (s[n] = ‘ ‘) then break;
      inc(n);
    end;
    while (s[n] in ['0'..'9', '+', '-', '.', 'e', 'E'])
      and (Length(s) >= n) do begin
      s1 := s1+s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;

  function ScanStr(c : Char) : Boolean;
  begin
    while (Length(s) > n) do begin
      if not (s[n] = ‘ ‘) then break;
      inc(n);
    end;
    inc(n);

    If (n <= Length(s)) then Result := True
    else Result := False;
  end;

  function GetFmt : Integer;
  begin
    Result := -1;

    while (TRUE) do begin
      while (Length(fmt) > m) do begin
        if not (fmt[m] = ‘ ‘) then break;
        inc(m);
      end;
      if (m >= Length(fmt)) then break;

      if (fmt[m] = ‘%’) then begin
        inc(m);
        case fmt[m] of
          ‘d’: Result := vtInteger;
          ‘f’: Result := vtExtended;
          //’s’: Result := vtString;
        end;
        inc(m);
        break;
      end;

      if (ScanStr(fmt[m]) = False) then break;
      inc(m);
    end;
  end;

begin
  n := 1;
  m := 1;
  Result := 0;

  for i := 0 to High(Pointers) do begin
    j := GetFmt;

    case j of
      vtInteger : begin
        if GetInt > 0 then begin
          L := StrToInt(s1);
          Move(L, Pointers[i]^, SizeOf(LongInt));
          inc(Result);
        end
        else break;
      end;

      vtExtended : begin
        if GetFloat > 0 then begin
          X := StrToFloat(s1);
          Move(X, Pointers[i]^, SizeOf(Extended));
          inc(Result);
        end
        else break;
      end;

      else break;
    end;
  end;
end;

{ strScanf parses an input string. The parameters …
    s – input string to parsdssssse
    fmt – ‘C’ scanf-like format string to control parsing
      %s – convert a string (delimited by spaces)
      other char – increment s pointer past "other char"
      space – does nothing

    result – number of variables actually assigned

    for example with …
      var Strs:array[1..4] of string;
      strScanf(‘Name. Bill   Time. 7:32.77   Age. 8′,
             ‘Name. %s Time. %s:%s Age. %s’, Strs);

    You get …
      Strs[1] = ‘Bill’  Strs[2] = ‘7′  Strs[3] = ‘32.77′  Strs[4] = ‘8′       }
function strScanf(const s: string; const fmt : string;
                      var Strs : array of string) : Integer;
var
  i,j,n,m : integer;
  s1      : string;
  L       : LongInt;
  X       : Extended;

  function GetString : Integer;
  begin
    s1 := ”;
    while (Length(s) > n) do begin
      if not (s[n] = ‘ ‘) then break;
      inc(n);
    end;
    while (Length(s) >= n) do
    begin
      if (s[n] = ‘ ‘) then break;
      //如果遇到模板的下一个字符,则被认为是字符串结束,Lujiaxing
      if m<=length(fmt) then
        if (s[n]=fmt[m]) then break;
      s1 := s1+s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;

  function ScanStr(c : Char) : Boolean;
  begin
    while (Length(s) > n) do begin
      if not (s[n] = ‘ ‘) then break;
      inc(n);
    end;
    inc(n);

    If (n <= Length(s)) then Result := True
    else Result := False;
  end;

  function GetFmt : Integer;
  begin
    Result := -1;

    while (TRUE) do begin
      while (Length(fmt) > m) do begin
        if not (fmt[m] = ‘ ‘) then break;
        inc(m);
      end;
      if (m >= Length(fmt)) then break;

      if (fmt[m] = ‘%’) then begin
        inc(m);
        case fmt[m] of
          //’d': Result := vtInteger;
          //’f': Result := vtExtended;
          ’s’: Result := vtString;
        end;
        inc(m);
        break;
      end;

      if (ScanStr(fmt[m]) = False) then break;
      inc(m);
    end;
  end;

begin
  n := 1;
  m := 1;
  Result := 0;

  for i := 0 to Sizeof(Strs) do begin
    j := GetFmt;

    case j of
      vtString : begin
        if GetString > 0 then begin
          Strs[i]:=s1;
          inc(Result);
        end
        //else break;
      end;

      else break;
    end;
  end;
end;
 

 

后台调用外部程序的完美实现

 

后台调用外部程序的完美实现

最近在做的一个软件,其中有一部分功能需要调用其它的软件来完成,而那个软件只有可执行文件,根本没有源代码,幸好,我要做的事不难,只需要在我的程序启动后,将那个软件打开,在需要的时候,对其中的一个文本矿设置一些文字,再点击一个按钮就可以了。

说到这里,相信你也有了对该功能的一些初步设想了,没错,其基本思路就是:
1)调用CreateProcess()打开目标程序。
2)用FindWindow()找到目标程序的窗口Handle。
3)找到文本框的Handle,以及按钮的MessageID,用SendMessage()方法设置文字,并触发事件。

好了,这样确实很简单吧,但是当我实现它后,却发现这样做的结果则是:当我的程序启动并打开目标程序时,它的Splash窗口,以及主窗口都将显示出来,即使当我用FindWindow()找到主窗口Handle后,调用SendMessage(WindowHandle, SW_HIDE)来隐藏该窗口,还是会有一瞬主窗口被显示出来的,这样的效果实在是最求完美的我不忍心看到的。

那么怎么解决这个问题呢,首先我当然在CreateProcess()上面寻找方法,可惜,它只有一个参数可以设置窗口的默认显示方式,但是一旦这个窗口自己重设了显示方式,它就没有任何作用了。。。。继续查找文档,这时我看到CreateProcess()的一个参数TStartupInfo中有 lpDesktop这么一个属性,按照MSDN的说法,如果该指针为NULL,那么新建的Process将在当前Desktop上启动,而如果对其赋了一个Desktop的名称后,Process将在指定的Desktop上启动,恩,看来不错,就从它入手了:

1)首先,建立一个虚拟的Desktop,
const
  DesktopName = ‘MYDESK’;

FDesktop:=CreateDesktop(DesktopName,nil,nil,0,GENERIC_ALL,nil);
Windows 中可以建立多个Desktop,可以使用SwitchDesktop()来切换哪个Desktop被显示出来,以前有过将Windows模拟成Linux 的形式,可以在多个虚拟Desktop中切换的程序,其实那种程序也是用的Windows本身的虚拟Desktop功能来实现的,另外 Windows的启动画面,以及屏保画面也都是用虚拟Desktop实现的,好了,关于这方面不多介绍了,感兴趣的话,可以到MSDN中查看更详细资料:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/enumdesktops.asp

2)在CreateProcess的时候,指定程序在我新生成的Desktop上运行:
var
  StartInfo:TStartupInfo;

  FillChar(StartInfo, sizeof(StartInfo), 0);
  StartInfo.cb:=sizeof(StartInfo);
  StartInfo.lpDesktop:=PChar(DesktopName);      //指定Desktop的名称即可
  StartInfo.wShowWindow:=SW_HIDE;
  StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
  StartInfo.hStdError:=0;
  StartInfo.hStdInput:=0;
  StartInfo.hStdOutput:=0;
  if not CreateProcess(PChar(FileName),nil,nil,nil,true,CREATE_NEW_CONSOLE+HIGH_PRIORITY_CLASS,nil,PChar(ExtractFilePath(FilePath)),StartInfo,FProceInfo) then begin
    MessageBox(Application.Handle,’Error when init voice (5).’,PChar(Application.Title),MB_ICONWARNING);
    exit;
  end;

3)用FindWindow去找程序的主窗口
开始我直接写下了这样的代码:
  for I:=0 to 60 do begin //wait 30 seconds for open the main window
    WindowHandle:=FindWindow(nil,’WindowCaption’);
    if WindowHandle<>0 then begin
      break;
    end;
    Sleep(500);
  end;
但是,实践证明,这样是找不到不在当前Desktop中的Window的,那怎么办呢:
答案是,可以用SetThreadDesktop()函数,这个函数可以设置当前Thread工作所在的Desktop,于是我在以上代码前又加了一句:
  if not SetThreadDesktop(FDesktop) then begin
    exit;
  end;
但是,程序运行后,该函数却返回了false,说明方法调用失败了,再仔细看MSDN,发现有这么一句话:

The SetThreadDesktop function will fail if the calling thread has any windows or hooks on its current desktop (unless the hDesktop parameter is a handle to the current desktop).

哦,原来需要切换Desktop的线程中不能有任何 UI方面的东西,而我是在程序的主线程中调用该方法的,当然会失败拉,知道了这点就好办了,我只需要用一个“干净”的线程,让它绑定到新的Desktop 上,再让它用FindWindow()方法找到我要找的WindowHandle,不就可以了吗,于是,这一步就需要借助一个线程了,线程的代码如下:

  TFindWindowThread = class(TThread)
  private
    FDesktop:THandle;
    FWindowHandle:THandle;
  protected
    procedure Execute();override;
  public
    constructor Create(ACreateSuspended:Boolean;const ADesktop:THandle);reintroduce;
    property WindowHandle:THandle read FWindowHandle;
  end;

{ TFindWindowThread }

procedure TFindWindowThread.Execute();
var
  I:Integer;
begin
  //make the current thread find window on the new desktop!
  if not SetThreadDesktop(FDesktop) then begin
    exit;
  end;
  for I:=0 to 60 do begin //wait 30 seconds for open the main window
    FWindowHandle:=FindWindow(nil,PChar(‘WindowCaption’));
    if FWindowHandle<>0 then begin
      break;
    end;
    Sleep(500);
  end;
end;

constructor TFindWindowThread.Create(ACreateSuspended:Boolean;const ADesktop:THandle);
begin
  inherited Create(ACreateSuspended);
  FDesktop:=ADesktop;
end;

而主程序中的代码变成这样:
  FindWindowThread:=TFindWindowThread.Create(false,FDesktop);
  try
    FindWindowThread.WaitFor;
    FMainWindowHandle:=FindWindowThread.WindowHandle;
  finally
    FindWindowThread.Free;
  end;
  if FMainWindowHandle=0 then begin
    MessageBox(Application.Handle,’Error when init voice (6).’,PChar(Application.Title),MB_ICONWARNING);
    exit;
  end;

呵呵,成功,这样果然可以顺利的找到窗口Handle了。

4)最后,再用这个主窗口Handle,找出里面的EditBox的Handle,如这样:
  FEditWindow:=FindWindowEx(FMainWindowHandle,0,PChar(‘Edit’),nil);
我在这里指定了这个文本框的ClassName,这个名称可以用Spy++得到。

初始化的工作就到此结束了,如果顺利,程序就真正在后台被运行了起来。那么功能调用呢,还是和一般的做法一样:

  if (FMainWindowHandle=0) or (FEditWindow=0) then begin
    exit;
  end;
  SendMessage(FEditWindow,WM_SETTEXT,0,LongInt(@AText[1]));
  SendMessage(FMainWindowHandle,WM_COMMAND,$8012,$0);
其中$8012这个数字,也是用Spy++来得到的资源ID。

最后,别忘了关闭程序,以及释放虚拟Desktop:
  if FProceInfo.hProcess<>0 then begin
    TerminateProcess(FProceInfo.hProcess,0);
  end;
  if FDesktop<>0 then begin
    CloseDesktop(FDesktop);
  end;

好了,这样就几乎完美的实现了一个后台调用程序的功能,它对最终客户来说将是完全透明的,客户根本感觉不到后台还有另一个程序在工作。是不是很爽啊,这样别人的很多程序我们都可以直接拿来用了(当然了,得在遵守版权的基础上才行拉)。 

 

webbrowser 的使用

 

webbrowser

1.获得网页中变量值
htm中<script> var currID=123</script>
程序中可以这么调用 id := Form1.WebBrowser1.OleObject.Document.script.currID
值得说明的是,变量可以是javascript定义的,也可以是vbscript定义的,如果Webbrowser1中找不到该变量,调用会触发一个异常事件,即变量currID不存在

2.执行网页中的函数
tmp := ‘currID = getNextID(currID)’+#13#10;
Form1.WebBrowser1.OleObject.Document.parentWindow.execScript(tmp,’JavaScript’);
调用函数的方法就是execScript接口,同样,如果函数不存在,或者运行错误也会触发脚本错误异常

3.设置网页背景
背景图片 WebBrowser1.OleObject.Document.body.background     := ‘http://seelearn.com/bg.gif
‘        背景颜色 WebBrowser1.OleObject.Document.body.bgcolor     := ‘#eeeeee’

4.调用网页中已知对象
src := WebBrowser1.OleObject.Document.getElementByID(‘img1′).src      该方法其实就是javascript中的 getElementByID

5.获取页面中所有的frame
使用DHTML。
      frames:=wb.OleObject.document.frames;
      for i:=0 to frames.length do
      memo1.lines.Add(frames[i].document.body.innerHTML);

6.BorderStyle=bsNone后Webbrowser会被重新初始化
这是一个让人很意外的一个问题,Delphi在窗口控件的控制方面做得非常好,很少出现这种BUG
根据分析,出现这个现象有很多情况 改变FormStyle也会出现 ; 如果     webbrowser.parent
由panel1改到panel2.也会导致webbrowser重新初始化。

7.直接向Webbrowser中写入html代码,不需要Navigate到实际存在的文件
     var
     StrStream:TStringStream;
     SetNoteStr: string;      begin    SetNoteStr :=’<body bgcolor=222222 align=center><br><p align=center><font size=+2 color=#FFFFFF>扬帆博客 http://wesohon.com</font></p>’;
    SetNoteStr :=SetNoteStr+’<br><p align=center><font size=+2 color=#FFFFFF>点击左边按钮可查看对应图片</font></p>’;
    StrStream:=TStringStream.Create(SetNoteStr);
    WebBrowser1.Navigate(‘about:blank’);
    try
      StrStream.Position:=0;
      ( WebBrowser1.Document as IPersistStreamInit).Load(TStreamadapter.Create(StrStream));
    finally
      StrStream.Free;
    end;
8.前进,后退,刷新
self.WebBrowser1.GoBack
self.WebBrowser1.GoForward
self.WebBrowser1.Refresh

9.捕捉NewWindow2事件,即新开窗口事件
procedure TForm1.WebBrowser1NewWindow2(Sender: TObject;
    var ppDisp: IDispatch; var Cancel: WordBool);
var
    NewWindow: TForm2;
begin
    //exit;
    NewWindow:= TForm2.Create(nil);
    NewWindow.Show;
    ppDisp:= NewWindow.Webbrowser1.DefaultDispatch;
end;    值得一题的是该方法不能获得新开窗口的URL,退一步的方法只能是等到在新的Webbrowser中触发BeforeNavigate2事件判断了

10.网页中存在iframe时判断页面是否下载结束
procedure TForm1.WebBrowser1DocumentComplete(Sender: TObject;const pDisp: IDispatch; var URL: OleVariant);
   begin
   if WebBrowser1.Application = pDisp then showmessage(‘页面已全部下载完毕’)end;
   说明一下,每个iframe下载完毕都会触发DocumentComplete事件,所以一个页面在真正下载完毕前可能被触发多次

11. 获取网页中图片//使用DOM模型,什么都能读下来
   uses    MSHtml,    ActiveX;
   var
           html_doc:    IHTMLDocument2;
           doc_all :    IHtmlElementCollection;
           vI      :    IHtmlImgElement;
   begin
           html_doc    :=    WebBrowser1.Document    as    IHTMLDocument2;
           doc_all     :=    html_doc.images;
           for    I    :=    0    to    doc_all.length    -    1      do
           begin
                   vI  :=    doc_all.item(I,    EMPTYPARAM)    as    IHtmlImgElement;
                   //读取vI.src
                   Memo1.Lines.Add(vI.src);
           end;
   end;12. 下面函数获得ISomeControl接口var
           html_doc:    IHTMLDocument2;
           doc_all :    IHtmlElementCollection;
           vI      :    IHtmlElement;
           vD      :    IDispatch;
   begin
           html_doc    :=    WebBrowser1.Document    as    IHTMLDocument2;
           doc_all     :=    html_doc.all;
           for    I    :=    0    to    doc_all.length    -    1      do
           begin
                   vI  :=    doc_all.item(I,    EMPTYPARAM)    as    IHtmlElement;
                   if    vI.tagName=’OBJECT’    then
                   begin
                           //读取Classid
                           Memo1.Lines.Add((vI    as    IHtmlObjectElement).classid);
                           //读取包容的对象接口
                           vD    :=    (vI    as      IHtmlObjectElement).object_;
                           …
                   end;
           end;
   end;

 

你的编程语言可以这样做吗?(map/reduce的js示范) (转)

 

你的编程语言可以这样做吗?(map/reduce的js示范) (转)
[ 2006-08-15 21:39:13 | Author: todd ]
Font Size: Large | Medium | Small
一篇很好的关于map/reduce的示范文章。看了这个,你就会理解prototype.js中Enumerable的精髓。

通过它,你可以写出很多非常奇特非常美妙的代码。短短几行,功能可不简单哦~
例如Scriptaculous中,一开篇就应用了一个findAll,两个each。8行代码,其实只是一句而已:
$A(document.getElementsByTagName("script")).findAll( function(s) {
 return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
}).each( function(s) {
 var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,”);
 var includes = s.src.match(/\?.*load=([a-z,]*)/);
 (includes ? includes[1] : ‘builder,effects,dragdrop,controls,slider’).split(‘,’).each(
 function(include) { Scriptaculous.require(path+include+’.js’) });
});

《你的编程语言可以这样做吗?(map/reduce的js示范) 》原文如下:

有一天,你在浏览自己的代码,发现有两大段代码几乎一样。实际上,它们确实是一样的——除了一个关于意大利面(Spaghetti)而另一个关于巧克力慕思(Chocolate Moose)。
 // 一个小例子:
 
 alert("偶要吃意大利面!");
 alert("偶要吃巧克力慕思!");
嗯,这个例子碰巧是用javascript写的,不过你就算不懂JavaScript,应该也能明白它在干什么。

拷贝代码不好。于是,你创建了个函数
 function SwedishChef( food ){
 alert("偶要吃" + food + "!");
 }
 SwedishChef("意大利面");
 SwedishChef("巧克力慕思");
Ok,这只是一个很小很小的例子而已,相信你能想像到个更实际一点的例子。这段代码有很多优点,你全都听过几万次了:可维护性、可读性、抽象性 = 好!

现在你留意到有另外两段代码几乎跟它们一模一样,除了一个反复调用一个叫BoomBoom的函数,另一个反复调用一个叫PutInPot的。除此之外,這两段代码简直没什么两样:
 alert("拿龙虾");
 PutInPot("龙虾");
 PutInPot("水");
 alert("拿鸡肉");
 BoomBoom("鸡肉");
 BoomBoom("椰子酱");
现在要想个办法,使得你可以將一个函数用作另一个函数的参数。这是个重要的能力,因为你更容易将框架代码写成一个函数(emu注:还记得template method模式吧?)。
 function Cook( i1, i2, f ){
 alert("拿" + i1);
 f(i1);
 f(i2);
 }
 Cook( "龙虾", "水", PutInPot );
 Cook( "鸡肉", "椰子酱", BoomBoom );
看看,我们居然把函数当成调用参数传递了!

你的编程语言能办到吗?

等等……假如我们已经有了PutInPot和BoomBoom这些函数的具体实现代码(而且又不需要在别的地方重用它们),那么用内联语法把它们写进函数调用里面不是比显式的声明这两个函数更漂亮吗?
 Cook( "龙虾",
 "水",
 function(x) { alert("pot " + x); } );
 Cook( "鸡肉",
 "椰子酱",
 function(x) { alert("boom " + x); } );
耶,真方便!请注意我只是随手创建了个函数,甚至不用考虑怎么为它起名,只要拎着它的耳朵把它往一个函数里头一丢就可以了。

当你一想到作为参数的匿名函数,你也许想到对那些对数组里的每个元素进行相同操作的代码。
 var a = [1,2,3];
 for (i=0; i<a.length; i++){
 a[i] = a[i] * 2;
 }
 for (i=0; i<a.length; i++){
 alert(a[i]);
 }
常常要对数组里的所有元素做同一件事,因此你可以写个这样的函数来帮忙:
 function map(fn, a){
 for (i = 0; i < a.length; i++){
 a[i] = fn(a[i]);
 }
 }
现在你可以把上面的东西改成:
 map( function(x){return x*2;}, a );
 map( alert, a );
另一个常见的任务是将数组内的所有元素按照某总方式汇总起来:
 function sum(a){
 var s = 0;
 for (i = 0; i < a.length; i++)
 s += a[i];
 return s;
 }
 
 function join(a){
 var s = "";
 for (i = 0; i < a.length; i++)
 s += a[i];
 return s;
 }
 
 alert(sum([1,2,3]));
 alert(join(["a","b","c"]));
sum和join长得很像,你也许想把它们抽象为一个将数组内的所有元素按某种算法汇总起來的泛型函数:
 function reduce(fn, a, init){
 var s = init;
 for (i = 0; i < a.length; i++)
 s = fn( s, a[i] );
 return s;
 }
 
 function sum(a){
 return reduce( function(a, b){ return a + b; }, a, 0 );
 }
 
 function join(a){
 return reduce( function(a, b){ return a + b; }, a, "" );
 }
许多早期的编程语言没法子做这种事。有些语言容许你做,却又困难重重(例如C有函数指针,但你要在別处声明和定义函数)。面向对象语言也不确保你用函数可以干些啥(把函数当对象处理?)。

如果你想将函数视为一类对象,Java要求你建立一个有单方法的对象,称为算子对象。许多面向对象语言要你为每个类都建立一个完整文件,像这样开发可真叫快。如果你的编程語言要你使用算子对象来包装方法(而不是把方法本身当成对象),你就不能徹底得到现代(动态)编程语言的好处。不妨试试看你可否退货拿回些钱?

不用再写那些除了经过一个数组对每个元素做一些事情之外一无是处的函数,有什么好处?

让我们看回map函数。当你要对数组内的每个元素做一些事,你很可能不在乎哪个元素先做。无论由第一个元素开始执行,还是是由最后一个元素执行,你的结果都是一样的,对不?如果你手头上有2個CPU,你可以写段代码,使得它们各对一半的元素工作,于是乎map快了两倍。

或者,发挥一下想像力,设想你在全球有千千万万台服务器分布在全世界的若干个数据中心,你有一个真的很大很大的数组,嗯,再发挥一下想像力,设想这个数组记录有整个互联网的内容。还了,现在你可以在几千台服务器上同时执行map,让每台服务器都来解决同一个问题的一小部分。

那么在这个例子里面,编写一段非常快的代码来搜索整个互联网这个问题,其实就和用一个简单的字符串搜索器(算子)作为参数来调用map函数一样简单了。

希望你注意到一个真正有意思的要点,如果你想要把map/reduce模式变成一个对所有人都有用,对所有人都能立刻派上用场的技术,你只需要一个超级天才来写最重要的一部分代码,来让map/reduce可以在一个巨大的并行计算机阵列上运行,然后其他旧的但是一向在单一个循环中运行良好的代码,仍可以保持正确的运行,惟一的差别只是比原来单机运行快了n倍。这意味着它们都一不留神突然变成可以被用来解决一个巨大的问题的代码。

让我再啰嗦一下,通过把“循环”这个概念加以抽象,你可以把用任何你喜欢的方式来实现“循环”过程,包括可以实现让循环迭代速度随着硬件计算能力保持令人满意的同步增长。

你现在应该可以明白不久为何对那些对除了Java之外什么都沒被学过的计算机系学生表示不满了:
( http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html ) :

Without understanding functional programming, you can’t invent MapReduce, the algorithm that makes Google so massively scalable. The terms Map and Reduce come from Lisp and functional programming. MapReduce is, in retrospect, obvious to anyone who remembers from their 6.001-equivalent programming class that purely functional programs have no side effects and are thus trivially parallelizable. The very fact that Google invented MapReduce, and Microsoft didn’t, says something about why Microsoft is still playing catch up trying to get basic search features to work, while Google has moved on to the next problem: building Skynet^H^H^H^H^H^H the world’s largest massively parallel supercomputer. I don’t think Microsoft completely understands just how far behind they are on that wave.

不理解函数式编程,你就发明不了MapReduce这个让Google的计算能力如此具有可扩展性的算法。Map和Reduce这两个术语源自Lisp语言和函数式编程……(这是另一篇文章的内容,emu也不是很理解其中的各种说法的来龙去脉,就不翻译了)

我希望你现在明白,把函数当成基本类型的(动态)编程语言能让你在编程过程中更好的进行抽象化,也就是使代码精悍、功能更内聚、更具可重用性及更具有扩展性。很多的Google应用使用Map/Reduce模式,因此一有人对其优化或修正缺陷,它们就都可以从中得益。

我准备要再罗嗦一下,我认为最有生产力的编程语言莫过于能让你在不同层次上都可以进行抽象化的。老掉牙的FORTRAN 语言以前是不让你写函数的注。C 有函数指针,可是它们都非常丑丑丑丑丑丑丑丑陋,不允许匿名声明,又不能在用它们时实现它们而偏偏要放在別处去实现。Java让你使用算子对象,一种更丑陋的东西。正如Steve Yegge所述,Java是個名词王国
( http://steveyegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html )。

作者注:这里提起了FORTRAN,不过我上次使用FORTRAN是27年前的事了。FORTRAN是有函数的,我码字那会儿脑子里面想的大概是GW-BASIC语言。(emu注,basic确实只有所谓的子程序和go-sub语句,作用只是重新组织代码结构而已,没有参数和调用堆栈,因此没有真正的函数调用)

译者注:原作者起了《你的编程语言可以这样做吗》这个标题其实并不是这篇文章的真正价值所在,我转这篇文章也不是因为原作者可以把语言的初级技巧玩得转,而是因为这是一篇map/reduce模型的示范。