zoukankan      html  css  js  c++  java
  • Lazarus+FPC2.7.1 下DLL 创建及调用

     Computer is full of bugs

    You can't eat them

    So just get used to them.

    -- 别想了,我说的

                                        

    原创,转载请注明出处,谢谢。

    看了官方论坛上的一个贴子,试着写了一个类似于 InputBox函数的DLL,期间处理了几个小问题,整理一下,做个备忘。

    系统环境:Win7 32bit中文版,lazarus 2012/3/12 daily snapshot,fpc2.7.1.

    先看一下测试程序运行的效果。

    一、主程序窗口:

    窗口设置为固定边框,无最大最小按钮,“(R)获取”按钮为窗体默认按钮,“(C)退出”按钮设为ESC键。

    二、按“获取”按钮,askForInt模式窗口弹出,文本框默认获取焦点,默认为0,等待输入。请注意此时主窗口无法获取焦点,虽然是模式对话框的正常反应,但因呼叫DLL,所以此处在后来的程序中确实出现了意外,花了一阵功夫才解决!!

    另外请注意该askForInt 对话框固定边框,无系统按钮(最大、最小、关闭),也不能按Alt+F4强制退出,在下面的实现部分只关注代码,GUI设计部分不再赘述。

    三、程序要求只接受整数输入,当输入非整数值时,弹出错误提示:

    四、单击OK按钮,退回到输入整数模式对话框,输入整数的文本框重新获取焦点,等待再次输入。尝试按Alt+F4强制退出失败:

     五、重新输入有效整数899:

    六、主测试窗口获得该整数:

    以下为实现。

    一、DLL部分的实现

    1.从Lazarus IDE新建一个library工程,保存为askfor. 看一下askfor.lpr的代码:

     1 library askfor;
    2
    3 {$mode objfpc}{$H+}
    4
    5 uses
    6 Classes, uaskfor ,interfaces,forms
    7 { you can add units after this };
    8 //如果没有下面这行,请添加,因为DLL包含GUI控件
    9 { $R *.res}
    10 //你需要添加以下整个exports部分(11-14行):
    11 exports
    12 {以下为需要导出DLL的函数,函数名为askForInt,返回值为integer}
    13
    14 askForInt;
    15
    16 begin
    17 Application.Initialize;//你需要添加的代码部分
    18 end.

     呵呵,Lazarus做DLL真是简单了明~

    2.为工程添加一个Form窗体单元,保存为uaskfor. 

    以下为窗体的*.frm文件内容:

     1 object Form1: TForm1
    2 Left = 540
    3 Height = 124
    4 Top = 260
    5 Width = 413
    6 BorderIcons = []
    7 BorderStyle = bsDialog
    8 Caption = '数字:'
    9 ClientHeight = 124
    10 ClientWidth = 413
    11 OnCloseQuery = FormCloseQuery
    12 OnCreate = FormCreate
    13 Position = poScreenCenter
    14 LCLVersion = '0.9.31'
    15 object Label1: TLabel
    16 Left = 16
    17 Height = 13
    18 Top = 14
    19 Width = 163
    20 Caption = '请输入一个数字(默认为O):'
    21 ParentColor = False
    22 end
    23 object txtNumber: TEdit
    24 Left = 16
    25 Height = 25
    26 Top = 40
    27 Width = 368
    28 TabOrder = 0
    29 Text = '0'
    30 end
    31 object btnOK: TButton
    32 Left = 304
    33 Height = 25
    34 Top = 72
    35 Width = 75
    36 Caption = '&OK'
    37 Default = True
    38 OnClick = btnOKClick
    39 TabOrder = 1
    40 end
    41 end

    注意OK按钮为窗体默认按钮(btnOK.default:=true).

    3.窗体包含OK按钮单击事件的默认代码:

     1 unit uaskfor;
    2
    3 {$mode objfpc}{$H+}
    4
    5 interface
    6
    7 uses
    8 classes, sysutils, fileutil, forms, controls, graphics, dialogs, StdCtrls, Buttons;
    9
    10 type
    11
    12 { tform1 }
    13
    14 tform1 = class(tform)
    15 btnOK: tbutton;//OK按钮
    16 txtNumber: TEdit;//按受整数文本框
    17 label1: tlabel;
    18 procedure btnokclick(sender: tobject);//OK按钮单击事件
    19
    20 private
    21 { private declarations }
    22
    23 public
    24 { public declarations }
    25 end;
    26
    27 var
    28 form1: tform1;
    29
    30 implementation
    31
    32 {$R *.lfm}
    33
    34 procedure tform1.btnokclick(sender: tobject);
    35 begin
    36
    37 end;

    4.因为要获取一个整数,首先需要一个integer类型的变量number, 添加在第29行。

    27  var
    28 form1:tform1;
    39 number:integer;//number 用来保存程序获取的整数值

    5.用户输入一个值,然后程序进行判断,如果合法,则保存在number变量里,然后窗体关闭;如果不合法,提示,然后要求重新输入,焦点定位在文本框里:

     1 procedure tform1.btnokclick(sender: tobject);
    2 begin
    3 try
    4 number:=strtoint(txtNumber.Text);
    5 close;//关闭窗体并退出
    6 Except on Exception do begin
    7 application.MessageBox('输入错误!','输入整数',0);
    8 self.txtNumber.SetFocus;
    9
    10 end;
    11 end;
    12
    13 end;

    6.那么另一个窗体如何能调用到number呢?正常情况下,添加对uaskfor单元的引用之后,就可以直接引用了。这里我们用函数来用。还记得在askfor.lpr工程代码里有两个函数吗?其中一个是askForInt,返回值为integer.现在的askfor.pas单元代码如下:

     1 unit uaskfor;
    2
    3 {$mode objfpc}{$H+}
    4
    5 interface
    6
    7 uses
    8 classes, sysutils, fileutil, forms, controls, graphics, dialogs, StdCtrls, Buttons;
    9
    10 type
    11
    12 { tform1 }
    13
    14 tform1 = class(tform)
    15 btnOK: tbutton;//OK按钮
    16 txtNumber: TEdit;//按受整数文本框
    17 label1: tlabel;
    18 procedure btnokclick(sender: tobject);//OK按钮单击事件
    19
    20 private
    21 { private declarations }
    22
    23 public
    24 { public declarations }
    25 end;
    26
    27 var
    28 form1: tform1;
    29
    30 implementation
    31
    32 {$R *.lfm}
    33
    34 function askForInt:integer;stdcall;export;
    35
    36 procedure tform1.btnokclick(sender: tobject);
    37 begin
    38 try
    39 number:=strtoint(txtNumber.Text);
    40
    41 close;//关闭窗体并退出
    42 Except on Exception do begin
    43 application.MessageBox('输入错误!','输入整数',0);
    44 self.txtNumber.SetFocus;
    45
    46      end;
    47 end;
    48
    49 end;
    50
    51
    52 function askforint: integer; stdcall;
    53 begin
    54 result:=number;
    55
    56 end;

    我们在34行添加了一个 askForInt函数,注意 stdcall;export 修饰符为导出DLL提供了必要的支持。52-56行是该函数的实现,简单返回number 的值。

    7.如何保证输入不合法的时候窗体不允许强制关闭(ALT-F4)?在窗体的FormCloseQuery(sender: tobject; var canclose: boolean)事件里,当参数canClose为真是可以关闭窗体,为假时就关不了了。

    接下来的逻辑是这样的:

    if 输入合法 then
    canClose:=true
    else
    canClose:=false;

    8.那么我们在何处判断输入是合法的呢?ok 按钮单击事件里,分别在第39行和第42行。其中第39行说明strtoint类型转换成功,而第42行转化失败系统抛出异常。我们为TForm1 声明一个boolean型变量okToClose,39行没抛出异常时okToClose应为true,而42行时应为false.接下来,okToClose为真时,canClose就为真;okToClose为假时,canClose 就为假。

    procedure tform1.formclosequery(sender: tobject; var canclose: boolean);
    begin
    canclose:=okToClose;
    end;

    9.差不多了,再说两点。那么okToClose是何时何地初始化的?初始值为真还是为假?我们说,Form不能被关闭,除非...(除非输入合法),所以初始值应为假,只有在输入合法时才被修正为真,即上面所讲的第39行,这样当输入非法时,在异常部分即便不给okToClose赋值,它也是为假,符合系统的设计。一般地,窗体的变量初始化在Form 的 FormCreate 事件里做就可以了。另外,当窗体一弹出时,接受输入的文本框即获得焦点方显得合乎道理一些。这两个问题都放在Form的FormCreate事件里处理:

    procedure tform1.formcreate(sender: tobject);
    begin

    okToClose:=false;
    txtNumber.Focused;
    end;

    10.现在剩下最后一步了:askForInt函数可以获取所需要的整数输入,但问题是对话框窗口是几时打开的呢?不至一个方案可以解决,我们先偿试在askForInt函数里打开这个对话框的方案,即让已经声明好的form1变量做好自己的工作:

    function askforint: integer; stdcall;
    begin
    form1:=TForm1.Create(nil);
    form1.ShowModal();//注意这句,我们要的是模式对话框!!
    result:=number;
    form1.Destroy();//用Create(nil)创建的类得自己释放

    end;

    我们添加了FormCloseQuery和FormCreate事件处理函数,对askForInt函数的实现进行了修改,uaskfor单元的全部代码如下:

    View Code
     1 unit uaskfor;
    2
    3 {$mode objfpc}{$H+}
    4
    5 interface
    6
    7 uses
    8 classes, sysutils, fileutil, forms, controls, graphics, dialogs, StdCtrls, Buttons;
    9
    10 type
    11
    12 { tform1 }
    13
    14 tform1 = class(tform)
    15 btnOK: tbutton;
    16 txtNumber: TEdit;
    17 label1: tlabel;
    18 procedure btnokclick(sender: tobject);
    19 procedure formclosequery(sender: tobject; var canclose: boolean);
    20 procedure formcreate(sender: tobject);
    21 private
    22 { private declarations }
    23 okToClose:boolean;
    24 public
    25
    26 { public declarations }
    27 end;
    28
    29 function askForInt:integer;stdcall;export;
    30
    31 var
    32 form1: tform1;
    33 number:integer;
    34
    35 implementation
    36
    37 {$R *.lfm}
    38
    39 { tform1 }
    40
    41
    42 procedure tform1.btnokclick(sender: tobject);
    43 begin
    44 try
    45 number:=strtoint(txtNumber.Text);
    46 okToClose:=true;
    47 close;//关闭窗体并退出
    48 Except on Exception do begin
    49 application.MessageBox('输入错误!','输入整数',0);
    50 self.txtNumber.SetFocus;
    51
    52 end;
    53 end;
    54
    55
    56 end;
    57
    58 procedure tform1.formclosequery(sender: tobject; var canclose: boolean);
    59 begin
    60 canclose:=okToClose;
    61 end;
    62
    63 procedure tform1.formcreate(sender: tobject);
    64 begin
    65
    66 okToClose:=false;
    67 txtNumber.Focused;
    68 end;
    69
    70
    71 function askforint: integer; stdcall;
    72 begin
    73 form1:=TForm1.Create(nil);
    74 form1.ShowModal();//注意我们要的是模式对话框!
    75 result:=number;
    76 form1.Destroy();//用Create(nil)创建的类得自己释放
    77
    78 end;
    79
    80
    81 end.

    11.Shift+F9 build,生成askfor.dll.

    二、客户测试程序的实现

    1.创建一个普通的GUI工程,测试主窗体的各属性值如下:

     1 object Form1: TForm1
    2 Left = 530
    3 Height = 127
    4 Top = 350
    5 Width = 355
    6 BorderStyle = bsDialog
    7 Caption = '测试窗口'
    8 ClientHeight = 127
    9 ClientWidth = 355
    10 LCLVersion = '0.9.31'
    11 object Edit1: TEdit
    12 Left = 27
    13 Height = 25
    14 Top = 40
    15 Width = 304
    16 TabOrder = 0
    17 Text = '2012'
    18 end
    19 object btnRetrieve: TButton
    20 Left = 24
    21 Height = 25
    22 Top = 80
    23 Width = 75
    24 Caption = '(&R)获取'
    25 Default = True
    26 OnClick = btnRetrieveClick
    27 TabOrder = 1
    28 end
    29 object btnClose: TButton
    30 Left = 256
    31 Height = 25
    32 Top = 80
    33 Width = 75
    34 Cancel = True
    35 Caption = '(&C)退出'
    36 OnClick = btnCloseClick
    37 TabOrder = 2
    38 end
    39 object Label1: TLabel
    40 Left = 24
    41 Height = 13
    42 Top = 16
    43 Width = 115
    44 Caption = '从DLL获取一个整数:'
    45 ParentColor = False
    46 end
    47 end

     2.主窗体的代码单元unit1.pas 首先需要添加对dynlibs单元的引用,以使DLL调用的相关函数可用。

     1 unit unit1;
    2
    3 {$mode objfpc}{$H+}
    4
    5 interface
    6
    7 uses
    8 classes, sysutils, fileutil, forms, controls, graphics, dialogs, StdCtrls,dynlibs;
    9
    10 type
    11
    12 { tform1 }
    13
    14 tform1 = class(tform)
    15 btnRetrieve: tbutton;
    16 btnClose: tbutton;
    17 edit1: tedit;
    18 label1: tlabel;
    19 procedure btnRetrieveclick(sender: tobject);
    20 procedure btnCloseclick(sender: tobject);
    21 private
    22 { private declarations }
    23
    24 public
    25 { public declarations }
    26 end;
    27
    28 var
    29 form1: tform1;
    30
    31 implementation
    32
    33 {$R *.lfm}
    34
    35 { tform1 }

    3.“(C)退出"按钮的事件代码---close;

    procedure tform1.btnCloseclick(sender: tobject);
    begin
    close;
    end;

    4."(R)获取"按钮的事件代码:

    调用 askfor.dll里的askForInt函数的步骤:

    1).声明TLibHandle类型的变量句柄lib;

    2).调用LoadLibrary('askfor.dll'),返回的句柄存放在lib变量里;

    3).声明函数类型TFunc=function():integer,stdcall;即返回值为integer,无参的函数类型;

    4).声明TFunc类型的变量getInt:TFunc;

    5).调用GetProcedureAddress(lib,'askForInt')返回函数askForInt地址,getInt指向该地址,注意pointer(getInt)转换,即:pointer(getInt):=GetProcedureAddress(lib,'askForInt');pointer(getInt)其实就是C下不透明指针的对等。

    6).Assigned(getInt)测试getInt是否为空,如不为空就可以准备调用了。

    7).声明integer类型变量num;

    8).num:=getInt();

    9).调用完毕后记得用 FreeLibrary(lib)释放资源;

    以上即为调用 askfor.dll里askForInt()函数的全部步骤;接下来:

    10). TEdit1文本框显示该num值 :

    11).edit1.text:=inttostr(num);
    代码:

    View Code
     1 procedure tform1.btnRetrieveclick(sender: tobject);
    2 type
    3
    4 TFunc=function():integer;stdcall;
    5 var
    6 lib:TlibHandle;
    7
    8 getInt:TFunc;
    9 num:integer;
    10
    11
    12
    13 begin
    14 lib:=loadlibrary('askfor.dll');
    15 try
    16 pointer(getInt):=getProcedureAddress(lib,'askForInt');
    17 if Assigned(getInt) then begin
    18 num:=getInt();
    19 self.edit1.Text:=inttostr(num);
    20 end;
    21 finally
    22     freelibrary(lib);
    23 end;
    24
    25 end;

     主程序全部代码:

    View Code
     1 unit unit1;
    2
    3 {$mode objfpc}{$H+}
    4
    5 interface
    6
    7 uses
    8 classes, sysutils, fileutil, forms, controls, graphics, dialogs, StdCtrls,dynlibs;
    9
    10 type
    11
    12 { tform1 }
    13
    14 tform1 = class(tform)
    15 btnRetrieve: tbutton;
    16 btnClose: tbutton;
    17 edit1: tedit;
    18 label1: tlabel;
    19 procedure btnRetrieveClick(sender: tobject);
    20 procedure btnCloseClick(sender: tobject);
    21 private
    22 { private declarations }
    23
    24 public
    25 { public declarations }
    26 end;
    27
    28 var
    29 form1: tform1;
    30
    31 implementation
    32
    33 {$R *.lfm}
    34
    35 { tform1 }
    36
    37 procedure tform1.btnCloseClick(sender: tobject);
    38 begin
    39 close;
    40 end;
    41
    42 procedure tform1.btnRetrieveClick(sender: tobject);
    43 type
    44
    45 TFunc=function():integer;stdcall;
    46 var
    47 lib:Tlibhandle;
    48
    49 getInt:TFunc;
    50 num:integer;
    51
    52 form:TForm;
    53
    54 begin
    55 lib:=loadlibrary('askfor.dll');
    56 try
    57 pointer(getInt):=getProcedureAddress(lib,'askForInt');
    58 if Assigned(getInt) then begin
    59 num:=getInt();
    60 self.edit1.Text:=inttostr(num);
    61 end;
    62
    63 finally
    64 freelibrary(lib);
    65 end;
    66
    67 end;
    68
    69 end.

    F9 Run.试着输入几个字母,提示输入错误....一切正常。Congratulations!!

    等等。打开输入整数的对话框后,试着给主程序窗口提供焦点,行??再试着给主程序窗口的TEdit控件里输入数字神马滴,也竟然行??DLL里不是模式对话框吗?我倒,我倒。


    好在有官方论坛,好在有百度GOOGLE。

    不管你是相信它是个bug:http://bugs.freepascal.org/view.php?id=7182,还是相信以下的解释:

    在Delphi或是Lazarus的 GUI应用中,主窗体启用了一个TApplication实例,用户的DLL(由LCL GUI)构建也开启了一个TApplication实例,现在共有两个TApplication实例,所以虽然DLL的窗体设计为模式对话框,但主程序由另一个TApplication实例控制,所以使得模式对话框失效。

    ----我们都得解决它,不是吗?

     三、如果再回到从前

    回头看DLL的实现部分。如果DLL提供一个返回对话框窗口类TForm1 的函数,主程序从该函数入手,然后构造该输入对话框实例并显示之,在这个过程中进一步控制模式还是非模式窗口问题情况会如何?下面试试。

    1.askfor.lpr的export部分添加另一个导出函数getClass:

    library askfor;

    {$mode objfpc}{$H+}

    uses
    Classes, uaskfor ,interfaces,forms
    { you can add units after this };

    { $R *.res}

    exports

    getClass,
    askForInt;

    begin
    Application.Initialize;
    end.

    2.uaskfor.pas单元添加getClass的实现,并对原来的askForInt函数的实现做相应的修改,uaskfor.pas 全部代码如下:

    View Code
     1 unit uaskfor;
    2
    3 {$mode objfpc}{$H+}
    4
    5 interface
    6
    7 uses
    8 classes, sysutils, fileutil, forms, controls, graphics, dialogs, StdCtrls, MaskEdit, Buttons;
    9
    10 type
    11
    12 { tform1 }
    13
    14 tform1 = class(tform)
    15 btnOK: tbutton;
    16 txtNumber: TEdit;
    17 label1: tlabel;
    18
    19 procedure btnokclick(sender: tobject);
    20 procedure formclosequery(sender: tobject; var canclose: boolean);
    21 procedure formcreate(sender: tobject);
    22 private
    23 { private declarations }
    24 okToClose:boolean;
    25 public
    26 //number:integer;
    27 { public declarations }
    28 end;
    29 function getClass:TFormClass;stdcall;export;
    30 function askForInt:integer;stdcall;export;
    31
    32 var
    33 //form1: tform1;
    34 number:integer;
    35
    36 implementation
    37
    38 {$R *.lfm}
    39
    40 { tform1 }
    41
    42
    43
    44
    45
    46
    47 procedure tform1.btnokclick(sender: tobject);
    48 begin
    49 try
    50 number:=strtoint(txtNumber.Text);
    51 okToClose:=true;
    52 close;
    53 Except on Exception do begin
    54 application.MessageBox('输入错误!','输入整数',0);
    55 self.txtNumber.SetFocus;
    56 okToClose:=false;
    57 end;
    58 end;
    59
    60
    61 end;
    62
    63 procedure tform1.formclosequery(sender: tobject; var canclose: boolean);
    64 begin
    65 canclose:=okToClose;
    66 end;
    67
    68 procedure tform1.formcreate(sender: tobject);
    69 begin
    70
    71
    72 okToClose:=false;
    73 txtNumber.Focused;
    74 end;
    75
    76 function getclass: tformclass; stdcall;
    77 begin
    78 result:=tform1;
    79 end;
    80
    81 function askforint: integer; stdcall;
    82 begin
    83 result:=number;
    84
    85 end;
    86
    87
    88
    89 end.

    第一是getClass 函数原型返回一个TFormClass类型,实际上实现部分是返回了TForm1.因TFormClass 是我们的TForm1的祖先类,所以向上转型是可以的。

    第二是askForInt函数这次简单地返回了变量number.原因前面讲过了,打算在主程序里通过调用getClass后得到TFormClass,然后通过它来构造一个TForm1的实例。大概的思路如下:

    var
    formClass:TFormClass;
    form:TForm;
    begin
    formClass:=getClass();//
    form:=formClass.Create(nil);
    form.ShowModal();//现在输入窗口打开,焦点锁定,用户无论如何得输入合法整数,然后number变量被填充

    ...

    end;

    Shift+F9 build,再次生成askfor.dll.

    四、再看主测试程序:

    唯一改变的代码部分是"(R)获取"按钮单击事件:

    View Code
     1 procedure tform1.button1click(sender: tobject);
    2 type
    3 TClassFunc=function():TFormClass;stdcall;
    4 TFunc=function():integer;stdcall;
    5 var
    6 lib:Tlibhandle;
    7 getTheClass:TClassFunc;
    8 getInt:TFunc;
    9 num:integer;
    10 formclass:TFormClass;
    11 form:TForm;
    12
    13 begin
    14 lib:=loadlibrary('askfor.dll');
    15 try
    16 pointer(getTheClass):=getProcedureAddress(lib,'getClass');
    17 if Assigned(getTheClass) then begin
    18 self.Enabled:=false;
    19 try
    20 formClass:=GetTheClass();
    21 form:=GetTheClass.create(nil);
    22 try
    23 form.ShowModal;
    24 pointer(getInt):=getProcedureAddress(lib,'askForInt');
    25 if Assigned(getInt) then begin
    26 num:=getInt();
    27 self.edit1.Text:=inttostr(num);
    28 end;
    29 finally
    30 form.Free;
    31 end;
    32 finally
    33 self.Enabled:=true;
    34 end;
    35
    36
    37 end;

    两点要说,一是self.Enabled:=false; 及self.Enabled:=true的插入点及其作用,这个想一想自然明白;二是通过调用getProcedureAddress(lib,'getClass')获取DLL getClass:TFormClass 函数入口地址,然后通过调用它来得到TForm(实际上是TForm1)类,最后通过TForm1.Create(nil)来创建窗口实例,这个过程也是明了自然。

    F9 Run,测试,测试。O啦~

    Thank you ^_^

  • 相关阅读:
    python爬虫之趟雷
    python附录-builtins.py模块str类源码(含str官方文档链接)
    python-基础学习篇(一)
    pycharm和webstorm永久激活方法
    计算机网络基础概述
    计算机基础
    B/S和C/S架构简单理解
    认识HTML中文本、图片、链接标签和路径
    结对开发
    全国疫情可视化地图 (一)
  • 原文地址:https://www.cnblogs.com/godspeedsam/p/2392182.html
Copyright © 2011-2022 走看看