Sample Code
The following code shows how to obtain the filename or path and description of a given link file:
#include <windows.h>
#include <shlobj.h>
// GetLinkInfo() fills the filename and path buffer
// with relevant information.
// hWnd - calling application's window handle.
//
// lpszLinkName - name of the link file passed into the function.
//
// lpszPath - the buffer that receives the file's path name.
//
// lpszDescription - the buffer that receives the file's
// description.
HRESULT
GetLinkInfo( HWND hWnd,
LPCTSTR lpszLinkName,
LPSTR lpszPath,
LPSTR lpszDescription)
{
HRESULT hres;
IShellLink *pShLink;
WIN32_FIND_DATA wfd;
// Initialize the return parameters to null strings.
*lpszPath = '\0';
*lpszDescription = '\0';
// Call CoCreateInstance to obtain the IShellLink
// Interface pointer. This call fails if
// CoInitialize is not called, so it is assumed that
// CoInitialize has been called.
hres = CoCreateInstance( &CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLink,
(LPVOID *)&pShLink );
if (SUCCEEDED(hres))
{
IPersistFile *ppf;
// The IShellLink Interface supports the IPersistFile
// interface. Get an interface pointer to it.
hres = pShLink->lpVtbl->QueryInterface(pShLink,
&IID_IPersistFile,
(LPVOID *)&ppf );
if (SUCCEEDED(hres))
{
WORD wsz[MAX_PATH];
// Convert the given link name string to a wide character string.
MultiByteToWideChar( CP_ACP, 0,
lpszLinkName,
-1, wsz, MAX_PATH );
// Load the file.
hres = ppf->lpVtbl->Load(ppf, wsz, STGM_READ );
if (SUCCEEDED(hres))
{
// Resolve the link by calling the Resolve() interface function.
// This enables us to find the file the link points to even if
// it has been moved or renamed.
hres = pShLink->lpVtbl->Resolve(pShLink, hWnd,
SLR_ANY_MATCH | SLR_NO_UI);
if (SUCCEEDED(hres))
{
// Get the path of the file the link points to.
hres = pShLink->lpVtbl->GetPath( pShLink, lpszPath,
MAX_PATH,
&wfd,
SLGP_SHORTPATH );
// Only get the description if we successfully got the path
// (We can't return immediately because we need to release ppf &
// pShLink.)
if(SUCCEEDED(hres))
{
// Get the description of the link.
hres = pShLink->lpVtbl->GetDescription(pShLink,
lpszDescription,
MAX_PATH );
}
}
}
ppf->lpVtbl->Release(ppf);
}
pShLink->lpVtbl->Release(pShLink);
}
return hres;
}
posted @
2008-08-14 06:02 幽幽 阅读(570) |
评论 (0) |
编辑 收藏
GDI+ 将字样相同但字形不同的字体分组为字体系列。例如,Arial 字体系列中包含以下字体:
-
Arial Regular
-
Arial Bold
-
Arial Italic
-
Arial Bold Italic
GDI+ 使用四种字形形成字体系列:常规、粗体、倾斜和粗斜体。像 narrow 和 rounded 之类的形容词不被视为字形;而是作为字体系列名的一部分。例如,Arial Narrow 是包含以下成员的字体系列:
-
Arial Narrow Regular
-
Arial Narrow Bold
-
Arial Narrow Italic
-
Arial Narrow Bold Italic
在可以使用 GDI+ 绘制文本之前,您需要构造一个 FontFamily 对象和一个 Font 对象。FontFamily 对象指定字样(例如 Arial),而 Font 对象指定字号、字形和单位。
示例
下面的示例构造一个字号为 16 像素、常规字形的 Arial 字体。在下面的代码中,传递给 Font 构造函数的第一个参数是 FontFamily 对象。第二个参数指定字体的大小,其单位由第四个参数确定。第三个参数确定字形。
Pixel 为 GraphicsUnit 枚举的一个成员,Regular 是 FontStyle 枚举的一个成员。
FontFamily fontFamily = new FontFamily("Arial");
Font font = new Font(
fontFamily,
16,
FontStyle.Regular,
GraphicsUnit.Pixel);
posted @
2008-08-14 03:51 幽幽 阅读(1498) |
评论 (0) |
编辑 收藏
SHGetFileInfo函数
function SHGetFileInfo(pszPath: PAnsiChar; dwFileAttributes: DWORD;
var psfi: TSHFileInfo; cbFileInfo, uFlags: UINT): DWORD; stdcall;
pszPath 参数:指定的文件名。
当uFlags的取值中不包含 SHGFI_PIDL时,可直接指定;
当uFlags的取值中包含 SHGFI_PIDL时pszPath要通过计算获得,不能直接指定;
dwFileAttributes参数:文件属性。
仅当uFlags的取值中包含SHGFI_USEFILEATTRIBUTES时有效,一般不用此参数;
psfi 参数:返回获得的文件信息,是一个记录类型,有以下字段:
_SHFILEINFOA = record
hIcon: HICON; { out: icon } //文件的图标句柄
iIcon: Integer; { out: icon index } //图标的系统索引号
dwAttributes: DWORD; { out: SFGAO_ flags } //文件的属性值
szDisplayName: array [0..MAX_PATH-1] of AnsiChar; { out: display name (or path) } //文件的显示名
szTypeName: array [0..79] of AnsiChar; { out: type name } //文件的类型名
end;
cbFileInfo 参数:psfi的比特值;
uFlags 参数:指明需要返回的文件信息标识符,常用的有以下常数:
SHGFI_ICON; //获得图标
SHGFI_DISPLAYNAME; //获得显示名
SHGFI_TYPENAME; //获得类型名
SHGFI_ATTRIBUTES; //获得属性
SHGFI_LARGEICON; //获得大图标
SHGFI_SMALLICON; //获得小图标
SHGFI_PIDL; // pszPath是一个标识符
函数SHGetFileInfo()的返回值也随uFlags的取值变化而有所不同。
可见通过调用SHGetFileInfo()可以由psfi参数得到文件的图标句柄。但要注意在uFlags参数中不使用SHGFI_PIDL时,SHGetFileInfo()不能获得“我的电脑”等虚似文件夹的信息。
应该注意的是,在调用SHGetFileInfo()之前,必须使用 CoInitialize 或者OleInitialize 初始化COM,否则表面上能够使用,但是会造成不安全或者丧失部分功能。例如,一个常见的例子:如果不初始化COM,那么调用该函数就无法得到.htm/.mht/.xml文件的图标。
以下是两个例子:
1.获得系统图标列表:
//取得系统图标列表
uses
ShellAPI
var
ImageListHandle : THandle;
FileInfo: TSHFileInfo;
//小图标
ImageListHandle := SHGetFileInfo('C:\',
0,
FileInfo,
SizeOf(FileInfo),
SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
//把图标列表同一个名叫ListView1的ListView控件的小图标关联。
SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, ImageListHandle);
//大图标
ImageListHandle := SHGetFileInfo('C:\',
0,
FileInfo,
SizeOf(FileInfo),
SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
//把图标列表同一个名叫ListView1的ListView控件的大图标关联。
SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, ImageListHandle);
2.获得一个文件的显示名和图标
var
sfi: TSHFileInfo;
IconIndex : Integer;
//取图标的索引号等信息
SHGetFileInfo(PAnsiChar(FileName),
0,
sfi,
sizeof(TSHFileInfo),
ShellAPI.SHGFI_DISPLAYNAME or ShellAPI.SHGFI_TYPENAME or ShellAPI.SHGFI_LARGEICON or ShellAPI.SHGFI_ICON);
//显示名和图标在系统图标列表中的编号就分别在sfi.szDisplayName和sfi.iIcon中
posted @
2008-08-13 23:11 幽幽 阅读(1356) |
评论 (0) |
编辑 收藏
一、 简介
屏幕抓图程序在处理图形中应用广泛。作为Windows XP及以后版本操作系统的图形处理内核,GDI+在二维几何图形处理、图像显示与转换和字符排版等方面简直是传统GDI程序员的一种解脱。但是,至少在目前情况下,GDI+尚不能完全代替GDI。与GDI相比,它至少还存在以下不足:
不支持从内存到屏幕的位传输操作;
不支持光栅“位运算”操作;
如果程序性能、速度要求比较严格,在图片输出方面的表现较差时,GDI往往能取代实现高性能的输出。
本文通过对流行的屏幕抓图程序工作原理的剖析,力图向读者阐明GDI+与GDI各自在图形处理方面的优缺点,并给出相应的VC++ .NET代码实现。
二、 GDI在抓图中的关键作用
要实现屏幕抓图,关键有两点:一是获取图片所在窗口的窗口句柄,即在何处捕获图片;二是保存抓取的图片,实现这一点正是GDI+的强项。
对于问题一,可以利用SetCapture函数,它能够追踪鼠标指针的移动(包括在屏幕抓图程序窗口之外的窗口)。在移动鼠标的过程中,它还可以根据鼠标的指针所在位置来判断当前窗口的窗口句柄。我们还可以使用函数WindowFromPoint,这个函数能够找出鼠标指针当前位置所对应的窗口句柄。
使用过知名的抓图软件SnagIT的读者都知道,在选择抓图窗口时,鼠标指针所在位置的窗口都会出现加粗的红色边框,以提醒目前所选择的窗口,这个功能实现起来有些复杂。下面介绍在GDI中如何使这个红色边框出现。
【注意】正是由于这个红色边框的实现,读者才能发现GDI+在这方面的弱点。
在GDI中,一个最基本的概念就是设备环境(DC),每一个窗口都具有自己的DC。如果能够找到窗口的DC,那么,用户就能够在该窗口的任何位置绘图。然而,在屏幕抓图程序中,由于用户所选择的窗口不固定,所以,要想得到鼠标指针所处窗口的DC并不容易。这一问题的答案在于GetDC函数。下面是GetDC的函数声明:
HDC GetDC(HWND hWnd);
这里,hWnd是DC对应的窗口句柄。注意,当hWnd为空时,该函数返回的是整个屏幕的设备环境句柄。这就意味着,开发人员可以在屏幕上的任何位置进行任意的绘图操作。
在鼠标指针所处的窗口绘图时,绘图的目的只是为了提醒用户目前所选择的窗口,所以,在绘图时,必须保证不会破坏窗口原有的画面。这时可将窗口的绘图模式设为RS_NOTXORPEN,将画笔颜色与屏幕颜色进行异或运算之后,再对屏幕颜色取反即可。RS_NOTXORPEN运算方式的特点在于:对同一像素进行两次RS_NOTXORPEN运算后,像素值并不会发生变化。这样,在同一个地方进行两次绘图后,窗口的画面并不会发生任何变化。
【注意】这些功能在GDI+中很难实现。
三、 编码实现
由上可知,屏幕抓图至少分为3个步骤:
(1) 启用鼠标指针捕获。
(2) 在鼠标指针所在处的窗口进行绘图,提示抓图的目标。
(3) 选定目标窗口时,将目标窗口的画面保存为自定义的位图并终止鼠标指针捕获。
以下是具体的编程步骤:
(1)在Visual C++ .NET中按照GDI+程序的框架新建一个基于对话框的项目ScreenCapture,然后准备好一个外形为相机的光标文件(*.cur),将之引入资源管理器(IDC_CAMERA)。接着在CScreenCaptureDlg类中加入以下两个全局变量:
HWND hwndCapture;
Crect rectCapture;
(2)通过类向导加入对WM_MOUSEMOVE及WM_LBUTTONUP事件的响应函数,分别如下所示。
void CScreenCaptureDlg::OnMouseMove(UINT nFlags, CPoint point)
{
//如果用户按隹鼠标左键不放,则开始抓取图片
if(nFlags==MK_LBUTTON){
//隐藏程序窗口,以免影响在抓取时的“视野”
ShowWindow(SW_HIDE);
//载入“照相机”鼠标指针,开始追踪鼠标指针的移动
HCURSOR cur=LoadCursor(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDC_CAMERA));
SetCursor(cur);
SetCapture();
//获得鼠标指针所在窗口的句柄
this->ClientToScreen(&point);
hwndCapture=(HWND)::WindowFromPoint(point);
//取得屏幕的设备环境句柄,以便在屏幕的任何位置绘图
HDC hDC=::GetDC(NULL);
//建立一个红色的画笔
HPEN hPen=CreatePen(PS_INSIDEFRAME,6,RGB(255,0,0));
//将绘图模式设为R2_NOTXORPEN,在绘图时可以不破坏原有的背景
int nMode=SetROP2(hDC,R2_NOTXORPEN);
HPEN hpenOld=(HPEN)SelectObject(hDC,hPen);
//得到鼠标指针所在窗口的区域
::GetWindowRect(hwndCapture,&rectCapture);
//在鼠标指针所在处的窗口四周画一红色的矩形,做为选定时的提示
POINT pt[5];
pt[0]=CPoint(rectCapture.left,rectCapture.top);
pt[1]=CPoint(rectCapture.right,rectCapture.top);
pt[2]=CPoint(rectCapture.right,rectCapture.bottom);
pt[3]=CPoint(rectCapture.left,rectCapture.bottom);
pt[4]=CPoint(rectCapture.left,rectCapture.top);
::Polyline(hDC,pt,5);
//延时后再重绘红色矩形,这样不会破坏原有的内容
Sleep(100);
::Polyline(hDC,pt,5);
::SelectObject(hDC,hpenOld);
::ReleaseDC(NULL,hDC);
}
CDialog::OnMouseMove(nFlags, point);
}
void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// 得到鼠标指针所在窗口的区域宽、高
int nWidth=rectCapture.Width();
int nHeight=rectCapture.Height();
HDC hdcScreen,hMemDC;
HBITMAP hBitmap,hOldBitmap;
//建立一个屏幕设备环境句柄
hdcScreen=CreateDC("DISPLAY",NULL,NULL,NULL);
hMemDC=CreateCompatibleDC(hdcScreen);
//建立一个与屏幕设备环境句柄兼容、与鼠标指针所在窗口的区域等大的位图
hBitmap=CreateCompatibleBitmap(hdcScreen,nWidth,nHeight);
//把新位图选到内存设备描述表中
hOldBitmap=(HBITMAP)SelectObject(hMemDC,hBitmap);
//把屏幕设备描述表拷贝到内存设备描述表中
BitBlt(hMemDC,0,0,nWidth,nHeight,hdcScreen,rectCapture.left,rectCapture.top,SRCCOPY);
DeleteDC(hdcScreen);
DeleteDC(hMemDC);
//返回位图句柄
//打开剪贴板,并将位图拷到剪贴板上
OpenClipboard();
EmptyClipboard();
SetClipboardData(CF_BITMAP,hBitmap);
//关闭剪贴板
CloseClipboard();
MessageBox("屏幕内容已经拷到剪贴板!");
ReleaseCapture();
//恢复窗口显示模式
ShowWindow(SW_NORMAL);
CDialog::OnLButtonUp(nFlags, point);
}
至此,一个具有专业效果的屏幕抓图程序的核心已经搞定。
四、 用GDI+实现画面的保存
经过上面两步,如果用户在对话框中按住鼠标左键不放,程序便开始“抓图”。当选择好抓图的目标后,松开鼠标左键,抓图的目标窗口的画面就自动保存到剪贴板中了。但是,把画面保存到文件中更为重要。如果用GDI的方式来操作,需要对各种类位图的结构有详尽的了解,极其麻烦。但如果用GDI+来实现之则极为容易。下面介绍如何将已经抓到的图片保存到一个BMP文件中。
由上面知,抓图程序已经得到了所捕获的窗口的位图句柄,接下来要将位图句柄保存为相应的位图文件。这一切归功于GDI+的Bitmap类,详见下列代码。
void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
//……省略
if(GetSaveFileName(&ofn))
{
CLSID pngClsid;
Bitmap bmp(hBitmap,NULL);
//获取BMP文件的编码方式
GetEncoderClsid(L"image/bmp",&pngClsid);//帮助函数
CString tmp(ofn.lpstrFile);
CStringW filename((LPCSTR)tmp);
//保存所截取的屏幕图片
bmp.Save(filename,&pngClsid);
}
ReleaseCapture();
MessageBox("屏幕内容已经保存到文件中!");
//恢复窗口显示模式
ShowWindow(SW_NORMAL);
CDialog::OnLButtonUp(nFlags, point);
}
五、 小结
本文通过一个专业的屏幕抓图程序的核心实现,对比分析了GDI与GDI+各自的优缺点。但我们相信,GDI+作为新一代图形引擎,随着版本的不断升级,其迟早要淘汰掉GDI。本人拙见,不足处还望读者指正。
另外,本文源码在Windows 2000/VC++.NET 2003环境中调试通过。调试过程中注意:
确保工程对GDI+库的正确引用:在头文件stdafx.h中要加入相应引用;在应用程序类的InitInstance成员函数前后及其析构函数中加适当的操作;工程编译时要加入对gdiplus.lib的引用(“项目”|“添加现有项”,我的机器上是在C:\Program Files\Microsoft Visual Studio.NET\vc7\platformSDK\lib下找到库文件)。
posted @
2008-08-13 23:02 幽幽 阅读(1996) |
评论 (0) |
编辑 收藏
下面是GetSystemMetrics函数参数nIndex的定义:
SM_ARRANGE Flags specifying how the system arranged minimized windows. For more information about minimized windows, see the following Remarks section.
SM_CLEANBOOT 返回系统启动方式:
0 正常启动
1 安全模式启动
2 网络安全模式启动
SM_CMOUSEBUTTONS 返回值为系统支持的鼠标键数,返回0,则系统中没有安装鼠标。
SM_CXBORDER,
SM_CYBORDER 返回以像素值为单位的Windows窗口边框的宽度和高度,如果Windows的为3D形态,则
等同于SM_CXEDGE参数
SM_CXCURSOR,
SM_CYCURSOR 返回以像素值为单位的标准光标的宽度和高度
SM_CXDLGFRAME,
SM_CYDLGFRAME 等同与SM_CXFIXEDFRAME and SM_CYFIXEDFRAME
SM_CXDOUBLECLK,
SM_CYDOUBLECLK 以像素值为单位的双击有效的矩形区域
SM_CXEDGE,SM_CYEDGE 以像素值为单位的3D边框的宽度和高度
SM_CXFIXEDFRAME,
SM_CYFIXEDFRAME 围绕具有标题但无法改变尺寸的窗口(通常是一些对话框)的边框的厚度
SM_CXFRAME,SM_CYFRAME 等同于SM_CXSIZEFRAME and SM_CYSIZEFRAME
SM_CXFULLSCREEN,
SM_CYFULLSCREEN 全屏幕窗口的窗口区域的宽度和高度
SM_CXHSCROLL,
SM_CYHSCROLL 水平滚动条的高度和水平滚动条上箭头的宽度
SM_CXHTHUMB 以像素为单位的水平滚动条上的滑动块宽度
SM_CXICON,SM_CYICON 系统缺省的图标的高度和宽度(一般为32*32)
SM_CXICONSPACING,
SM_CYICONSPACING 以大图标方式查看Item时图标之间的间距,这个距离总是大于等于
SM_CXICON and SM_CYICON.
SM_CXMAXIMIZED,
SM_CYMAXIMIZED 处于顶层的最大化窗口的缺省尺寸
SM_CXMAXTRACK,
SM_CYMAXTRACK 具有可改变尺寸边框和标题栏的窗口的缺省最大尺寸,如果窗口大于这个
尺寸,窗口是不可移动的。
SM_CXMENUCHECK,
SM_CYMENUCHECK 以像素为单位计算的菜单选中标记位图的尺寸
SM_CXMENUSIZE,
SM_CYMENUSIZE 以像素计算的菜单栏按钮的尺寸
SM_CXMIN,SM_CYMIN 窗口所能达到的最小尺寸
SM_CXMINIMIZED,
SM_CYMINIMIZED 正常的最小化窗口的尺寸
SM_CXMINTRACK,
SM_CYMINTRACK 最小跟踪距离,当使用者拖动窗口移动距离小于这个值,窗口不会移动。
SM_CXSCREEN,
SM_CYSCREEN 以像素为单位计算的屏幕尺寸。
SM_CXSIZE,SM_CYSIZE 以像素计算的标题栏按钮的尺寸
SM_CXSIZEFRAME,
SM_CYSIZEFRAME 围绕可改变大小的窗口的边框的厚度
SM_CXSMICON,
SM_CYSMICON 以像素计算的小图标的尺寸,小图标一般出现在窗口标题栏上。
SM_CXVSCROLL,
SM_CYVSCROLL 以像素计算的垂直滚动条的宽度和垂直滚动条上箭头的高度
SM_CYCAPTION 以像素计算的普通窗口标题的高度
SM_CYMENU 以像素计算的单个菜单条的高度
SM_CYSMCAPTION 以像素计算的窗口小标题栏的高度
SM_CYVTHUMB 以像素计算的垂直滚动条中滚动块的高度
SM_DBCSENABLED 如果为TRUE或不为0的值表明系统安装了双字节版本的USER.EXE,为FALSE或0则不是。
SM_DEBUG 如果为TRUE或不为0的值表明系统安装了debug版本的USER.EXE,为FALSE或0则不是。
SM_MENUDROPALIGNMENT 如果为TRUE或不为0的值下拉菜单是右对齐的否则是左对齐的。
SM_MOUSEPRESENT 如果为TRUE或不为0的值则安装了鼠标,否则没有安装。
SM_MOUSEWHEELPRESENT 如果为TRUE或不为0的值则安装了滚轮鼠标,否则没有安装。(Windows NT only)
SM_SWAPBUTTON 如果为TRUE或不为0的值则鼠标左右键交换,否则没有。
posted @
2008-08-10 21:42 幽幽 阅读(788) |
评论 (0) |
编辑 收藏
在 Windows 中实现 Java 本地方法
![](http://www.ibm.com/i/c.gif) |
![developerWorks](http://www.ibm.com/developerworks/i/dw.gif) |
![](http://www.ibm.com/i/c.gif) |
级别: 初级
David WendtWebSphere Development Research Triangle Park, NC
1999 年 5 月 01 日
本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。这些示例包括传递和返回常用的数据类型。
本文中的示例使用 Sun Microsystems 公司创建的 Java DevelopmentKit (JDK) 版本 1.1.6 和 Java本地接口 (JNI) 规范。 用 C 语言编写的本地代码是用 MicrosoftVisual C++ 编译器编译生成的。
简介
本文提供调用本地 C 代码的 Java 代码示例,包括传递和返回某些常用的数据类型。本地方法包含在特定于平台的可执行文件中。就本文中的示例而言,本地方法包含在 Windows 32 位动态链接库 (DLL) 中。
不过我要提醒您,对 Java 外部的调用通常不能移植到其他平台上,在 applet 中还可能引发安全异常。实现本地代码将使您的 Java 应用程序无法通过 100% 纯 Java 测试。但是,如果必须执行本地调用,则要考虑几个准则:
- 将您的所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。
- 本地方法要简单。尽量将您的 DLL 对任何第三方(包括 Microsoft)运行时 DLL 的依赖减到最小。使您的本地方法尽量独立,以将加载您的 DLL 和应用程序所需的开销减到最小。如果需要运行时 DLL,必须随应用程序一起提供它们。
Java 调用 C
对于调用 C 函数的 Java 方法,必须在 Java 类中声明一个本地方法。在本部分的所有示例中,我们将创建一个名为 MyNative 的类,并逐步在其中加入新的功能。这强调了一种思想,即将本地方法集中在单个类中,以便将以后所需的移植工作减到最少。
示例 1 -- 传递参数
在第一个示例中,我们将三个常用参数类型传递给本地函数: String、 int和 boolean 。本例说明在本地 C 代码中如何引用这些参数。
public class MyNative
{
public void showParms( String s, int i, boolean b )
{
showParms0( s, i , b );
}
private native void showParms0( String s, int i, boolean b );
static
{
System.loadLibrary( "MyNative" );
}
}
|
请注意,本地方法被声明为专用的,并创建了一个包装方法用于公用目的。这进一步将本地方法同代码的其余部分隔离开来,从而允许针对所需的平台对它进行优化。 static子句加载包含本地方法实现的 DLL。
下一步是生成 C 代码来实现 showParms0 方法。此方法的 C 函数原型是通过对 .class 文件使用 javah 实用程序来创建的,而 .class 文件是通过编译 MyNative.java 文件生成的。这个实用程序可在 JDK 中找到。下面是 javah 的用法:
javac MyNative.java(将 .java 编译为 .class)
javah -jni -classpath . (指定源代码的当前目录,这里要注意,是指package目录所在的目录)
MyNative(生成 .h 文件)
|
这将生成一个 MyNative.h 文件,其中包含一个本地方法原型,如下所示:
/*
* Class: MyNative
* Method: showParms0
* Signature: (Ljava/lang/String;IZ)V
*/
JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *, jobject, jstring, jint, jboolean);
|
第一个参数是调用 JNI 方法时使用的 JNI Environment 指针。第二个参数是指向在此 Java 代码中实例化的 Java 对象 MyNative 的一个句柄。其他参数是方法本身的参数。请注意,MyNative.h 包括头文件 jni.h。jni.h 包含 JNI API 和变量类型(包括jobject、jstring、jint、jboolean,等等)的原型和其他声明。
本地方法是在文件 MyNative.c 中用 C 语言实现的:
#include <stdio.h>
#include "MyNative.h"
JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );
printf( "String = [%s]\n", szStr );
printf( "int = %d\n", i );
printf( "boolean = %s\n", (b==JNI_TRUE ? "true" : "false") );
(*env)->ReleaseStringUTFChars( env, s, szStr );
}
|
JNI API,GetStringUTFChars,用来根据 Java 字符串或 jstring 参数创建 C 字符串。这是必需的,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C 字符串或 Unicode。有关转换 Java 字符串的详细信息,请参阅标题为 NLS Strings and JNI 的一篇论文。但是,jboolean 和 jint 值可以直接使用。
MyNative.dll 是通过编译 C 源文件创建的。下面的编译语句使用 Microsoft Visual C++ 编译器:
cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c
-FeMyNative.dll
|
其中 c:\jdk1.1.6 是 JDK 的安装路径。
MyNative.dll 已创建好,现在就可将其用于 MyNative 类了。 可以这样测试这个本地方法:在 MyNative 类中创建一个 main 方法来调用 showParms 方法,如下所示:
public static void main( String[] args )
{
MyNative obj = new MyNative();
obj.showParms( "Hello", 23, true );
obj.showParms( "World", 34, false );
}
|
当运行这个 Java 应用程序时,请确保 MyNative.dll 位于 Windows 的 PATH 环境变量所指定的路径中或当前目录下。当执行此 Java 程序时,如果未找到这个 DLL,您可能会看到以下的消息:
java MyNative
Can't find class MyNative
|
这是因为 static 子句无法加载这个 DLL,所以在初始化 MyNative 类时引发异常。Java 解释器处理这个异常,并报告一个一般错误,指出找不到这个类。 如果用 -verbose 命令行选项运行解释器,您将看到它因找不到这个 DLL 而加载 UnsatisfiedLinkError 异常。
如果此 Java 程序完成运行,就会输出以下内容:
java MyNative
String = [Hello]
int = 23
boolean = true
String = [World]
int
= 34
|
boolean = false 示例 2 -- 返回一个值
本例将说明如何在本地方法中实现返回代码。 将这个方法添加到 MyNative 类中,这个类现在变为以下形式:
public class MyNative
{
public void showParms( String s, int i, boolean b )
{
showParms0( s, i , b );
}
public int hypotenuse( int a, int b )
{
return hyptenuse0( a, b );
}
private native void showParms0( String s, int i, boolean b );
private native int hypotenuse0( int a, int b );
static
{
System.loadLibrary( "MyNative" );
}
/* 测试本地方法 */
public static void main( String[] args )
{
MyNative obj = new MyNative();
System.out.println( obj.hypotenuse(3,4) );
System.out.println( obj.hypotenuse(9,12) );
}
}
|
公用的 hypotenuse 方法调用本地方法 hypotenuse0 来根据传递的参数计算值,并将结果作为一个整数返回。这个新本地方法的原型是使用 javah 生成的。请注意,每次运行这个实用程序时,它将自动覆盖当前目录中的 MyNative.h。按以下方式执行 javah:
生成的 MyNative.h 现在包含 hypotenuse0 原型,如下所示:
/*
* Class: MyNative
* Method: hypotenuse0
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *, jobject, jint, jint);
|
该方法是在 MyNative.c 源文件中实现的,如下所示:
#include <stdio.h>
#include <math.h>
#include "MyNative.h"
JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );
printf( "String = [%s]\n", szStr );
printf( "int = %d\n", i );
printf( "boolean = %s\n", (b==JNI_TRUE ? "true" : "false") );
(*env)->ReleaseStringUTFChars( env, s, szStr );
}
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *env, jobject obj, jint a, jint b)
{
int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );
return (jint)rtn;
}
|
再次请注意,jint 和 int 值是可互换的。 使用相同的编译语句重新编译这个 DLL:
cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c
-FeMyNative.dll
|
现在执行 java MyNative 将输出 5 和 15 作为斜边的值。
示例 3 -- 静态方法
您可能在上面的示例中已经注意到,实例化的 MyNative 对象是没必要的。实用方法通常不需要实际的对象,通常都将它们创建为静态方法。本例说明如何用一个静态方法实现上面的示例。更改 MyNative.java 中的方法签名,以使它们成为静态方法:
public static int hypotenuse( int a, int b )
{
return hypotenuse0(a,b);
}
...
private static native int hypotenuse0( int a, int b );
|
现在运行 javah 为 hypotenuse0创建一个新原型,生成的原型如下所示:
/*
* Class: MyNative
* Method: hypotenuse0
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *, jclass, jint, jint);
|
C 源代码中的方法签名变了,但代码还保持原样:
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *env, jclass cls, jint a, jint b)
{
int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );
return (jint)rtn;
}
|
本质上,jobject 参数已变为 jclass 参数。此参数是指向 MyNative.class 的一个句柄。main 方法可更改为以下形式:
public static void main( String[] args )
{
System.out.println( MyNative.hypotenuse( 3, 4 ) );
System.out.println( MyNative.hypotenuse( 9, 12 ) );
}
|
因为方法是静态的,所以调用它不需要实例化 MyNative 对象。本文后面的示例将使用静态方法。
示例 4 -- 传递数组
本例说明如何传递数组型参数。本例使用一个基本类型,boolean,并将更改数组元素。下一个示例将访问 String(非基本类型)数组。将下面的方法添加到 MyNative.java 源代码中:
public static void setArray( boolean[] ba )
{
for( int i=0; i < ba.length; i++ )
ba[i] = true;
setArray0( ba );
}
...
private static native void setArray0( boolean[] ba );
|
在本例中,布尔型数组被初始化为 true,本地方法将把特定的元素设置为 false。同时,在 Java 源代码中,我们可以更改 main 以使其包含测试代码:
boolean[] ba = new boolean[5];
MyNative.setArray( ba );
for( int i=0; i < ba.length; i++ )
System.out.println( ba[i] );
|
在编译源代码并执行 javah 以后,MyNative.h 头文件包含以下的原型:
/*
* Class: MyNative
* Method: setArray0
* Signature: ([Z)V
*/
JNIEXPORT void JNICALL Java_MyNative_setArray0
(JNIEnv *, jclass, jbooleanArray);
|
请注意,布尔型数组是作为单个名为 jbooleanArray 的类型创建的。 基本类型有它们自已的数组类型,如 jintArray 和 jcharArray。 非基本类型的数组使用 jobjectArray 类型。下一个示例中包括一个 jobjectArray。这个布尔数组的数组元素是通过 JNI 方法 GetBooleanArrayElements 来访问的。 针对每种基本类型都有等价的方法。这个本地方法是如下实现的:
JNIEXPORT void JNICALL Java_MyNative_setArray0
(JNIEnv *env, jclass cls, jbooleanArray ba)
{
jboolean* pba = (*env)->GetBooleanArrayElements( env, ba, 0 );
jsize len = (*env)->GetArrayLength(env, ba);
int i=0;
// 更改偶数数组元素
for( i=0; i < len; i+=2 )
pba[i] = JNI_FALSE;
(*env)->ReleaseBooleanArrayElements( env, ba, pba, 0 );
}
|
指向布尔型数组的指针可以使用 GetBooleanArrayElements 获得。 数组大小可以用 GetArrayLength 方法获得。使用 ReleaseBooleanArrayElements 方法释放数组。现在就可以读取和修改数组元素的值了。jsize 声明等价于 jint(要查看它的定义,请参阅 JDK 的 include 目录下的 jni.h 头文件)。
示例 5 -- 传递 Java String 数组
本例将通过最常用的非基本类型,Java String,说明如何访问非基本对象的数组。字符串数组被传递给本地方法,而本地方法只是将它们显示到控制台上。 MyNative 类定义中添加了以下几个方法:
public static void showStrings( String[] sa )
{
showStrings0( sa );
}
private static void showStrings0( String[] sa );
|
并在 main 方法中添加了两行进行测试:
String[] sa = new String[] { "Hello,", "world!", "JNI", "is", "fun." };
MyNative.showStrings( sa );
|
本地方法分别访问每个元素,其实现如下所示。
JNIEXPORT void JNICALL Java_MyNative_showStrings0
(JNIEnv *env, jclass cls, jobjectArray sa)
{
int len = (*env)->GetArrayLength( env, sa );
int i=0;
for( i=0; i < len; i++ )
{
jobject obj = (*env)->GetObjectArrayElement(env, sa, i);
jstring str = (jstring)obj;
const char* szStr = (*env)->GetStringUTFChars( env, str, 0 );
printf( "%s ", szStr );
(*env)->ReleaseStringUTFChars( env, str, szStr );
}
printf( "\n" );
}
|
数组元素可以通过 GetObjectArrayElement 访问。 在本例中,我们知道返回值是 jstring 类型,所以可以安全地将它从 jobject 类型转换为 jstring 类型。字符串是通过前面讨论过的方法打印的。有关在 Windows 中处理 Java 字符串的信息,请参阅标题为 NLS Strings and JNI 的一篇论文。
示例 6 -- 返回 Java String 数组
最后一个示例说明如何在本地代码中创建一个字符串数组并将它返回给 Java 调用者。MyNative.java 中添加了以下几个方法:
public static String[] getStrings()
{
return getStrings0();
}
private static native String[] getStrings0();
|
更改 main 以使 showStrings 将 getStrings 的输出显示出来:
MyNative.showStrings( MyNative.getStrings() );
|
实现的本地方法返回五个字符串。
JNIEXPORT jobjectArray JNICALL Java_MyNative_getStrings0
(JNIEnv *env, jclass cls)
{
jstring str;
jobjectArray args = 0;
jsize len = 5;
char* sa[] = { "Hello,", "world!", "JNI", "is", "fun" };
int i=0;
args = (*env)->NewObjectArray(env, len, (*env)->FindClass(env, "java/lang/String"), 0);
for( i=0; i < len; i++ )
{
str = (*env)->NewStringUTF( env, sa[i] );
(*env)->SetObjectArrayElement(env, args, i, str);
}
return args;
}
|
字符串数组是通过调用 NewObjectArray 创建的,同时传递了 String 类和数组长度两个参数。Java String 是使用 NewStringUTF 创建的。String 元素是使用 SetObjectArrayElement 存入数组中的。
调试
现在您已经为您的应用程序创建了一个本地 DLL,但在调试时还要牢记以下几点。如果使用 Java 调试器 java_g.exe,则还需要创建 DLL 的一个“调试”版本。这只是表示必须创建同名但带有一个 _g 后缀的 DLL 版本。就 MyNative.dll 而言,使用 java_g.exe 要求在 Windows 的 PATH 环境指定的路径中有一个 MyNative_g.dll 文件。在大多数情况下,这个 DLL 可以通过将原文件重命名或复制为其名称带缀 _g 的文件。
现在,Java 调试器不允许您进入本地代码,但您可以在 Java 环境外使用 C 调试器(如 Microsoft Visual C++)调试本地方法。首先将源文件导入一个项目中。 将编译设置调整为在编译时将 include 目录包括在内:
c:\jdk1.1.6\include;c:\jdk1.1.6\include\win32
|
将配置设置为以调试模式编译 DLL。在 Project Settings 中的 Debug 下,将可执行文件设置为 java.exe(或者 java_g.exe,但要确保您生成了一个 _g.dll 文件)。程序参数包括包含 main 的类名。如果在 DLL 中设置了断点,则当调用本地方法时,执行将在适当的地方停止。
下面是设置一个 Visual C++ 6.0 项目来调试本地方法的步骤。
- 在 Visual C++ 中创建一个 Win32 DLL 项目,并将 .c 和 .h 文件添加到这个项目中。
- 在 Tools 下拉式菜单的 Options 设置下设置 JDK 的 include 目录。下面的对话框显示了这些目录。
- 选择 Build 下拉式菜单下的 Build MyNative.dll 来建立这个项目。确保将项目的活动配置设置为调试(这通常是缺省值)。
- 在 Project Settings 下,设置 Debug 选项卡来调用适当的 Java 解释器,如下所示:
当执行这个程序时,忽略“在 java.exe 中找不到任何调试信息”的消息。当调用本地方法时,在 C 代码中设置的任何断点将在适当的地方停止 Java 程序的执行。
其他信息
JNI 方法和 C++
上面这些示例说明了如何在 C 源文件中使用 JNI 方法。如果使用 C++,则请将相应方法的格式从:
(*env)->JNIMethod( env, .... );
|
更改为:
在 C++ 中,JNI 函数被看作是 JNIEnv 类的成员方法。
字符串和国家语言支持
本文中使用的技术用 UTF 方法来转换字符串。使用这些方法只是为了方便起见,如果应用程序需要国家语言支持 (NLS),则不能使用这些方法。有关在 Windows 和 NLS 环境中处理 Java 字符串正确方法,请参标题为 NLS Strings and JNI 的一篇论文。
小结
本文提供的示例用最常用的数据类据(如 jint 和 jstring)说明了如何实现本地方法,并讨论了 Windows 特定的几个问题,如显示字符串。本文提供的示例并未包括全部 JNI,JNI 还包括其他参数类型,如 jfloat、jdouble、jshort、jbyte 和 jfieldID,以及用来处理这些类型的方法。有关这个主题的详细信息,请参阅 Sun Microsystems 提供的 Java 本地接口规范。
关于作者
![](http://www.ibm.com/i/c.gif) |
|
![](http://www.ibm.com/i/c.gif) |
David Wendt 是 IBM WebSphere Studio 的一名程序员,该工作室位于北卡罗莱纳州的 Research Triangle Park。可以通过 wendt@us.ibm.com 与他联系。
|
|
posted @
2008-08-08 05:17 幽幽 阅读(611) |
评论 (0) |
编辑 收藏
1、基本用法
JFileChooser dlg = new JFileChooser();
dlg.setDialogTitle("Open JPEG file");
int result = dlg.showOpenDialog(this); // 打开"打开文件"对话框
// int result = dlg.showSaveDialog(this); // 打"开保存文件"对话框
if (result == JFileChooser.APPROVE_OPTION) {
File file = dlg.getSelectedFile();
...
}
2、自定义FileFilter
JDK没有提供默认的文件过滤器,但提供了过滤器的抽象超类,我们可以继承它。
import javax.swing.filechooser.FileFilter;
public final class PictureFileFilter extends FileFilter {
private String extension;
private String description;
public PictureFileFilter(String extension, String description) {
super();
this.extension = extension;
this.description = description;
}
public boolean accept(File f) {
if (f != null) {
if (f.isDirectory()) {
return true;
}
String extension = getExtension(f);
if (extension != null && extension.equalsIgnoreCase(this.extension)) {
return true;
}
}
return false;
}
public String getDescription() {
return description;
}
private String getExtension(File f) {
if (f != null) {
String filename = f.getName();
int i = filename.lastIndexOf('.');
if (i > 0 && i < filename.length() - 1) {
return filename.substring(i + 1).toLowerCase();
}
}
return null;
}
}
其实主要就是accept(File f)函数。上例中只有一个过滤器,多个过滤器可参考JDK目录中“demo\jfc\FileChooserDemo\src”中的“ExampleFileFilter.java”
3、多选
在基本用法中,设置
c.setMultiSelectionEnabled(true);
即可实现文件的多选。
读取选择的文件时需使用
File[] files = c.getSelectedFiles();
4、选择目录
利用这个打开对话框,不仅可以选择文件,还可以选择目录。
其实,对话框有一个FileSelectionMode属性,其默认值为“JFileChooser.FILES_ONLY”,只需要将其修改为“JFileChooser.DIRECTORIES_ONLY”即可。
JFileChooser c = new JFileChooser();
c.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
c.setDialogTitle("Select path to save");
int result = c.showOpenDialog(PrintDatetime.this);
if (result == JFileChooser.APPROVE_OPTION) {
String path = c.getSelectedFile().getAbsolutePath());
...
}
posted @
2008-08-08 01:38 幽幽 阅读(10092) |
评论 (1) |
编辑 收藏
清除屏幕闪烁
(转自网上)
<一>
由于作图过于复杂和频繁,所以时常出现闪烁的情况,一些防止闪烁的方法,如下:
(1)将Invalidate()替换为InvalidateRect()。
Invalidate()会导致整个窗口的图象重画,需要的时间比较长,而InvalidateRect()仅仅重画Rect区域内的内容,所以所需时间会少一些。不要为一小块区域的重画就调用Invalidate(),不愿意自己去计算需要重画的Rect,事实上,如果你确实需要改善闪烁的情况,计算一个Rect所用的时间比起重画那些不需要重画的内容所需要的时间要少得多。
(2)禁止系统擦除你的窗口。
系统在需要重画窗口的时候会帮你用指定的背景色来擦除窗口。可是,也许需要重画的区域也许非常小。或者,在你重画这些东西之间还要经过大量的计算才能开始.这个时候你可以禁止系统擦掉原来的图象。直到你已经计算好了所有的数据,自己把那些需要擦掉的部分用背景色覆盖掉(如:dc.FillRect(rect,&brush);rect是需要擦除的区域,brush是带背景色的刷子),再画上新的图形。要禁止系统擦除你的窗口,可以重载OnEraseBkgnd()函数,让其直接返回TRUE就可以了。如
BOOL CmyWin::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
//return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。
}
(3)有效的进行擦除。
擦除背景的时候,不要该擦不该擦的地方都擦。比如,你在一个窗口上放了一个很大的Edit框,几乎占了整个窗口,那么你频繁的擦除整个窗口背景将导致Edit不停重画形成剧烈的闪烁.事实上你可以CRgn创建一个需要擦除的区域,只擦除这一部分.如
GetClientRect(rectClient);
rgn1.CreateRectRgnIndirect(rectClient);
rgn2.CreateRectRgnIndirect(m_rectEdit);
if(rgn1.CombineRgn(&rgn1,&rgn2,RGN_XOR)= ERROR)
//处理后的rgn1只包括了Edit框之外的客户区域,这样,Edit将不会被我的背景覆盖而导致重画.
{
ASSERT(FALSE);
return ;
}
brush.CreateSolidBrush(m_clrBackgnd);
pDC->FillRgn(&rgn1,&brush);
brush.DeleteObject();
注意:在使用这个方法的时候要同时使用方法二。
(4).使用MemoryDC先在内存里把图画好,再复制到屏幕上。
这对于一次画图过程很长的情况比较管用。毕竟内存操作比较快,而且复制到屏幕又是一次性的,至少不会出现可以明显看出一个东西从左画到右的情况。
void CMyWiew::OnDraw() //CScrollView下双缓冲内存的实现:
{
CRect rect;
GetClientRect(&rect);
CDC* m_pMemoryDC = new CDC();
CBitmap * m_pBitmap = new CBitmap();
CPoint ScrollPoint=GetScrollPosition();
m_pMemoryDC->CreateCompatibleDC(pDC);
m_pBitmap->CreateCompatibleBitmap(pDC,rect.right+1,rect.bottom+1);//这里的Bitmap是必须的,否则当心弄出一个大黑块.
CBitmap * pOldbmp=m_pMemoryDC->SelectObject(m_pBitmap);
//m_pMemoryDC->SelectStockObject(WHITE_BRUSH);//画出白色背景方法一
//m_pMemoryDC->Rectangle(-1,-1,rect.right + 2 , rect.bottom + 2 );
//m_pMemoryDC->SelectStockObject(NULL_BRUSH);
m_pMemoryDC->PatBlt(0,0,rect.right, rect.bottom,WHITENESS);//画出白色背景方法二
//-----------------如下是显示图片的方法----------------------------------------------------------
//BITMAP BM;
//CBitmap pBitmap;
//pBitmap.LoadBitmap(IDB_BITMAP2);
//CDC * pTdc = new CDC();
//pTdc->CreateCompatibleDC(pDC);
//CBitmap* pom = pTdc->SelectObject(&pBitmap);
//pBitmap->GetObject(sizeof(BM),&BM);
//m_pMemoryDC->BitBlt(0-ScrollPoint.x,0-ScrollPoint.y, BM.bmWidth,BM.bmHeight, pTdc,0,0,SRCCOPY);
//pTdc->DeleteDC();
//delete pTdc;
//--------------图片显示完毕----------------------------------------------------------------------
//m_pMemoryDC->SetROP2(R2_NOT);//设定绘图模式
m_pMemoryDC->MoveTo(0-ScrollPoint.x,0-ScrollPoint.y);
m_pMemoryDC->LineTo(1000-ScrollPoint.x,5000-ScrollPoint.y);
pDC->BitBlt(ScrollPoint.x, ScrollPoint.y, rect.right, rect.bottom, m_pMemoryDC, 0, 0, SRCCOPY);
m_pMemoryDC->SelectObject(pOldbmp);
m_pBitmap->DeleteObject();
m_pMemoryDC->DeleteDC();
delete m_pBitmap;
delete m_pMemoryDC;
}
*******************************
解决Windows程序界面闪烁问题的一些经验
(转自网上)
<二>
一般的windows 复杂的界面需要使用多层窗口而且要用贴图来美化,所以不可避免在窗口移动或者改变大小的时候出现闪烁。
先来谈谈闪烁产生的原因
原因一:
如果熟悉显卡原理的话,调用GDI函数向屏幕输出的时候并不是立刻就显示在屏幕
上只是写到了显存里,而显卡每隔一段时间把显存的内容输出到屏幕上,这就是刷新周期。
一般显卡的刷新周期是 1/80秒左右,具体数字可以自己设置的。
这样问题就来了,一般画图都是先画背景色,然后再把内容画上去,如果这两次操作不在同一个
刷新周期内完成,那么给人的视觉感受就是,先看到只有背景色的图像,然后看到画上内容的图像,
这样就会感觉闪烁了。
解决方法:尽量快的输出图像,使输出在一个刷新周期内完成,如果输出内容很多比较慢,那么采用
内存缓冲的方法,先把要输出的内容在内存准备好,然后一次输出到显存。要知道一次API调用一般可以
在一个刷新周期内完成。
对于GDI,用创建内存DC的方法就可以了
原因二:
复杂的界面有多层窗口组成,当windows在窗口改变大小的时候是先重画父窗口,然后重画子窗口,子父
窗口重画的过程一般无法在一个刷新周期内完成,所以会呈现闪烁。
我们知道父窗口上被子窗口挡住的部分其实没必要重画的
解决方法:给窗口加个风格 WS_CLIPCHILDREN ,这样父窗口上被子窗口挡住的部分就不会重画了。
如果同级窗口之间有重叠,那么需要再加上 WS_CLIPSIBLINGS 风格
原因三:
有时候需要在窗口上使用一些控件,比如IE,当你的窗口改变大小的时候IE会闪烁,即使你有了WS_CLIPCHILDREN
也没用。原因在于窗口的类风格有CS_HREDRAW 或者 CS_VREDRAW,这两个风格表示窗口在宽度或者高度变化的时候
重画,但是这样就会引起IE闪烁
解决方法:注册窗口类的时候不要使用这两个风格,如果窗口需要在改变大小的时候重画,那么可以在WM_SIZE的时候
调用RedrawWindow。
原因四:
界面上窗口很多,而且改变大小时很多窗口都要移动和改变大小,如果使用MoveWindow或者SetWindowPos两个API来
改变窗口的大小和位置,由于他们是等待窗口重画完成后才返回,所以过程很慢,这样视觉效果就可能会闪烁。
解决方法:
使用以下API来处理窗口移动,BeginDeferWindowPos, DeferWindowPos,EndDeferWindowPos
先调用 BeginDeferWindowPos 设定需要移动的窗口的个数
使用DeferWindowPos,来移动窗口,这个API并不真的造成窗口移动
EndDeferWindowPos 一次性完成所有窗口的大小和位置的改变。
有个地方要特别注意,要仔细计算清楚要移动多少个窗口,BeginDeferWindowPos设定
的个数一定要和实际的个数一致,否则在Win9x下,如果实际移动的窗口数多于调用BeginDeferWindowPos
时设定的个数,可能会造成系统崩溃。在Windows NT系列下不会有这样的问题。
*******************************
使用内存DC解决重画闪烁问题
(转自网上)
<三>
下述代码在OnDraw时绘图:
void CRedrawDemoView::OnDraw(CDC* pDC)
{
CRedrawDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
static const char* pText = "解决重画闪烁问题!";
RECT clRect;
::GetClientRect(m_hWnd, &clRect);
pDC->FillSolidRect(&clRect, RGB(255, 255, 255));
int x = 100, y = 100;
RECT rect = { x - 20, y - 20};
rect.right = rect.left + 160;
rect.bottom = rect.top + 60;
pDC->FillSolidRect(&rect, RGB(0, 255, 0));
pDC->TextOut(x, y, pText, strlen(pText));
}
首先将背景填充白色,然后画一绿色的矩形,再在矩形上输出一段文字,如此过程必然会引起画面闪烁,
解决办法:使用内存DC,先将图形绘制到内存DC,然后拷贝到屏幕,实现无闪烁绘图。
修改后的代码如下:
void CRedrawDemoView::OnDraw(CDC* pDC)
{
CRedrawDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
static const char* pText = "解决重画闪烁问题!";
CRect clRect;
::GetClientRect(m_hWnd, &clRect);
CDC memDC;
memDC.CreateCompatibleDC(pDC);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(pDC, clRect.Width(), clRect.Height());
CBitmap * pOldBitmap = memDC.SelectObject(&bitmap);
memDC.FillSolidRect(&clRect, RGB(255, 255, 255));
int x = 100, y = 100;
RECT rect = { x - 20, y - 20};
rect.right = rect.left + 160;
rect.bottom = rect.top + 60;
memDC.FillSolidRect(&rect, RGB(0, 255, 0));
memDC.TextOut(x, y, pText, strlen(pText));
pDC->BitBlt(0, 0, clRect.Width(), clRect.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOldBitmap);
}
也可以在上述代码中加入绘制Bitmap位图代码,注意应该阻止窗口擦除背景,重载OnEraseBkgnd函数
BOOL CRedrawDemoView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return TRUE;
// return CView::OnEraseBkgnd(pDC);
}
为易于理解,以上代码未经优化。
*******************************
用:
CreateCompatibleBitmap
CreateCompatibleDC
等函数在内存中把要画的图先画出来,然后使用
BitBlt复制到设备上就OK!
*******************************
posted @
2008-08-04 23:39 幽幽 阅读(1949) |
评论 (0) |
编辑 收藏
PE格式,是Windows的可執行檔的格式。 Windows中的 exe檔,dll檔,都是PE格式。 PE 就是Portable Executable 的縮寫。 Portable 是指對於不同的Windows版本和不同的CPU類型上PE檔的格式是一樣的,當然CPU不一樣了,CPU指令的二進位編碼是不一樣的。只是檔中各種東西的佈局是一樣的。 能告示根底嗎! 摘要 Matt Pietrek(姜慶東譯) 對可執行檔的深入認識將帶你深入到系統深處。如果你知道你的exe/dll裏是些什麼東東,你就是一個更有知識的程式師。作為系列文章的第一章,將關注這幾年來PE格式的變化,同時也簡單介紹一下PE格式。經過這次更新,作者加入了PE格式是如何與.NET協作的及PE檔表格(PE FILE SECTIONS),RVA,The DataDirectory,函數的輸入等內容。 ==================== 很久以前,我給Microsoft Systems Journal(現在的MSDN)寫了一篇名為“Peering Inside the PE: A Tour of the Win32 Portable Executable File format”的文章。後來比我期望的還流行,到現在我還聽說有人在用它(它還在MSDN裏)。不幸的是,那篇文章的問題依舊存在,WIN32的世界靜悄悄地變了好多,那篇文章已顯得過期了。從這個月開始我將用這兩篇文章來彌補。 你可能會問為什麼我應當瞭解PE格式,答案依舊:作業系統的可執行檔格式和資料結構暴露出系統的底層細節。通過瞭解這些,你的程式將編的更出色。 當然,你可以閱讀微軟的文檔來瞭解我將要告訴你的。但是,像很多文檔一樣,‘寧可晦澀,但為瓦全’。 我把焦點放在提供一些不適合放在正式文檔裏的內容。另外,這篇文章裏的一些知識不見得能在官方文檔裏找到。 1. 裂縫的撕開 讓我給你一些從1994年我寫那篇文章來PE格式變化的例子。WIN16已經成為歷史,也就沒有必要作什麼比較和說明了。另外一個可憎的東西就是用在WINDOWS 3.1 中的WIN32S,在它上面運行程式是那麼的不穩定。 那時候,WINDOWS 95(也叫Chicago)還沒有發行。NT還是3.5版。微軟的連接器還沒開始大規模的優化,儘管如此,there were MIPS and DEC Alpha implementations of Windows NT that added to the story. 那麼究竟,這麼些年來,有些什麼新的東西出來呢?64位的WINDOWS有了它自己的PE變種,WINDOWS CE 支持各種CPU了,各種優化如DLL的延遲載入,節表的合併,動態捆綁等也已出臺。 有很多類似的東西發生了。 讓我們最好忘了.NET。它是如何與系統切入的呢?對於作業系統,.NET的可執行檔格式是與舊的PE格式相容的。雖然這麼說,在運行時期,.NET還是按元資料和中間語言來組織資料的,這畢竟是它的核心。這篇文章當中,我將打開.NET元資料這扇門,但不做深入討論。 如果WIN32的這些變化都不足以讓我重寫這篇文章,就是原來的那些錯誤也讓我汗顏。比如我對TLS的描述只是一帶而過,我對時間戳的描述只有你生活在美國西部才行等等。還有,一些東西已是今是作非了,我曾說過.RDATA幾乎沒排上用場,今天也是,我還說過.IDATA節是可讀可寫的,但是一些搞API攔截的人發現好像是錯的。 在更新這篇文章的過程當中,我也檢查了PEDUMP這個用來傾印PE檔的程式.這個程式能夠在0X86和IA-64平臺下編譯和運行。 2. PE格式概覽 微軟的可執行檔格式,也就是大家熟悉的PE 格式,是官方文檔的一部分。但是,它是從VAX/VMS上的COFF派生出來的,就WINDOWS NT小組的大部分是從DEC轉過來的看來,這是可以理解的。很自然,這些人在NT的開發上會用他們以往的代碼。 採用術語“PORTABLE EXECUTABLE”是因為微軟希望有一個通用在所有WINDOWS平臺上和所有CPU上的檔格式。從大的方面講,這個目標已經實現。它適用于NT及其後代,95及其後代,和CE. 微軟產生的OBJ檔是用COFF格式的。當你看到它的很多域都是用八進制的編碼的,你會發現她是多麼古老了。COFF OBJ檔用到了很多和PE一樣的資料結構和枚舉,我馬上會提到一些。 64位的WINDOWS只對PE格式作了一點點改變。這個新的格式叫做PE32+。沒有增加一個欄位,且只刪了一個欄位。其他的改變就是把以前的32位欄位擴展成64位。對於C++代碼,通過巨集定義WINDOWS的頭檔已經遮罩了這些差別。 EXE與DLL的差別完全是語義上的。它們用的都是同樣一種檔格式-PE。唯一的區別就是其中有一個欄位標識出是EXE還是DLL.還有很多DLL的擴展比如OCX,CPL等都是DLL.它們有一樣的實體。 你首先要知道的關於PE的知識就是磁片中的資料結構佈局和記憶體中的資料結構佈局是一樣的。載入可執行檔(比如LOADLIBARY)的首要任務就是把磁片中的檔映射到進程的位址空間.因此像IMAGE_NT_HEADER(下面解釋)在磁片和記憶體中是一樣的。關鍵的是你要懂得你怎樣在磁片中獲得PE檔某些資訊的,當它載入記憶體時你可以一樣獲得,基本上是沒什麼不同的(即記憶體映射檔)。但是知道與映射普通的記憶體映射檔不同是很重要的。WINDOWS載入器察看PE檔才決定映射到哪里,然後從檔的開始處往更高的位址映射,但是有的東西在檔中的偏移和在記憶體中的偏移會不一樣。儘管如此,你也有了足夠的資訊把檔偏移轉化成記憶體偏移。見圖一: 當Windows載入器把PE載入記憶體,在記憶體中它稱作模組(MODULE),檔從HMODULE這個位址開始映射。記住這點:給你個HMODULE,從那你可以知道一個資料結構(IMAGE_DOS_HEADER),然後你還可以知道所有得資料結構。這個強大的功能對於API攔截特別有意義。(準確地說:對於WINDOWS CE,這是不成立的,不過這是後話)。 記憶體中的模組代表著進程從這個可執行檔中所需要的所有代碼,資料,資源。其他部分可以被讀入,但是可能不映射(如,重定位節)。還有一些部分根本就不映射,比如當調試資訊放到檔的尾部的時候。有一個欄位告訴系統把檔映射到記憶體需要多少記憶體。不需要的資料放在檔的尾部,而在過去,所有部分都映射。 在WINNT.H描述了PE 格式。在這個檔中,幾乎有所有的關於PE的資料結構,枚舉,#DEFINE。當然,其他地方也有相關文檔,但是還是WINNT.H說了算。 有很多檢測PE文件的工具,有VISUAL STUDIO的DUMPBIN,SDK中的DEPENDS,我比較喜歡DEPENDS,因為它以一種簡潔的方式檢測出檔的引入引出。一個免費的PE察看器,PEBrowse,來自smidgenosoft。我的pedump也是很有用的,它和dumpbin有一樣的功能。 從api的立場看,imagehlp.dll提供了讀寫pe檔的機制。 在開始討論pe檔前,回顧一下pe檔的一些基本概念是有意義的。在下面幾節,我將討論:pe 節,相對虛擬位址(rva),資料目錄,函數的引入。 3. PE節 PE節以某鍾順序表示代碼或資料。代碼就是代碼了,但是卻有多種類型的資料,可讀寫的程式資料(如總體變數),其他的節包含API的引入引出表,資源,重定位。每個節有自己的屬性,包括是否是代碼節,是否唯讀還是可讀可寫,節的資料是否全局共用。 通常,節中的資料邏輯上是關聯的。PE檔一般至少要有兩個節,一個是代碼,另一個為資料。一般還有一個其他類型的資料的節。後面我將描述各種類型的節。 每個節都有一個獨特的名字。這個名字是用來傳達這個節的用途的。比如,.RDATA表示一個唯讀節,節的名字對於作業系統毫無意義,只是為了人們便於理解。把一個節命名為FOOBAR和.TEXT是一樣有用的。微軟給他們的節命名了個有特色的名字,但是這不是必需的。Borland的連接器用的是code和data 一般編譯器將產生一系列標準的節,但這沒有什麼不可思議的。你可以建立和命名自己的節,連接器會自動在程式檔中包含它們。在visual c++中,你能用#pragma指令讓編譯器插入資料到一個節中。像下面這樣: #pragma data_seg("MY_DATA") ...有必要初始化 #pragma data_seg() 你也可以對.data做同樣的事。大部分的程式都只用編譯器產生的節,但是有時候你卻需要這樣。比如建立一個全局共用節。 節並不是全部由連接器確定的,他們可以在編譯階段由編譯器放入obj檔。連接器的工作就是合併所有obj和庫中需要的節成一個最終的合適的節。比如,你的工程中的所有obj可能都有一個包含代碼的.text節,連接器把這些節合併成一個.text節。同樣對於.data等。這些主題超出了這篇文章的範圍了。還有更多的規則關於連接器的。在obj文件中是專門給linker用的,並不放入到pe檔中,這種節是用來給連接器傳遞資訊的。 節有兩個關於對齊的欄位,一個對應磁片檔,另一個對應記憶體中的檔。Pe檔頭指出了這兩個值,他們可以不一樣。每個節的偏移從對齊值的倍數開始。比如,典型的對齊值是0x200,那麼每個節的的偏移必須是0x200的倍數。一旦載入記憶體,節的起始位址總是以頁對齊。X86cpu的頁大小為4k,al-64為8k。 下麵是pedump傾印出的Windows XP KERNEL32.DLL.的.text .data節的信息: Section Table 01 .text VirtSize: 00074658 VirtAddr: 00001000 raw data offs: 00000400 raw data size: 00074800 ... 02 .data VirtSize: 000028CA VirtAddr: 00076000 raw data offs: 00074C00 raw data size: 00002400 建立一個節在檔中的偏移和它相對於載入位址的偏移相同的pe檔是可能的。在98/me中,這會加速大檔的載入。Visual studio 6.0 的默認選項 /opt:win98j就是這樣產生檔的。在Visual studio.net中是否用/opt:nowin98取決於檔是否夠小。 一個有趣的連接器特徵是合併節的能力。如果兩個節有相似相容的屬性,連接的時候就可以合併為一個節。這取決於是否用/merger開關。像下麵就把.rdata和.text合併為一個節.text /MERGE:.rdata=.text 合併節的優點就是對於磁片和記憶體節省空間。每個節至少佔用一頁記憶體,如果你可以把可執行檔的節數從4減到3,很可能就可以少用一頁記憶體。當然,這取決於兩個節的空餘空間加起來是否達到一頁。 當你合併節事情會變得有意思,因為這沒有什麼硬性和容易的規則。比如你可以合併.rdata到.text, 但是你不可以把.rsrc.reloc.pdata合併到別的節。先前Visual Studio .NET允許把.idata合併,後來又不允許了。但是當發行的時候,連接器還是可以把.idata合併到別的節。 因為引入節的一部分在載入器載入時將被寫入,你可能驚奇它是如何被放入一個唯讀節的。是這樣的,在載入的時候系統會臨時改變那些包含引入節的頁為可讀可寫,初始化完成後,又恢復原來屬性。 4. 相對虛擬位址 在可執行檔中,有很多地方需要指定記憶體位址,比如,引用總體變數時,需要指定它的位址。Pe檔儘管有一個首選的載入位址,但是他們可以載入到進程空間的任何地方,所以你不能依賴於pe的載入點。由於這點,必須有一個方法來指定位址而不依賴於pe載入點的地址。為了避免把記憶體位址硬編碼進pe檔,提出了RVA。RVA是一個簡單的相對於PE載入點的記憶體偏移。比如,PE載入點為0X400000,那麼代碼節中的地址0X401000的RVA為(target address) 0x401000 - (load address)0x400000 = (RVA)0x1000。把RVA加上PE的載入點的實際位址就可以把RVA轉化實際位址。順便說一下,按PE的說法,記憶體中的實際位址稱為VA(VIRTUAL ADDRESS).不要忘了早點我說的PE的載入點就是HMODULE。 想對探索記憶體中的任意DLL嗎?用GetModuleHanle(LPCTSTR)取得載入點,用你的PE知識來幹活吧 5. 資料目錄 PE檔中有很多資料結構需要快速定位。顯然的例子有引入函數,引出函數,資源,重定位。這些東西是以一致的方式來定位的,這就是資料目錄。 資料目錄是一個結構陣列,包含16個結構。每個元素有一個定義好的標識,如下: // Export Directory #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Import Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Security Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Debug Directory #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Description String #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // Machine value (MIPS GP) #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_TLS 9 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 typedef struct _IMAGE_DATA_DIRECTORY { ULONG VirtualAddress; ULONG Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; 6. 引入函數 當你使用別的DLL中的代碼或資料,稱為引入。當PE載入時,載入器的工作之一就是定位所有引入函數及資料,使那些位址對於載入的PE可見。具體細節在後面討論,在這裏只是大概講一下。 當你用到了一個DLL中的代碼或資料,你就暗中連接到這個DLL。但是你不必為“把這些位址變得對你的代碼有效”做任何事情,載入器為你做這些。方法之一就是顯式連接,這樣你就要確定DLL已被載入,及函數的位址。調用LOADLIBARY和GETPROCADDRESS就可以了。 當你暗式連接DLL,LOADLIBARY和GETPROCADDRESS同樣還是執行了的。只不過載入器為你做了這些。載入器還保證PE檔所需得任何附加的DLL都已被載入。比如,當你連接了KERNEL32.DLL,而它又引入了NTDLL.DLL的函數,又比如當你連接了GDI32.DLL,而它又依賴於USER32, ADVAPI32,NTDLL, 和 KERNEL32 DLLs的函數,載入器會保證這些DLL被載入及函數的決議。 暗式連接時,決議過程在PE檔在載入時就發生了。如果這時有什麼問題(比如這個DLL檔找不到),進程終止。 VISUAL C++ 6.0 加入了DLL的延遲載入的特徵。它是暗式連接和顯式連接的混合。當你延遲載入DLL,連接器做出一些和引入標準規則DLL類似的東西,但是作業系統卻不管這些東西,而是在第一次調用這個DLL中的函數的時候載入(如果還沒載入),然後調用GetProcAddress取得函數的位址。 對於pe檔要引入的dll都有一個對應的結構陣列,每個結構指出這個dll的名字及指向一個函數指標陣列的指標,這個函數指標陣列就是所謂的IAT(IMORT ADDRESS TABLE)。每個輸入函數,在IAT中都有一個保留槽,載入器將在那裏寫入真正的函數位址。最後特別重要一點的是:模組一旦載入,IAT中包含所要調用的引入函數的位址。 把所有輸入函數放在IAT一個地方是很有意義的,這樣無論代碼中多少次調用一個引入函數,都是通過IAT中的一個函數指標。 讓我們看看是怎樣調用一個引入函數的。有兩種情況需要考慮:有效率的和效率差的。最好的情況像下面這樣: CALL DWORD PTR [0x00405030] 直接調用[0x405030]中的函數,0x405030位於IAT部分。效率差的方式如下: CALL 0x0040100C ... 0x0040100C: JMP DWORD PTR [0x00405030] 這種情況,CALL把控制權轉到一個子程式,副程式中的JMP指令跳轉到位於IAT中的0x00405030,簡單說,它多用了5位元組和JMP多花的時間。 你可能驚訝引入函數就採用了這種方式,有個很好的解釋,編譯器無法區別引入函數的調用和普通函數調用,對於每個函數調用,編譯器只產生如下指令: CALL XXXXXXXX XXXXXXXX是一個由連接器填入的RVA。注意,這條指令不是通過函數指標來的,而是代碼中的實際地址。 為了因果的平衡,連接器必須產生一塊代碼來代替取代XXXXXXXX,簡單的方法就是象上面所示調用一個JMP STUB. 那麼JMP STUB 從那裏來呢?令人驚異的是,它取自輸入函數的引入庫。如果你去察看一個引入庫,在輸入函數名字的關聯處,你會發現與上面JMP STUB相似的指令。 接著,另一個問題就是如何優化這種形式,答案是你給編譯器的修飾符,__declspec(import) 修飾符告訴編譯器,這個函數來自另一個dll,這樣編譯器就會產生第一種指令。另外,編譯器將給函數加上__imp_首碼然後送給連接器決議,這樣可以直接把__imp_xxx送到iat,就不需要jmp stub了。 對於我們這有什麼意義呢,如果你在寫一個引出函數的東西並提供一個頭檔的話,別忘了在函數前加上修飾符__declspec(import) __declspec(dllimport) void Foo(void); 在winnt.h等系統頭檔中就是這樣做的。 7. PE 檔結構 現在讓我們開始研究PE檔格式,我將從檔的頭部開始,描述每個PE檔中都有的各種資料結構,然後,我將討論更多的專門的資料結構比如引入表和資源,除非特殊說明,這些結構都定義在WINNT.H中。 一般地,這些結構都有32和64位之分,如IMAGE_NT_HEADERS32 ,IMAGE_NT_HEADER64等,他們基本上是一樣的,除了64位的擴展了某些欄位。通過#DEFINE WINNT.H都遮罩了這些區別,選擇那個資料結構取決於你要如何編譯了(如,是否定義_WIN64) The MS-DOS Header 每個PE檔是以一個DOS程式開始的,這讓人想起WINDOWS在沒有如此可觀的使用者的早期年代。當可執行檔在非WINDOWS平臺上運行的時候至少可以顯示出一條資訊表示它需要WINDOWS。 PE檔的開頭是一個IMAGE_DOS_HEADER結構,結構中只有兩個重要的欄位e_magic and e_lfanew。e_lfanew指出pe file header的偏移,e_magic需要設定位0x5a4d,被#define 成IMAGE_DOS_SIGNATURE 它的ascii為’MZ’,Mark Zbikowski的首字母,DOS 的原始構建者之一。 The IMAGE_NT_HEADERS Header 這個結構是PE檔的主要定位資訊的所在。它的偏移由IMAGE_DOS_HEADER的e_lfanew給出 確實有64和32位之分,但我在討論中將不作考慮,他們幾乎沒有區別。 typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; 在一個有效的pe檔裏,Signture被設為0x00004500,ascii 為’PE00’,#define IMAGE_NT_SIGNTURE 0X00004500;第二個欄位是一個IMAGE_FILE_HEADER結構,它包含檔的基本資訊,特別重要的是它指出了IMAGE_OPTIONAL_HEADER的大小(重要嗎?);在PE文件中,IMAGE_OPTIONAL_HEADER是非常重要的,但是仍稱作IMAGE_OPTIONAL_HEADER。 IMAGE_OPTIONAL_HEADER結構的末尾就是用來定位pe檔中重要資訊的位址簿-資料目錄,它的定義如下: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // RVA of the data DWORD Size; // Size of the data }; The Section Table 緊接著IMAGE_NT_HEADERS後的就是節表,節表就是IMAGE_SECTION_HEADER的陣列。IMAGE_SECTION_HEADER包含了它所關聯的節的資訊,如位置,長度,特徵;該陣列的數目由IMAGE_NT_HEADERS.FileHeader.NumberOfSections指出。具體見下圖 PE中的節的大小的總和最後是要對齊的,Visual Studio 6.0中的預設值是4k,除非你使用/OPT:NOWIN98 或/ALIGN開關;在.NET中,依然用了默認的/OPT:WIN98,但是如果檔小於一特定大小時,就會採用0X200為對齊值。 .NET文檔中有關於對齊的另一件有趣的事。.NET檔的記憶體對齊值為8K而不是普通X86平臺上的4K,這樣就保證了在X86平臺編譯的程式可以在IA-64平臺上運行。如果記憶體對齊值為4K,那麼IA-64的載入器就不能載入這個程式,因為它的頁為8K
posted @
2008-07-28 04:04 幽幽 阅读(680) |
评论 (0) |
编辑 收藏
在看yoda's Protector源代码的时候,发现
const DWORD ALIGN_CORRECTION =0x1000;// this big value is e.g. needed for WATCOM compiled files
上网一查,发现WATCOM竟然有这样一段传奇:
一、Watcom的发展史
在编译器混战的时代,一家加拿大的小公司出品了Watcom C/C++编译器,但是以在DOS下能够产生最佳化程序代码闻名于世的,许多写游戏和DOS Extender的厂商都指名要使用Watcom C/C++,因为不论是Borland C/C++还是Visual C/C++,它们产生的最佳化程序代码都比Watcom C/C++的最佳化程序代码差上一截。再加上当时最有名的DOS Extender厂商PharLap公司也是使用Watcom C/C++,因此Watcom C/C++在当时专业的C/C++程序员以及系统程序员心中是第一品牌的C/C++开发工具。
Watcom C/C++在DOS市场站稳了脚跟之后,由于Windows已经逐渐成为市场的主流,DOS势必将被逐渐淘汰出局,因此,Watcom C/C++如果要继续生存下去,也就一定要推出Windows平台的C/C++开发工具。大约是在1993、1994年左右,Watcom终于推出第一个Windows下的C/C++开发工具。
不过,当时Watcom C/C++在Windows推出的C/C++开发工具实在是平淡无奇。其集成开发环境和另外三个对手比较起来简直像是远古的产品,一点特色都没有。不过Watcom C/C++仍然是以它的最佳化编译器作为号召。因此当时发生了一个非常有趣的现象,那就是许多软件公司会同时买Borland C/C++,或是Visual C/C++,Symantec C/C++之一,再搭配一套Watcom C/C++。在开发应用系统时使用其他三套开发工具之一,最后要出货时再使用Watcom C/C++来编译以产生最佳的程序代码。
在Watcom C/C++推出了Windows平台的开发工具之后,也吸引了一群使用者。虽然Watcom C/C++的市场比起其他的三家来说是最小的,但是总算撑起了一片天,成为四大C/C++开发工具之一。稍后Watcom C/C++被Sybase并购,成为Sybase的Optima++的前身。
二、石破天惊还是巨星陨落
1996年左右,Sybase并购了Watcom之后终于推出了石破天惊的C/C++开发工具:Optima++。Optima++是当初结合了Watcom的最佳化编译器以及类似Delphi的组件拖曳开发环境的第一个RAD C/C++开发工具。更棒的是Optima++的组件架构(类似Delphi的VCL)完全是以纯正的C/C++程序代码撰写的。这可不得了,因为这代表Optima++是一个融合了Visual C/C++和Delphi两大王者开发工具为一身的超级赛亚人工具。
在我(《Borland传奇》作者李维,下同)知道这个工具、并且尝试实际使用之后,极为震惊。因为对于我这个使用了C/C++ 五六年的人来说,它比Delphi更具有吸引力。因此我立刻在《RUN!PC》上介绍了这个不可置信的工具。果然,Optima++很快开始风靡市场,虽然没有立刻占据很大的市场份额,但是已经造成了一股气势,开始为Visual C/C++和Delphi带来压力。
我记得当时台湾Sybase办的产品发表会也吸引了数百人与会,不可一世。我的文章在《RUN!PC》6上发表之后,台湾的Sybase立刻和我联络,由当时的余协理和我见面,也是希望我继续为Optima++写文章,台湾Sybase也提供额外一字加2元稿费的待遇。但是我告诉余协理,Optima++ 1.0虽然很棒,但是仍然有一些臭虫,而且和中文环境相冲突,无法处理中文,需要立刻解决这个问题才能够在台湾的市场成功。她答应我立刻向总公司反映。我也老实地告诉她,在问题没有解决之前,我无法写一些不确实的东西。后来台湾Borland的总经理方先生也找我去询问有关Optima++的事情,我告诉他Optima++是好东西,但是中文有问题。如果中文问题能够解决,那么将对Borland和Microsoft的产品有很大的影响,当时我还不知道Borland由于Optima++的影响,已经开始准备开发C++ Builder。
在1996年底左右吧,Optima++ 1.5终于进入Beta的阶段。但是在我拿到Beta版时非常失望,因为中文的问题仍然没有解决。后来台湾Sybase又找我去,这次和我见面的是台湾Sybase总经理郭俊男先生,以及Sybase的新加坡技术总裁,不过我忘记这位先生的名字了。见了面之后,我立刻把Optima++ 1.5中文的问题以及许多的臭虫告诉他们,希望他们能够解决,如此Optima++ 1.5才能够在中文市场成功。可是出乎我意料之外的是,他们似乎并不着急这些问题,反而询问我是否有意愿为Sybase工作,做PowerBuilder的产品经理。
也许是因为我为Delphi写了太多的东西,让PowerBuilder在台湾受了很大的影响,因此他们希望我到Sybase工作,以打击Delphi并且Promote PowerBuilder。当时他们提出的待遇条件实在是非常、非常的诱人,比我当时的薪水高出一倍左右(我当时在资策会工作)。不过由于我对PowerBuilder实在没有什么兴趣,因此我告诉他们,如果是做Optima++的产品经理,那么我将会考虑并且接受。
没有想到,Sybase的新加坡技术总裁告诉我Optima++在1.5推出之后就可能会停止,因为Sybase要把资源移去为当时愈来愈红的Java研发一个新的Java RAD开发工具,那就是后来的PowerJ。于是他询问我如果不愿意做PowerBuilder的产品经理,那么是不是愿意做PowerJ的产品经理?由于当时我已经知道Borland开始了Open JBuilder的研发,而我对Open JBuilder的兴趣远大于PowerJ,因此没有答应Sybase。果然,在Optima++ 1.5推出之后,不但中文的问题没有解决,Sybase之后也没有继续对Optima++研发下去。
Optima++一个如此有潜力的产品就这样消失了,真是令人遗憾。Optima++应该有很好的机会可以成功。我相信,如果当时Sybase知道C++ Builder后来的成果,可能就不会放弃Optima++了,而C/C++的RAD工具一直要到后来的C++ Builder来完成这个梦。
至此,和Visual C/C++竞争的只有Borland的编译器了,然而虽然后来Borland继续推出了Borland C/C++ 5.0,但是品质仍然不够好,市场反应也不佳。后来终于在Borland C/C++ 5.02之后宣布停止此条产品线的开发,Borland C/C++的光荣历史也就从此打住,真是令人不胜感叹,而Visual C/C++从此在C/C++开发工具市场中再也没有对手。不过没有竞争的市场的确会让人松懈,后来的Visual C/C++进步的幅度愈来愈小,MFC也数年没有什么大进步,不像当时和Borland C/C++竞争时每一个版本都有大幅的改善。看来寡占的市场的确是不好的,这也让人回想起Visual C/C++、Borland C/C++、Symantec C/C++、Watcom C/C++四雄逐鹿的辉煌时代了。
三、开源潮流
Watcom C/C++产生目标程序的质量还是非常让人难忘的,这也是不少程序员(尤其是游戏程序员)青睐于这个编译器的原因,这也促成了OpenWatcom C/C++的诞生,免费、开源,也希望很多的人使用,最新版支持C/C++/Fortran的编译。
posted @
2008-07-26 11:52 幽幽 阅读(2070) |
评论 (0) |
编辑 收藏