主题:含笑不语:异型窗体代码逐句详解,请高手指教点评,菜鸟分享
含笑不语
[专家分:50] 发布于 2008-01-06 19:35:00
在自学VB过程中,我遇到许多困难,很多教材语焉不详,让身为菜鸟的我吃够了苦头!特发此帖,作为我的VB自学系列笔记之一,希望能给广大VB菜鸟同仁带来帮助!
------------------含笑不语 2008-1-6
特别鸣谢 btxdlibin大大 tanchuhan大大 merry05大大
实例1 异型窗体
1.建Form1,添加2个CommandButton,Caption1="外边为圆角矩形,里边为椭圆透明洞的窗体" Caption2="中间有椭圆透明洞的窗体"
添加From2, Caption="中间有椭圆透明洞的窗体" , BackColor=&H00FFFF80&
2.Form1代码:
Option Explicit '(注释1)
Private Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long '(注释2) 用API函数 CreateRectRgn 创建剪裁区域,用来在上边建立各种异型窗体
Private Declare Function CreateRoundRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 As Long, ByVal Y3 As Long) As Long'(注释3)用API函数 CreateRoundRectRgn创建外边的圆 角矩形
Private Declare Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long '(注释4) 用API函数 CreateEllipticRgn 创建里边的椭圆
Private Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long'(注释5) 用API函数 CombineRgn 将两个区域组合在一起
Private Declare Function SetWindowRgn Lib "user32" (ByVal hWnd As Long, ByVal hRgn As Long, ByVal bRedraw As long) As Long'(注释6) 用API函数 SetWindowRgn 将组合在一起的区域创建为新区域
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long'(注释7) 删除创建的新区域
Private Const RGN_DIFF = 4'(注释8)用Const将RGN_DIFF声明为常数4,即将两个区域相减
Private OutRgn As Long '声明变量外边圆角矩形OutRgn的数据类型为Long(长整型)
Private InRgn As Long '声明变量里边椭圆InRgn的数据类型为Long(长整型)
Private MyRgn As Long '声明变量创建的新区域MyRgn的数据类型为Long(长整型)
Private Sub Command1_Click()
If OutRgn <> 0 And InRgn <> 0 And MyRgn <> 0 Then Exit Sub ' (注释9)防止重复创建图形
Dim w As Long, h As Long '声明变量w,h的数据类型为Long(长整型)
w = ScaleX(Form1.Width, vbTwips, vbPixels) ' (注释10) 将Form1的窗体宽度由缇变为像素值后赋给变量W
h = ScaleY(Form1.Height, vbTwips, vbPixels) '将Form1的窗体高度由缇变为像素值后赋给变量h
MyRgn = CreateRectRgn(0, 0, 0, 0) '用API函数 CreateRectRgn 创建剪裁区域MyRgn
OutRgn = CreateRoundRectRgn(30, 30, w - 30, h - 30, 100, 100) '用API函数 CreateRoundRectRgn 创建圆角矩形OutRgn
InRgn = CreateEllipticRgn(100, 100, w - 100, h - 100) '用API函数 CreateEllipticRgn 创建椭圆InRgn
Call CombineRgn(MyRgn, OutRgn, InRgn, RGN_DIFF) '(注释11)将圆角矩形区域OutRgn中不属于椭圆InRgn的区域合并到MyRgn
Call SetWindowRgn(Form1.hWnd, MyRgn, True) '在Form1中以MyRgn为句柄创建新区域
Form1.BackColor = QBColor(4) '(注释12)将Form1的背景色设置为红色
End Sub
Private Sub Command2_Click()
Form2.Show '显示Form2
End Sub
Private Sub Form_Load()
OutRgn = 0 '加载窗体Form1时将区域OutRgn设置为0
InRgn = 0 '加载窗体Form1时将区域InRgn设置为0
MyRgn = 0 '加载窗体Form1时将区域MyRgn 设置为0
End Sub
Private Sub Form_Unload(Cancel As Integer)
If MyRgn <> 0 Then DeleteObject MyRgn '卸载窗体Form1时如果区域MyRgn不为0则删除MyRgn
If OutRgn <> 0 Then DeleteObject OutRgn '卸载窗体Form1时如果区域OutRgn不为0则删除OutRgn
If InRgn <> 0 Then DeleteObject InRgn '卸载窗体Form1时如果区域InRgn不为0则删除InRgn
End Sub
Form2代码:
Option Explicit'(注释1)
Private Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long'(注释2) 用API函数 CreateRectRgn 创建剪裁区域,用来在上边建立各种异型窗体
Private Declare Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long,ByVal Y2 As Long) As Long'(注释4) 用API函数 CreateEllipticRgn 创建里边的椭圆
Private Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long'(注释5) 用API函数 CombineRgn 将两个区域组合在一起
Private Declare Function SetWindowRgn Lib "user32" (ByVal hWnd As Long, ByVal hRgn As Long, ByVal bRedraw As Long) As Long'(注释6) 用API函数 SetWindowRgn 将组合在一起的区域创建为新区域
Private Sub Form_Resize() '(注释13)用Form_Resize()事件设置控件的属性
Const RGN_DIFF = 4 '(注释8)用Const将RGN_DIFF声明为常数4,即将两个区域相减
Dim outer_rgn As Long '声明变量outer_rgn为Long型(长整型)
Dim inner_rgn As Long '声明变量inner_rgn为Long型(长整型)
Dim combined_rgn As Long '声明变量combined_rgn为Long型(长整型)
Dim wid As Single '声明变量wid为Single型(单精度浮点数)
Dim hgt As Single '声明变量hgt为Single型(单精度浮点数)
Dim border_width As Single '声明变量border_width为Single型(单精度浮点数)
Dim title_height As Single '声明变量title_height为Single型(单精度浮点数)
If WindowState = vbMinimized Then Exit Sub '(注释14)如果窗口最小化那么就退出.(窗口最小化的话,就没有坐标来创建图形了)
Me.ScaleMode = 3 '将Form2的ScaleMode属性设置为3,否则的话,Form2有可能编译通不过!
wid = ScaleX(Width, vbTwips, vbPixels) ' (注释10) 将Form2的窗体宽度由缇变为像素值后赋给变量wid
hgt = ScaleY(Height, vbTwips, vbPixels) ' (注释10) 将Form2的窗体高度由缇变为像素值后赋给变量 hgt
outer_rgn = CreateRectRgn(0, 0, wid, hgt) '(注释2) 用API函数 CreateRectRgn 创建外边的矩形窗体
border_width = (wid - ScaleWidth) / 2 '(注释15)计算窗体Form2边框宽度
title_height = hgt - border_width - ScaleHeight '(注释16)计算窗体Form2标题栏高度
inner_rgn = CreateEllipticRgn(border_width + ScaleWidth * 0.1, title_height + ScaleHeight * 0.1, ScaleWidth * 0.9, ScaleHeight * 0.9) '(注释17) 用 API函数 CreateEllipticRgn 创建里边的椭圆
combined_rgn = CreateRectRgn(0, 0, 0, 0) '(注释2) 用API函数 CreateRectRgn 创建剪裁区域,用来在上边建立各种异型窗体
CombineRgn combined_rgn, outer_rgn,inner_rgn, RGN_DIFF '(注释11)将矩形区域outer_rgn中不属于椭圆inner_rgn的区域合并到combined_rgn中
SetWindowRgn hWnd, combined_rgn, True '在Form2中以combined_rgn为句柄创建新区域
End Sub
回复列表 (共37个回复)
沙发
含笑不语 [专家分:50] 发布于 2008-01-06 19:40:00
注释1: Option Explicit 语句
用于在文件级(即一段完整的程序代码中)强制对该文件中的所有变量进行显式声明。
Option Explicit { On | Off }
On
可选。启用 Option Explicit 检查。如果在 Option Explicit 语句后没有指定 On 或 Off,则默认为 On。
Off
可选。禁用 Option Explicit 检查。
备注
如果使用,则 Option Explicit 语句必须出现在文件中其他所有源语句之前。
当 Option Explicit 出现在文件中时,必须使用 Dim、Private、Public 或 ReDim 语句显式声明所有变量。试图使用未声明的变量名将发生编译时错误。
如果没有使用 Option Explicit 语句,则所有未声明的变量都是 Object 类型。
注意 使用 Option Explicit 可避免拼错现有变量的名称,或避免在变量范围不清楚的代码中产生混淆。如果代码中没有指定 Option Explicit,编译器的默认设置将是 Option Explicit On。
补充:强制声明,因为VB的变量是可以不声明而直接使用的,但是这样可能会造成一些莫名奇妙的隐含错误,还很难排查,用了这个语句后所有的变量都要遵循先声明后引用的规矩,这样就避免了一些不必要的麻烦,这是一个良好的编码习惯。
板凳
含笑不语 [专家分:50] 发布于 2008-01-06 19:45:00
注释2: Private Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
1). vb 中declare的使用方法
VB里的说明语句(Declare),仅支持动态链结库(DLL)的子程序和无变量函数。
VB程序要使用DLL中的函数,首先必须要有特殊的声明,用Declare声明语句在窗体级或模块级或全局模块的代码声明段进行声明,将动态链接库中的函数声明到VB中,供VB程序调用。
语句格式为:Declare Sub 过程名Lib [ Alias " 别名]([ByVal 参数AS类型]),或为Declare Function函数名Lib [Alias " 别名]([ByVal 参数AS类型])AS类型 在声明中首先用Declare关键字表示声明DLL中的函数。
2).Funtion 过程
Visual Basic 包含内置的、或内部的函数,如 Sqr、Cos 或 Chr。此外,还可用 Function 语句编写自己的 Function 过程。
函数过程的语法是:
Private |Public] [Static] Function procedurename (arguments) [As type]
statements
End Function
与 Sub 过程一样,Function 过程也是一个独立的过程,可读取参数、执行一系列语句并改变其参数的值。与子过程不同,Function 过程可返回一个值到调用的过程。在Sub 过程与Function 过程之间有三点区别:
1.一般说来,让较大的语句或表达式的右边包含function过程名和参数 (returnvalue = function),这就调用了function函数。
2.function过程与变量完全一样,函数过程有数据类型。这就决定了返回值的类型。(如果没有 As 子句,缺省的数据类型为 Variant。)
3.给procedurename(Function 过程名)自身赋一个值,就可返回这个值。Function 过程返回一个值时,该值可成为较大表达式的一部分。
例如,下面是已知直角三角形两直角边的值,计算第三边(斜边)的函数:
Function Hypotenuse (A As Integer, B As Integer) As String
Hypotenuse = Sqr (A ^ 2 + B ^ 2)
End Function
在 Visual Basic 中调用 Function 过程的方法和调用任何内部函数的方法是一样的
sub 执行完程序代码后就直接返回了,不会带回数值。而function执行完代码后可以返回一个值,用来返回一个计算结果等数字。
例如:
function sum(a,b)
sum=a+n
end function
可以调用为:
a=1
b=2
c=sum(a,b)
运行后c=3
而sub则不能用“变量=程序名 ”来调用。
补充:
Sub 过程
子过程是在响应事件时执行的代码块。将模块中的代码分成子过程后,在应用程序中查找和修改代码变得更容易了。
子过程的语法是:
[Private|Public][Static]Sub procedurename (arguments)
statements
End Sub
每次调用过程都会执行 Sub 和 End Sub 之间的 statements。可以将子过程放入标准模块、类模块和窗体模块中。按照缺省规定,所有模块中的子过程为 Public(公用的),这意味着在应用程序中可随处调用它们。
过程的 arguments 类似于变量声明,它声明了从调用过程传递进来的值。
在 Visual Basic 中应区分通用过程和事件过程这两类子过程。
1.通用过程
通用过程告诉应用程序如何完成一项指定的任务。一旦确定了通用过程,就必须专由应用程序来调用。反之,直到为响应用户引发的事件或系统引发的事件而调用事件过程时,事件过程通常总是处于空闲状态。
为什么要建立通用过程呢?理由之一就是,几个不同的事件过程也许要执行同样的动作。将公共语句放入一分离开的过程(通用过程)并由事件过程来调用它,诚为编程上策。这样一来就不必重复代码,也容易维护应用程序。例如,VCR 示例应用程序使用了一个通用过程,几个不同滚动按钮的 Click 事件都调用这个通用过程。图 5.7 说明了通用过程的使用。Click 事件中的代码调用按钮管理器的子过程,子过程运行自身的代码,然后将控制返回到 Click 事件过程。
2.事件过程
当 Visual Basic 中的对象对一个事件的发生作出认定时,便自动用相应于事件的名字调用该事件的过程。因为名字在对象和代码之间建立了联系,所以说事件过程是附加在窗体和控件上的。
一个控件的事件过程将控件的(在 Name 属性中规定的)实际名字、下划线 (_) 和事件名组合起来。例如,如果希望在单击了一个名为 cmdPlay 的命令按钮之后,这个按钮会调用事件过程,则要使用 cmdPlay_Click 过程。
一个窗体事件过程将词汇 "Form"、下划线和事件名组合起来。如果希望在单击窗体之后,窗体会调用事件过程,则要使用 Form_Click 过程。(和控件一样,窗体也有唯一的名字,但不能在事件过程的名字中使用这些名字。)如果正在使用 MDI 窗体,则事件过程将词汇 " MDIForm " 、下划线和事件名组合起来,如 MDIForm_Load。
所有的事件过程使用相同的语法。
控件事件的语法 窗体事件的语法
Private Sub controlname_eventname (arguments )
statements
End Sub
Private Sub Form_eventname (arguments)
statements
End Sub
虽然可以自己编写事件过程,但使用 Visual Basic 提供的代码过程会更方便,这个过程自动将正确的过程名包括进来。从“对象框”中选择一个对象,从“过程框”中选择一个过程,就可在“代码编辑器” 窗口选择一个模板。
在开始为控件编写事件过程之前先设置控件的 Name 属性,这不失为一个好主意。如果对控件附加一个过程之后又更改控件的名字,那么也必须更改过程的名字,以符合控件的新名字。否则,Visual Basic 无法使控件和过程相符。过程名与控件名不符时,过程就成为通用过程。
****最最根本的区别不是sub没有返回值,而function有。
而是
sub调用,没有堆栈的开销。而function却有
sub调用,调用前,不用压入栈,退出后也不用弹出栈
而function则不然
3 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:46:00
补充:什么是堆栈
在计算机领域,堆栈是一个不容忽视的概念,但是很多人甚至是计算机专业的人也没有明确堆栈其实是两种数据结构。
要点:
堆:顺序随意
栈:先进后出
堆和栈的区别
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放 。
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
}
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
堆和栈的理论知识
1.申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。
2.申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
3.申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
4.申请效率的比较
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活
5.堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
6.存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。
7.小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
堆和栈的区别主要分:
操作系统方面的堆和栈,如上面说的那些,不多说了。
还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。
虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因。
4 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:49:00
3).CreateRectRgn VB声明
'创建一个矩形区域
Declare Function CreateRectRgn Lib "gdi32" Alias "CreateRectRgn" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
说明
创建一个由点X1,Y1和X2,Y2描述的矩形区域
返回值
Long,执行成功为区域句柄,失败则为零
参数表
参数 类型 说明
X1,Y1 Long, 矩形左上角X,Y坐标
X2,Y2 Long, 矩形右下角X,Y坐标
注解
不用时一定要用DeleteObject函数删除该区域
这个矩形的下边和右边不包含在区域之内
CreateRectRgn()的作用是创建一个矩形区域,以你传入参数的矩形位置创建一个区域对象
CreateRectRgn()用来创建一个“剪裁区域”;至于剪裁区域,它是对显示器上一个范围的描述,这个范围是矩形、多边形和椭圆的组合。剪裁区域一般用来绘制和剪裁,通过将剪裁区域选进设备上下文,就可以用剪裁区域来进行剪裁(就是说,将可以绘图的范围限制为显示区域的一部分)。它是GDI对象。在GDI中当函数名称中包含Rgn字符时,一般表示此函数与剪裁区域有关。
5 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:51:00
4).关于LIB和DLL
dll 是 编译后 生成的 动态连接库, 而 lib 是在 程序编译时 会用到的 类库文件
1.什么是动态链接库?
DLL是Dynamic Link Library 的缩写形式,动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。
动态链接库是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源。由于DLL代码使用了内存共享技术,在某些地方windows也给了DLL一些更高的权限,因而DLL中可以实现一些一般程序所不能实现的功能,如实现windows的HOOK、ISAPI等。
同时,DLL还为不同语言间代码共享提供了一条方便的途径。因而DLL在编程时应用较为广泛,本文将介绍如何在 Delphi 中建立和使用DLL。
一.DLL 库内存共享机制
从使用效果看,DLL和unit 很像,它们都可以被别的工程模块所调用,但二者在内部的实现机制上确存在着差别。如果一个程序模块中用uses语句引用了某个unit,编译程序在编译该模块时,便会连同unit一起编译,并把编译后的可执行代码链接到本程序模块中,这就是一个程序模块能够调用所引用unit中过程和函数的原因。
当同一个unit被多个工程所引用时,则每个工程中都含有该unit的可执行代码,当含有该unit的多个工程同时执行时,unit的可执行代码会随不同工程而多次被调入内存,造成内存资源的浪费。DLL则不同,它即使被某个工程调用,编译后仍是独立的。
也就是说编译后,一个DLL库形成一个单独的可执行文件,而不与任何其它的可执行文件连接在一起,因而DLL库并不从属于某个特定的工程,当多个工程调用同一个DLL库时只有第一个工程把DLL库调入内存,其余工程并不重复调入同一个DLL库到内存,而是到同一个共享内存区读取。并且,DLL的执行代码是在程序运行期间动态调入的,而不是如unit在程序运行时就与整个工程一起调入内存。这样便可消除unit带来的相同代码多处占用内存的弊病。
二 Delphi中DLL库的建立
在Delphi环境中,编写一个DLL同编写一个一般的应用程序并没有太大的区别。事实上作为DLL主体的DLL函数的编写,除了在内存、资源的管理上有所不同外,并不需要其它特别的手段。
一般工程文件的格式为:
program 工程标题;
uses 子句;
程序体
而DLLs工程文件的格式为:
library 工程标题;
uses 子句;
exprots 子句;
绦蛱?
它们主要的区别有两点:
1.一般工程文件的头标用program关键字,而DLL工程文件头标用library 关键字。不同的关键字通知编译器生成不同的可执行文件。用program关键字生成的是.exe文件,而用library关键字生成的是.dll文件;
2.假如DLL要输出供其它应用程序使用的函数或过程,则必须将这些函数或过程列在exports子句中。而这些函数或过程本身必须用export编译指令进行编译。 在Delphi主菜单file 中选new...项,在弹出的窗口中双击DLL图标,便会自动给出DLL源模块框架,如下:
Library project1;
{...注释...}
uses
SysUtils, Classes;
begin
end.
接下来便可在USES和begin之间加入想在该DLL中实现的过程和函数的定义,并用export和exprots保字把它们引出,以便别的模块引用,在begin和end之间加入初始化代码,初始化代码是用来对DLL变量初始化的。应注意,即便无初始化代码begin与end也不可省略,如下例:
library minmax;
function Min(X, Y: Integer): Integer; export;
begin
if X < Y then Min := X else Min := Y;
end;
function Max(X, Y: Integer): Integer; export;
begin
if X > Y then Max := X else Max := Y;
end;
exports
Min index 1,
Max index 2;
begin
end.
经编译后,并以minmax.DLL存盘后,一个DLL库文件便形成了。
三 DLL库的访问
访问DLL库有两种方式,一种是静态引用,另一种是动态引用。
用静态引用这种方法装入DLL要做两件事情:为DLL 库创建一个输入单元,以及用USES把输入单元连接到要使用DLL 函数的程序模块中。为DLL库创建的输入单元与普通的单元的区别仅在于:在它的接口处声明的过程、函数,并不在它的实现部分给出真正的实现代码,而是用external关键字把过程、函数的实现细节委托给外部DLL模块。
external命令的使用语法如下:
procedure /function 过程/函数名;external DLL模块名;
下面给出为上面创建的minmax.DLL库写的输入单元源文件testdll .pas,从中可看出输入单元与一般单元的一些差别,代码如下所示:
unit testdll;
interface
uses
function Min (X, Y: Integer): Integer;
function Max (X, Y: Integer): Integer;
implementation
function Min; external ‘minmax.DLL’;
function Max; external ‘minmax.DLL’;
end.
一个应用程序若想调用minmax.DLL中的函数,只须在其uses语句中加入testdll 单元即可。
动态装入DLL,要用到Windows的三个API函数。Loadlibrary、Freelibrary和GetprocAddress 。 loadlibrary函数用来装入DLL库,其调用格式如下:
function loadlobrary (DLLfileName:Pchar): THandle:
当不再需要一个DLL库时,应调用FreeLibrary函数将其释放,以空出宝贵的内存资源,其调用格式如下:
procedure FreeLibrary (Libmodule:THandle)
Libmodule 为由LoadLibrary调用得到的DLL库句柄。在用loadlobrary 函数装入某个DLL库和调用FreeLibrary释放该DLL库之间的程序段中, 可以使用该DLL库中的过程和函数,具体使用方法是:用GetprocAddress函数把DLL库中函数的地址传递给程序中某个函数变量,再用该变量实现DLL函数的调用。GetprocAddress函数声名如下
function GetprocAddress (Libmodule:THandle:procname:pchar):TFarProc:
如下例所示:
type
TTimeRec = record
Second: Integer;
Minute: Integer;
Hour: Integer;
end;
TGetTime = procedure(var Time: TTimeRec);
THandle = Integer;
var
Time: TTimeRec;
Handle: THandle;
GetTime: TGetTime;
...
begin
Handle := LoadLibrary('DATETIME.DLL');
if Handle <> 0 then
begin
@GetTime := GetProcAddress(Handle, 'GetTime');
if @GetTime <> nil then
begin
GetTime(Time);
with Time do
WriteLn('The time is ', Hour, ':', Minute, ':', Second);
end;
FreeLibrary(Handle);
end;
end;
在调用动态链接库时应注意, 所需动态链接库须与应用程序在同一目录或Windows System 目录下。
动态链接库是 Windows下程序组织的一种重要方式,使用动态链接库可以极大地保护用户在不同开发工具、不同时期所做的工作,提高编程效率。
6 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:52:00
2.什么是动态连接库文件?有什么用?
计算机语言 就是写程序语言
跟我们说话一样 由一句一句完整的意思来构成
拿C来说 每一段能够独立执行的语句 叫做函数
动态链接库 就包含了很多函数
但是这些函数 不能够独立运行
需要exe文件中的函数 来调用它
dll文件整合了 一个函数集 这个函数集 赋给它一定的参数 就能独立的完成一项任务
在编程中 这样的东西 叫模块
根据dll的特性
它包含了 一定的自己的属性和事件动作
所以 又属于类模块
执行效率比较高
为什么要做成dll文件呢?
因为这样便于 扩展和移植
打个比方 一个类(.dll)它的作用是查询时间
所以不管什么程序 只要用到查询时间这个功能了
都可以调用这个查询时间功能的类
只要接口 和调用的函数名,参数正确就可以挂接
因此不必在每个程序里都写上查询时间的代码增加程序本身的长度
3.什么是静态链接库?
静态链接库就是.lib文件,库中得代码最后需要连接到你的可执行文件中去,所以静态连接的可执行文件一般比较大一些。 如果你使用VC,可以在Project Setting-->Link中加入你的静态库,也可以直接把该.lib文件加入到你的工程中。
在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB),Visual C++的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。
4.静态库和动态库的区别?
静态连接库,在编译以后包含在可执行文件中,不会以单独文件的形式存在.动态连接库是以单独文件的形式存在,被程序外部调用,动态连接库的好处就是可以多个进程访问一个动态连接库,并且共享一块内存.静态则包含在程序中,不能被外部调用!
简单地讲,静态库程序很长,但可以独立运行,动态库程序很短,但需要其它一些动态库存在才能进行工作。 需要注意的是,就某个动态库来说,它也有静态库版本和动态库版本,其静态库版本可以独立工作,但其动态库版本不能独立工作,仍然需要其它动态库的支持。
5.静态调用方式和动态调用方式
1)、静态调用方式:由编译系统完成对 DLL 的加载和应用程序结束时 DLL 卸载的编码(如还有其它程序使用该 DLL,则 Windows 对 DLL 的应用记录减1,直到所有相关程序都结束对该 DLL 的使用时才释放它,简单实用,但不够灵活,只能满足一般要求。
隐式的调用:需要把产生动态连接库时产生的 .LIB 文件加入到应用程序的工程中,想使用 DLL 中的函数时,只须说明一下。隐式调用不需要调用 LoadLibrary() 和 FreeLibrary()。程序员在建立一个 DLL 文件时,链接程序会自动生成一个与之对应的 LIB 导入文件。该文件包含了每一个 DLL 导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB 文件作为 DLL 的替代文件被编译到应用程序项目中。
当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与 LIB 文件中导出符号相匹配,这些符号或标识号进入到生成的 EXE 文件中。LIB 文件中也包含了对应的 DL L文件名(但不是完全的路径名),链接程序将其存储在 EXE 文件内部。
当应用程序运行过程中需要加载 DLL 文件时,Windows 根据这些信息发现并加载 DLL,然后通过符号名或标识号实现对 DLL 函数的动态链接。所有被应用程序调用的 DLL 文件都会在应用程序 EXE 文件加载时被加载在到内存中。可执行程序链接到一个包含 DLL 输出函数信息的输入库文件(.LIB文件)。操作系统在加载使用可执行程序时加载 DLL。可执行程序直接通过函数名调用 DLL 的输出函数,调用方法和程序内部其 它的函数是一样的。
2)、动态调用方式:是由编程者用 API 函数加载和卸载 DLL 来达到调用 DLL 的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。
显式的调用:是指在应用程序中用 LoadLibrary 或 MFC 提供的 AfxLoadLibrary 显式的将自己所做的动态连接库调进来,动态连接库的文件名即是上面两个函数的参数,再用 GetProcAddress() 获取想要引入的函数。自此,你就可以象使用如同本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用 FreeLibrary 或 MFC 提供的 AfxFreeLibrary 释放动态连接库。直接调用 Win32 的 LoadLibary 函数,并指定 DLL 的路径作为参数。LoadLibary 返回 HINSTANCE 参数,应用程序在调用 GetProcAddress 函数时使用这一参数。GetProcAddress 函数将符号名或标识号转换为 DLL 内部的地址。程序员可以决定 DLL 文件何时加载或不加载,显式链接在运行时决定加载哪个 DLL 文件。使用 DLL 的程序在使用之前必须加载(LoadLibrary)加载DLL从而得到一个DLL模块的句柄,然后调用 GetProcAddress 函数得到输出函数的指针,在退出之前必须卸载DLL(FreeLibrary)。
6.什么是类?什么是类库?
两个概念存在于面向对象的语言:
类,顾名思义,就是一些拥有相同属性的数据的集合,和现实中的人类,动物类相似.有类的概念相关的还有父类,子类,类的继承等.所谓父类和子类的关系相当于人和中国人的关系.类中有属性和方法,这两个也是重要的概念,上述三个概念(父类,子类,类的继承)都是建立在它的基础上的.
理解了类,类库就很简单了,就是类的集合.
以下是比较专业的解释
类(Class)实际上是对某种类型的对象定义变量和方法的原型。
类是对某个对象的定义。它包含有关对象动作方式的信息,包括它的名称、方法、属性和事件。实际上它本身并不是对象,因为它不存在于内存中。当引用类的代码运行时,类的一个新的实例,即对象,就在内存中创建了。虽然只有一个类,但能从这个类在内存中创建多个相同类型的对象。
可以把类看作“理论上”的对象,也就是说,它为对象提供蓝图,但在内存中并不存在。从这个蓝图可以创建任何数量的对象。从类创建的所有对象都有相同的成员:属性、方法和事件。但是,每个对象都象一个独立的实体一样动作。例如,一个对象的属性可以设置成与同类型的其他对象不同的值。
Microsoft(r) Visual Basic(r) for Applications (VBA) 工程中可包含两种不同类型的类模块:没有任何一种与其关联的用户界面的基本类模块;与窗体或其它组件关联的类模块。例如,与窗体相关联的类模块,它们和基本类模块相同,只是它们只有在那个窗体存在于内存中时,才存在于内存中。拥有相关联的类模块的对象的示例是“用户窗体”、Microsoft(r) Access 窗体和报表、Microsoft(r) Word 的 ThisDocument 对象,Microsoft(r) Excel 的 ThisWorkbook 和 SheetN 对象。
在现实世界中,你经常看到相同类型的许多对象。比如 ,你的自行车只是现实世界中许多自行车的其中一辆。使用面向对象技术,我们可以说你的自行车是自行车对象类的一个实例。通常,自行车有一些状态(当前档位、两个轮子等等)以及行为(改变档位、刹车等等)。但是,每辆自行车的状态都是独立的并且跟其它自行车不同。
当厂家制造自行车的时候,厂商利用了自行车共有的特性来根据相同的蓝图制造许多自行车。如果制造一辆自行车就要产生一个新蓝图,那效率就太低了。
在面向对象软件中,同样地,可以让相同种类地许多对象来共有一些特性,比如矩形、雇员记录、视频夹等等。就象自行车制造商人,你可以利用相同种类的对象是相似的事实并且你可以为这些对象创建一个蓝图。对对象的软件蓝图叫做类。
自行车的类需要定义一些实例变量来包括当前档位、当前速度等等。这个类将为实例方法定义和提供实施方法,它允许骑车者改变档位、刹车以及改变脚踏板的节奏。
当你创建了自行车类以后,你可以从这个类创建任意个自行车对象。当你创建了一个类的实例后,系统将为这个对象和的实例变量分配内存。每个实例将给所有实例变量的副本定义在类中。
除了实例变量,类还要定义类的变量。类变量包含了被类所有实例共享的信息。比如,假设所有的自行车有相同的档位数。在本例子中,要定义一个实例变量来容纳档位数。每一个实例都会有变量的副本,但是在每一个实例中数值都是相同的。在这样的情况下,你可以定义一个类变量来包含档位数,这样所有的类的实例都共享这个变量。如果一个对象改变了变量,它就为改变那个类的所有对象。类同样可以定义类方法。你可以直接从类中调用类方法,然而你必须在特定的实例中调用实例方法。
7.LIB和DLL
LIB 是 Library 的缩写,在程序编译时链接
DLL 是 Dynamic Link Library (动态链接库)的缩写,在程序运行过程中链接。用隐式的连接到一个DLL是的时候,必须要一个引入库(Import Lib) 就是DLL的Lib。dll是个编译好的程序, 调用时可以直接调用其中的函数, 不参加工程的编译. 而lib应该说是一个程序集, 只是把一些相应的函数总结在一起, 如果调用lib中的函数, 在工程编译时,这些调用的函数都将参加编译.
8.DLL与LIB的区别:
1).DLL是一个完整程序,其已经经过链接,即不存在同名引用,且有导出表,与导入表
lib是一个代码集(也叫函数集)他没有链接,所以lib有冗余,当两个lib相链接时地址会重新建立,当然还有其它相关的不同,用lib.exe就知道了
2)在生成dll时,经常会生成一个.lib(导入与导出),这个lib实际上不是真正的函数集,其每一个导出导入函数都是跳转指令,直接跳转到DLL中的位置,这个目的是外面的程序调用dll时自动跳转
3).实际上最常用的lib是由lib.exe把*.obj生成的lib,这才是真正的库,他是代码集,可完全代替目标代码
7 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:53:00
5).Gdi32
DLL 文件: gdi32 或者 gdi32.dll
DLL 名称: Windows GDI Client DLL
[编辑]描述
gdi32.dll是Windows GDI图形用户界面相关程序,用于辅助创建组建
6). ByVal是什么意思
过程中的代码通常需要某些关于程序状态的信息才能完成它的工作。信息包括在调用过程时传递到过程内的变量。当将变量传 递到过程时,称变量为参数。
参数的数据类型
过程的参数被缺省为具有 Variant 数据类型。不过,也可以声明参数为其它数据类型。
例如,下面的函数接受一个字符串和一个整数:
Function WhatsForLunch(WeekDay As String, Hour As Integer) As String
'根据星期几和时间,返回午餐菜单。
If WeekDay = "Friday" then
WhatsForLunch = "Fish"
Else
WhatsForLunch = "Chicken"
End If
If Hour > 4 Then WhatsForLunch = "Too late"
End Function
ByVal 按值传递(ByValue),意思是传入的形式参数改变了,但是不影响实际参数的数值
比如
在一个函数fn()有参数para,是byval的,
那么在函数里面如果有语句para=999,
有变量dim p as integer = 100;
那么调用
fn(p)
之后,p的值仍然是100,而不是999,函数里面的修改不影响外面的变量
1.按值传递参数
按值传递参数时,传递的只是变量的副本。如果过程改变了这个值,则所作变动只影响副本而不会影响变量本身。用 ByVal 关 键字指出参数是按值来传递的。
例如:
Sub PostAccounts (ByVal intAcctNum as Integer)
.
. '这里放语句。
.
End Sub
与它对应的是ByRef ,按引用传递
这个时候,调用了函数之后,p的值就变成999了
2.按地址传递参数
按地址传递参数使过程用变量的内存地址去访问实际变量的内容。结果,将变量传递给过程时,通过过程可永远改变变量值。按地址传递参数在 Visual Basic 中是缺省的。
如果给按地址传递参数指定数据类型,就必须将这种类型的值传给参数。可以给参数传递一个表达式,而不是数据类型。Visual Basic 计算表达式,如果可能的话,还会按要求的类型将值传递给参数。
把变量转换成表达式的最简单的方法就是把它放在括号内。例如,为了把声明为整数的变量传递给过程,该过程以字符串为参数,则可以用下面的语句:
Sub CallingProcedure ()
Dim intX As Integer
intX = 12 * 3
Foo (intX)
End Sub
Sub Foo (Bar As String)
MsgBox Bar 'Bar 的值为字符串‘ 36 ’。
End Sub
8 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:54:00
注释3: Private Declare Function CreateRoundRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 As Long, ByVal Y3 As Long) As Long
1).CreateRoundRectRgn
VB声明
Declare Function CreateRoundRectRgn Lib "gdi32" Alias "CreateRoundRectRgn" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, ByVal X3 As Long, ByVal Y3 As Long) As Long
说明
创建一个圆角矩形,该矩形由X1,Y1-X2,Y2确定,并由X3,Y3确定的椭圆描述圆角弧度
返回值
Long,执行成功则为区域句柄,失败则为0
参数表
参数 类型 说明
X1,Y1 Long 矩形左上角的X,Y坐标
X2,Y2 Long 矩形右下角的X,Y坐标
X3 Long 圆角椭圆的宽。其范围从0(没有圆角)到矩形宽(全圆) ?
Y3 Long 圆角椭圆的高。其范围从0(没有圆角)到矩形高(全圆) ?
注解
不用时一定要用DeleteObject函数删除该区域
用该函数创建的区域与用RoundRect API函数画的圆角矩形不完全相同,因为本矩形的右边和下边不包括在区域之内
9 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:55:00
注释4: Private Declare Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
1).CreateEllipticRgn
VB声明
Declare Function CreateEllipticRgn Lib "gdi32" Alias "CreateEllipticRgn" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
说明
创建一个椭圆,该椭圆与X1,Y1和X2,Y2坐标点确定的矩形内切
返回值
Long,执行成功则为区域句柄,失败则为零
参数表
参数 类型 说明
X1,Y1 Long 矩形左上角X,Y坐标
X2,Y2 Long 矩形右下角X,Y坐标
注解
不用时一定要用DeleteObject函数删除区域。用Ellipse API函数绘出的椭圆与该椭圆区域不完全相同,因为本函数的绘图计算不 包括矩形的下边和右边
下面的例子是我搜的,对我理解CreateEllipticRgn很有帮助:
主题:函数CreateEllipticRgn的问题
作者:nizainade
我在下载的vb例子有个长生椭圆窗体的函数,相信大家也都见过这个例子
只是有一点我不太明白:
w = Form1.Width / Screen.TwipsPerPixelX
h = Form1.Height / Screen.TwipsPerPixelY
a = CreateEllipticRgn(0, 0, w, h)
b = SetWindowRgn(Me.hWnd, a, True)
上面的代码,w,h好像是把twip转换成pixel的单位,一定要转换吗,直接用默认的twip不行吗,我把他该成:a = CreatEllopticRgn(0,0,width,height),却没有运行结果,请问这是怎么回事呢
1 楼
因为我们的桌面的分辨率是以像素来分的,而VB里是用缇。缇与像素的比是15,所以不转成像素的话,相当于将窗体扩大15倍,当然没结果了。
2 楼
1.你的意思是CreateEllipticRgn这个函数的参数要用象素作为单位,是不是api函数的图形函数都是以象素为函数呢??
2.CreateEllipticRgn(0, 0, w, h)中的,几个参数是以屏幕的坐标系为标准吗??,那么0,0 应该在屏幕的最左上角啊,但是结果运行的窗体是在屏幕中间,请问这是为什么???我对vb的坐标系有点迷惑,即:不知道是屏幕的还是窗体的,望指教!
3 楼
问题一:
一般应该是以像素为单位的
问题二:
它是以屏幕坐标系为标准的,为什么会在中间呢?呵呵其实你看看CreateEllipticRgn
函数的说明就知道了
“创建一个椭圆,该椭圆与X1,Y1和X2,Y2坐标点确定的矩形内切”注意是矩形内切,所以前面的坐标点即X1、Y1为0、0,只是确保所画的矩形起点是屏幕的(0,0),但内切椭圆
的中心点就是屏幕中心了。
4 楼
"椭圆与X1,Y1和X2,Y2坐标点确定的矩形内切"这句话,应该是创建一个以x1,y1为左上顶点,以x2,y2为右下顶点的矩形后,再在这个矩形里面划内切椭圆吧,按照这个理解,矩形应该是在左上角啊
我的理解是不是哪里有问题啊,望高手不吝赐教啊
5 楼
理解是对了但因为这个是创建椭圆的过程你是看不到的,能够让你的窗体成为椭圆的是b = SetWindowRgn(Me.hWnd, a, True)这句,至于为什么椭圆会在屏幕中间是因为你的窗体的StartUpPosition属性设了屏幕居中。
6 楼
多谢帅哥不厌其烦的回答我的问题,我明白了第一个函数只是产生椭圆区域,第二个再把窗体设为椭圆型的,
10 楼
含笑不语 [专家分:50] 发布于 2008-01-06 19:56:00
(注释5) : Private Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long
1).CombineRgn
VB声明
Declare Function CombineRgn Lib "gdi32" Alias "CombineRgn" (ByVal hDestRgn As
Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As
Long) As Long
说明
将两个区域组合为一个新区域
返回值
Long,下列常数之一:
COMPLEXREGION:区域有互相交叠的边界
SIMPLEREGION:区域边界没有互相交叠
NULLREGION:区域为空
ERRORAPI:不能创建组合区域
参数表
参数 类型 说明
hDestRgn Long 包含组合结果的区域句柄
hSrcRgn1 Long 源区域1
hSrcRgn2 Long 源区域2
nCombineMode Long 组合两区域的方法。可设为下述常数
RGN_AND hDestRgn 被设置为两个源区域的交集
RGN_COPY hDestRgn 被设置为hSrcRgn1的拷贝
RGN_DIFF hDestRgn 被设置为hSrcRgn1中与hSrcRgn2不相交的部分
RGN_OR hDestRgn 被设置为两个区域的并集
RGN_XOR hDestRgn 被设置为除两个源区域OR之外的部分
注意:
CombineRgn hDestRgn, hSrcRgn1, hSrcRgn2, RGN_DIFF
将区域hSrcRgn1中不属于hSrcRgn2的区域合并到combined_rgn中,如果你创建的hSrcRgn2区域要大于hSrcRgn1区域,即hSrcRgn1- hSrcRgn2〈0,那么hDestRgn是个空区域!即编译后异形窗体无法显示!用SetWindowRgn hWnd, hRgn, True就会看不到图形
这种错误是设置窗体坐标模式错误导致的,将焦点所在窗体的scalemode设置成其他模式你会看到效果的.
我来回复