zoukankan      html  css  js  c++  java
  • delphi dll创建及调用

    第一章 DLL简单介绍
    由于在目前的学习工作中,需要用到DLL文件,就学习了下,在这里作个总结。
    首先装简单介绍下DLL:
    1,减小可执行文件的大小
    DLL技术的产生有很大一部分原因是为了减小可执行文件的大小。当操作系统进入Windows时代后,其大小已经达到几十兆乃至几百兆。试想如果还是使用DOS时代的单执行文件体系的话一个可执行文件的大小可能将达到数十兆,这是大家都不能接受的。解决的方法就是采用动态链接技术将一个大的可执行文件分割成许多小的可执行程序。
    2,实现资源共享
    这里指的资源共享包括很多方面,最多的是内存共享、代码共享等等。DLL还有一个突出的特点就是在内存中只装载一次,这一点可以节省有限的内存,而且可以同时为多个进程服务。
    3,便于维护和升级
    在软件运行出现问题的时候,我们并不需要重新安装程序,只需要替换相应的DLL文件即可。
    4,比较安全
    这里说的安全也包括很多方面。比如,DLL文件遭受病毒的侵害机率要比普通的EXE文件低很多。另外,由于是动态链接的,这给一些从事破坏工作的“高手”们多少带来了一些反汇编的困难。


    第二章 在Delphi中编写DLL文件

    编写DLL文件其实不是什么困难的事情,和我们平时在Delphi中编写程序基本相似,下面先以一个简单的例子来说明。

    新建DLL

    library Lib;
     
    uses
      SysUtils, Classes;
     
    {$R *.res}
     
    procedure Test(p: PChar);
    const
      Title = 'Title ';
    var
      str: string;
    begin
      str := p;
      StrCopy(p, Title);
      StrCat(p, PChar(str));
    end;
     
    exports Test;
     
    begin
    end.
    View Code

    调用的代码文件:

    unit Unit1;
    
    interface
    
    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls;
    
    type
    TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    end;
    
    var
    Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure Test(p: PChar); external 'Lib.dll';
    
    var num: Integer;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
    p: PChar;
    begin
    Inc(num);
    p := StrAlloc(255);
    StrCopy(p, PChar(IntToStr(num)));
    Test(p);
    Text := p;
    StrDispose(p);
    end;
    
    end.
    View Code
    library DLL;
    
    { Important note about DLL memory management: ShareMem must be the
    first unit in your library's USES clause AND your project's (select
    Project-View Source) USES clause if your DLL exports any procedures or
    functions that pass strings as parameters or function results. This
    applies to all strings passed to and from your DLL--even those that
    are nested in records and classes. ShareMem is the interface unit to
    the BORLNDMM.DLL shared memory manager, which must be deployed along
    with your DLL. To avoid using BORLNDMM.DLL, pass string information
    using PChar or ShortString parameters. }
    
    uses
    ShareMem,
    SysUtils,
    Classes;
    
    function testStr: TStringList; stdcall;
    var
    str: string;
    strlist: TStringList;
    begin
    strlist := TStringList.Create;
    strlist.Add('hello');
    strlist.Add('world');
    str := 'hello world';
    result := strlist;
    end;
    
    function testInt(i: Integer): Integer; stdcall;
    begin
    Inc(i);
    result := i;
    end;
    {$R *.res}
    
    exports
    testStr,
    testInt;
    
    begin
    end.
    View Code

    可以看出在上面的Dll文件中我们封装了两个函数。跟一般Delphi编写的程序基本相同,只是在DLL文件中的函数后面多了一个stdcall参数,并且用exports声明需要引出的函数。只要编译上述代码就可以得到一个动态链接库的Dll文件。
    1,在Dll中编写的函数必须在后面加上stdcall调用参数,如果忘记添加stdcall参数的话虽然编译可以通过,但在调用这个DLL时会出现很严重的错误,导致操作系统的死锁。
    2,所写的函数和过程必须用exports声明为外部函数。若没有声明,那Dll内部的函数是不可调用的,这一点显然不是我们想看到的。
    3,当使用了长字符串类型的参数、变量时,如string,要引用ShareMem。虽然Delphi中的string功能很强大,但若是您编写的Dll文件要供其它编程语言调用时,最好使用PChar类型。如果您要坚持使用string类型的参数时、变量甚至是记录信息时,就要引用ShareMem单元,而且这个单元必须是第一个引用的,即在uses语句后的第一个单元。如下:
    uses
    ShareMem,
    SysUtils,
    Classes;
    另外,还有一个地方我们需要添加ShareMem单元,即在您的工程文件中(*.dpr)中的uses下第一个引用ShareMem单元。这里需要注意的是在*.dpr文件中而不是单元文件(*.pas)中添加相同的单元,这一点Delphi自带的帮助文件没有说清楚,造成了许多误会。若不这样做的话,您可能付出死机的代价。避免使用string类型的方法是将其转换为PChar或ShortString类型。


    第三章 Dll文件的调用
    一,静态调用
    调用Dll文件要比编写Dll容易多了,这里先总结一下Dll的静态调用。首先看下面的小例子:
    function testInt(i: Integer): Integer; stdcall; external 'DLL.dll';
    procedure TForm1.btn2Click(Sender: TObject);
    begin
    showMessage(inttostr(testInt(1)));
    end;
    大家可以看到我们做的唯一的工作是将Dll中的函数说明放在implementation下(或是放在文件单元的var下),并且用external语句指定了Dlll的位置。这样我们就可以直接使用Dll中的函数了。
    1,调用参数stdcall,在引用Dll中的函数时也要使用stdcalll参数。
    2,用external声明Dll文件的位置。注意文件的后缀名必须加上。
    3,不能从Dll中调用全局变量。如果我们在DLL中声明了某种全局变量,如:var s:byte 。这样在DLL中s这个全局变量是可以正常使用的,但s不能被调用程序使用,既s不能作为全局变量传递给调用程序。不过在调用程序中声明的变量可以作为参数传递给DLL。
    二,动态调用
    动态调用Dll文件要复杂很多,但非常灵活。在运行大的程序时可以节省内存空间。我们看下面的例子:

    procedure TForm1.btn1Click(Sender: TObject);
    type
    Taddc = function: TStringList; stdcall;
    var
    hh: THandle;
    addc: Taddc;
    temp: TStringList;
    i: Integer;
    begin
    hh := LoadLibrary('DLL.dll');
    try
    if hh <> 0 then
    @addc := GetProcAddress(hh, PChar('testStr'));
    if not (@addc = nil) then
    begin
    temp := addc;
    for i := 0 to temp.Count - 1 do
    showMessage(temp[i]);
    end
    else
    begin
    RaiseLastWin32Error;
    end;
    finally
    FreeLibrary(hh);
    end;
    end;
    View Code

    由上面代码可以看到,这种动态调用技术很复杂。但只要更改参数就可动态更改所调用Dll文件的名字。
    1,在type中定义所要调用的函数或过程的类型,在这里同样也要加上stdcall参数。
    2,释放Dll。在上面程序中我们使用LoadLibray函数加载了dll文件,在使用完Dll文件时切记调用FreeLibrary函数来释放Dll,否则Dll将会一直占用你的内存直至您退出Windows或是关机为止。在这里需要注意的是要确保在释放dll的时候没有任何指针指向该dll中所定义的变量、过程等,否则无法正常释放,招聘访问冲突异常。或是导致程序出现空指针,导致程序意外中止。
    3,在动态调用的Dll函数中我们使用了string类型,由于在两处文件内都引用了ShareMem,所以string类型的数据可以正使用,以及作为参数来传递。
    三,静态和动态调用的对比
    1,静态方法实现简单,易于掌握。并且一般来说速度比较快,也更加安全可靠一些。但静态方法不能灵活地在运行时装卸所需的Dll,而主程序在开始运行时就装载了Dll只到程序结束时才释放该Dll。
    2,动态方法很好地解决了静态方法中的不足,可以方便的访问Dll中的函数和过程。但动态方法难以完全掌握,使用时因为不同的函数或过程需要定义很多复杂的类型和调用方法。

    动态调用

    一、编写dll

    library TestDllByD2007;
    uses
    SysUtils,
    Classes;
    function test(const astr:PChar):Boolean;stdcall;
    begin
    Result:=True;
    end;
    {$R *.res}
    exports test;
    begin
    end.
    View Code

    注意:1.不能使用string类型,否则涉及到资源释放的问题,使用Pchar代替string。
    2.Dll中不能直接返回string,除非引用ShareMem单元,发布程序时需要包含BORLNDMM.DLL
    二、编写测试窗体,就一个button.在button的代码中,实现dll的动态加载和释放。

    unit Unit1;
    interface
    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls;
    type
    TFTest=function (const astr:PChar):Boolean;
    TForm1 = class(TForm)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
    private
    { Private declarations }
    public
    { Public declarations }
    end;
    var
    Form1: TForm1;
    implementation
    {$R *.dfm}
    procedure TForm1.btn1Click(Sender: TObject);
    var
    sdll:string;
    tfTTT:TFTest;
    handle:THandle;
    sstr:string;//声明该变量,目的是避免发生异常。
    begin
    sdll:='TestDllByD2007.dll';
    Caption:='';
    handle:=LoadLibrary(PAnsiChar(sdll));
    if Handle <> 0 then
    begin
    @tfTTT := GetProcAddress(Handle, 'test');
    if @tfTTT <> nil then
    begin
    sstr:='testsss';
    if tfTTT(PChar(sstr))=True then
    begin
    Caption:='true';
    end
    else
    begin
    Caption:='false';
    end;
    end
    else
    begin
    Caption:='can not find the test function';
    end;
    FreeLibrary(Handle);
    end
    else
    begin
    Caption:='can not load library '+ sdll;
    end;
    end;
    end.
    View Code

    Delphi中DLL的创建和使用
    1.DLL简介; 2.调用DLL; 3.创建DLL; 4.两个技巧; 5.初始化; 6.例外处理。

    1、DLL简介
      DLL是Dynamic-Link Libraries(动态链接库)的缩写,库里面是一些可执行的模块以及资源(如位图、图标等)。可以认为DLL和EXE基本上是一回事,只是DLL不能直接执行,而必须由应用程序或者其他DLL调用。DLL为应用程序间的资源共享提供了方便,同时也是多语言混合编程的重要手段。由此可见学习使用DLL是Windows程序员必须掌握的一项重要技术。

    2、如何调用DLL
      在Delphi中有两种方法调用DLL中的函数和过程,即外部声明或者动态加载。

    <1>外部声明
      在Delphi中外部声明是访问外部例程最容易和最常用的方式,有两种声明方式:通过名字、通过索引号。举例如下:在MYDLL.DLL中有两个函数和一个过程,则其外部声明可以写成:

    function test1:integer;external 'mydll';
    //直接通过名称调用test1(注意名称大小写敏感)。
    function test11:integer;external 'mydll' name 'test1';
    //通过名称调用test1,在程序中使用新名称(原名称仍然大小写敏感)。
    procedure test2;external 'mydll' index 1;
    //通过索引号调用TEST2。程序中可以用与DLL中不一样的名称.
      使用外部声明的缺点是程序启动时如果找不到mydll.dll将无法运行,即使没有调用其中的模块。 动态加载的方法可以避免这种情况。

    <2>动态加载
      通过调用Windows API中的相关函数,将DLL调入内存并获得指向函数或过程的指针,执行完模块后释放内存。除了节约内存外,这种方法的一个很大的优点是能处理找不到dll或者在装入过程中出错的情况。这样即使某个dll有问题,应用程序的其他部分仍然能够正常运行。动态加载的例子如下:

    var hDll:THandle; 
      Test1:function:integer; 
    begin 
      hDll:=LoadLibrary('mydll.dll'); 
      if hDll<32 then exit;//如果Dll无法加载则跳出 
      @Test1:=GetProcAddress(hDll,MakeIntResource(1)); 
        //取得mydll中的第一个函数的地址。 
      ... 
      FreeLibrary(hDll); 
    end; 
    View Code

    3、用Delphi创建DLL
      用Delphi创建一个DLL是十分简单的,首先需要新建一个DLL的Porject(如果使用Delphi3.0则可以在File->New对话框中选择DLL),当然也可以自己写,现在这个Project是这样的:

    library Project1; 
    uses SysUtils,Classes; 
    begin 
    end. 
    View Code

      当然这是一个空DLL,现在让我们来加入一个函数,让他成为我们的第一个可以使用的DLL。完成后的文件是这样的:

    library dll1; 
    uses SysUtils,Classes; 
    
    function Test1(a,b:integer):integer; 
    begin 
    Result:=a+b; 
    end; 
    
    exports 
    Test1 index 1; 
    
    begin 
    end. 
    View Code

      在这个DLL里我们声明了一个加法函数,然后用exports语句输出它,只有被输出的函数或过程能被其他程序调用。exports语句后的语法是:函数名 [index <n>],index <n>是为函数手工指定索引号,以便其他程序确定函数地址;也可以不指定,如果没有使用Index关键字,Delphi将按照exports后的顺序从1开始自动分配索引号。现在我们可以调用这个DLL了,下面给出一个实例,运行后form1的标题将变成“1+2=3”:

    声明部分:function Test1(a,b:integer):integer;external 'dll1';
           注意此处是大小写敏感的。
    运行部分:form1.caption:='1+2='+inttostr(test1(1,2));

    4、使用DLL的两个技巧
    <1>把现有的项目改成DLL
      学会制作DLL以前,大多数程序员手中都积攒下来不少已经完成了的项目,如果现在需要把这些项目做成DLL而不是可执行文件,重新写一遍显然是没有必要的,只要按照下面的步骤对已有的项目文件进行修改就可以了:
      ① 打开项目文件(.DPR),删除单元底部begin和end.之间的所有语句(一般情况下这些语句是由Delphi自动生成的)。如果项目中没有用到Form,则从uses子句中删除表单单元(Form),然后转到第③步。
      ② 对项目进行修改,令除Main Form之外的所有Form都是动态生成的,这样我们只要在DLL输出的一个函数或者过程中生成Main Form,即可调用执行整个项目。我们假设Main Form的名字是MyMainForm,项目的名字是MyDll,现在在单元底部的begin语句之前加入一个过程,过程的名字为RunMyDll,这个过程将动态生成Main Form,从而运行整个项目。RunMyDll的写法如下:

        procedure InitDll2; 
        begin 
        Application.CreateForm(TMyMainForm, MyMainForm); 
        MyMainForm.Show; //如果MyMainForm不可视则需要这一句. 
        end; 
    View Code

      ③ 如果想要输出其他函数或者过程,而原来的项目中没有,则可以在单元底部的begin语句之前加入这些代码。
      ④ 在单元底部的begin语句之前加入一个exports小节,然后写出所有想要输出的函数或过程的名字(最好指定索引号)。注意如果执行了第②步,一定要输出RunMyDll过程。
      ⑤ 将项目文件顶部的保留字program改为library。
      ⑥ 编译。
      现在就可以在其他程序中调用本项目中的函数和过程了,只要执行RunMyDll就可以执行这个项目,和执行原来的可执行文件一模一样。

    <2>创建一个引入文件
      如果DLL比较复杂,则为它的声明专门创建一个引入程序单元将是十分有意义的,并且会使这个DLL变得更加容易维护。引入单元的格式如下:

      unit MyImport; {Import unit for MyDll.Dll} 
      interface 
      procedure RunMyDll; 
      implementation 
      procedure RunMyDll;external 'MyDll' index 1; 
      end. 
    View Code

    这样以后想要使用MyDll中的例程时,只要简单的在程序模块中的uses子句中加上MyImport即可。

    5、DLL的初始化和善后工作
      一般的DLL不需要做初始化和善后工作,因此大部分读者可以跳过这一节。但如果你想让你的DLL在被载入时先作一些初始设定,或者退出时释放资源,则可以有三种方法达到目的:

    <1>利用Unit的Initalization与Finalization这两个小节
      可以在Unit的这两个小节中安排Unit的进入和退出,但是Program与Library并没有这两个部分,所以只能写在Unit中。

    <2>利用ExitProc变量
      在Library的begin..end.中间是可以写代码的,这里可以放置DLL初始化代码。如果想要做善后工作,则可以利用ExitProc变量。我们首先在初始化代码中把ExitProc中包含的默认的善后过程地址保存下来,然后把自定义的过程的地址赋给它,这样DLL退出时就会执行我们制定的程序;在自定义的过程的最后,把ExitProc恢复原来的默认值,以便DLL能够继续完成原来默认的善后工作。下面是示例:

      library MyDLL; 
      ... 
      OldExitProc: pointer; 
      ... 
      procedure MyExitProc; 
      begin 
      ... //善后程序 
      ExitProc := OldExitProc; 
      end; 
      ... 
      begin 
      ... //初始化程序 
      OldExitProc := ExitProc; 
      ExitProc := @MyExitProc; 
      end. 
    View Code

    <3>利用DllProc变量
      和ExitProc一样,DllProc也是一个在Systemd单元中预定义的变量。在使用DLLProc时, 必须先写好一个具有以下原型的程序:
      procedure DLLHandler(Reason: integer);
    并在library的begin..end.之间, 将这个DLLHandler程序的执行地址赋给DLLProc中, 这时就可以根据参数Reason的值分别作出相应的处理。另外注意要将Windows单元加入uses子句。

    示例如下:

    library TestDLL; 
      ... 
      procedure MyDLLHandler(Reason: integer); 
      begin 
       case Reason of 
        DLL_Process_Attach: //整个DLL的初始化代码 
        DLL_Process_Detach: //整个DLL的善後程序 
        DLL_Thread_Attach: //当主叫端开始一个Thread时 
        DLL_Thread_Detach: //当主叫端终止一个Thread时 
       end; 
      end; 
      ... 
      begin 
      ... //初始化代码 
      DLLProc := @MyDLLHandler; 
      MyDLLHandle(DLL_Process_Attach); 
      end. 
    View Code

    由上例可以知道,当DLL支援多进程(Thread)的处理时, DllProc非常适合使用。

    6、DLL中的例外处理
      在用Delphi制作DLL时, 在例外处理方面请留意以下三点:

    如果uses子句中没有SysUtils话,无法使用例外处理。
    如果DLL中没有对例外进行处理的话,这个例外会想完传导到主叫端的应用程序。如果该应用程序也是Delphi写的话, 这个例外可以由主叫端进行处理。
    承上, 如果主叫端的程式不是Delphi或Borland C++ Builder,则例外以作业系统错误的形式来处理,例外编号是$0EEDFACE,ExceptionInformation中第一个进入点是例外发生的地址,第二个进入点是指向的Delphi例外物件的引用。

    { Important note about DLL memory management: ShareMem must be the 
    first unit in your library's USES clause AND your project's (select 
    Project-View Source) USES clause if your DLL exports any procedures or 
    functions that pass strings as parameters or function results. This 
    applies to all strings passed to and from your DLL--even those that 
    are nested in records and classes. ShareMem is the interface unit to 
    the BORLNDMM.DLL shared memory manager, which must be deployed along 
    with your DLL. To avoid using BORLNDMM.DLL, pass string information 
    using PChar or ShortString parameters. } 
    
    uses 
    SysUtils, 
    Classes, 
    Unit1 in 'Unit1.pas'; 
    Exports 
    EnableMouseHook, //只要把这两个函数输出就可以了, 
    DisableMouseHook;//不会不懂函数的意思吧^_^。 
    
    {$R *.res} 
    
    begin 
    end. 
    
    
    unit1 
    
    unit Unit1; 
    
    interface 
    Uses Messages,Windows; 
    
    var 
    hHk: HHOOK;//钩子的句柄值。 
    function MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall; 
    //鼠标钩子的回调函数,即是用它来处理得到消息后要干什么。这里我只是发送一个//WM_PASTE消息。 
    //nCode参数是Hook的标志,一般只关心小于0时。看下面的详细说明 
    //WParam参数表示鼠标消息的类型 
    //LParam参数是一个指向 TMOUSEHOOKSTRUCT 结构的指针。结构包含了鼠标消息的状态,我只用了hwnd一个 
    //即鼠标消息要传递给的窗口句柄。 
    //返回值如果不是0的话windows就把这个消息丢掉,其它的程序就不会再收到这个消息了。 
    
    function EnableMouseHook:Boolean; stdcall; export; 
    function DisableMouseHook:Boolean; stdcall; export;//两个函数都是Boolean类型,成功都是返回True 
    
    
    implementation 
    function MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall; 
    var 
    MouseHookStruct: ^TMOUSEHOOKSTRUCT;//这个结构Delphi在Windows单元有定义,直接用就可以了。 
    nState: SHORT;//得到键盘状态的GetKeyState函数的返回值。这是一个16位的数。 
    begin 
    Result := 0; //最好首先给他一个返回值,不然会有警告的!记住这可不是C语言。 
    //当nCode小于0时表示还有其它的Hook,必须把参数传给他。 
    //此时就要用Api函数CallNextHookEx让他调用下一个Hook!!!当然不用好像也可以。 
    if nCode < 0 then 
    Result := CallNextHookEx(hHk,nCode,WParam,LParam)//参数是现成的,直接用就可以了, 
    //详细的说明可以参考Win32 SDK 
    else if wParam = WM_LBUTTONDBLCLK then //判断是不是鼠标左键双击事件 
    begin 
    nState := GetKeyState(VK_CONTROL);//这个函数只有一个参数,就是要得到的键的 
    //键值,这里用windows的虚拟键值表示ctrl键。 
    if (nState and $8000) = $8000 then//如果按下了,那么返回值的最高位为1 
    begin //即是16进制的8000,如果没有按下就返回0 
    MouseHookStruct := Pointer(LParam);//转换指针并付值给MouseHookStruct变量。 
    SendMessage(MouseHookStruct.hwnd,WM_PASTE,0,0);//如果条件都满足了就发送WM_PASTE(粘贴)消息 
    end; 
    end; 
    
    end; 
    
    function EnableMouseHook:Boolean; stdcall; export; 
    begin 
    if hHk = 0 then //为了安全,必须判断一下再设置钩子。 
    Begin 
    // 第三个参数的Hinstance 在Delphi中有定义,用就可以了。第四个参数必须为0 
    hHk := SetWindowsHookEx(WH_MOUSE,@MouseHookProc,Hinstance,0); 
    Result := True; 
    end 
    else 
    Result := False; 
    end; 
    
    function DisableMouseHook:Boolean; stdcall; export; 
    begin 
    if hHk <> 0 then //如果有钩子就卸掉他。 
    begin 
    UnHookWindowsHookEx(hHk); 
    hHk := 0; 
    Result := True; 
    end 
    else 
    Result := False; 
    end; 
    
    
    end.
    
     
    View Code

    动态与静态调用DLL


      一、动态链接库的概念

       动态链接库( Dynamic Link Library ,缩写为 DLL )是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。动态链接库文件的扩展名一般是 dll , 也有可能是 drv 、 sys 和 fon ,它和可执行文件( exe )非常类似,区别在于 DLL 中虽然包含了可执行代码却不能单独执行,而应由 Windows 应用 程序直接或间接调用。

      动态链接是相对于静态链接而言的。所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件 的一部分。换句话说,函数和过程的代码就在程序的 exe 文件中,该文件包含了运行时所需的全部代码。当多个程序都调用相同函数时,内存中就会存在这个函数 的多个拷贝,这样就浪费了宝贵的内存资源。而动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在 Windows 的管理下,才在应用程序与相应的 DLL 之间建立链接关系。当要执行所调 用 DLL 中的函数时,根据链接产生的重定位信息, Windows 才转去执行 DLL 中相应的函数代码。

      一般情况下,如果一个应用程序使 用了动态链接库, Win32 系统保证内存中只有 DLL 的一份复制品,这是通过内存映射文件实现的。 DLL 首先被调入 Win32 系统的全局堆栈,然后映射到 调用这个 DLL 的进程地址空间。在 Win32 系统中,每个进程拥有自己的 32 位线性地址空间,如果一个 DLL 被多个进程调用,每个进程都会收到该 DLL 的 一份映像。与 16 位 Windows 不同,在 Win32 中 DLL 可以看作是每个进程自己的代码。

      二、动态链接库的优点

       1 . 共享代码、资源和数据

       使用 DLL 的主要目的就是为了共享代码, DLL 的代码可以被所有的 Windows 应用程序共享。

       2 . 隐藏实现的细节

        DLL 中的例程可以被应用程序访问,而应用程序并不知道这些例程的细节。

       3 . 拓展开发工具如 Delphi 的功能

      由于 DLL 是与语言无关的,因此可以创建一个 DLL ,被 C++ 、 VB 或任何支持动态链接库的语言调用。这样如果一种语言存在不足,就可以通过访问另一种语言创建的 DLL 来弥补。

      三、动态链接库的实现方法

       1 . Load-time Dynamic Linking

      这种用法的前提是在编译之前已经明确知道要调用 DLL 中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含 DLL 函数的代码;当程序执行时,利用链接信息加载 DLL 函数代码并在内存中将其链接入调用程序的执行空间中,其主要目的是便于代码共享。

       2 . Run-time Dynamic Linking

      这种方式是指在编译之前并不知道将会调用哪些 DLL 函数,完全是在运行过程中根据需要决定应调用哪个函数,并用 LoadLibrary 和 GetProcAddress 动态获得 DLL 函数的入口地址。
    四、DLL 的两种调用方式在Delphi 中的比较

       编写DLL 的目的是为了输出例程供其他程序调用,因此在DLL 的工程文件中要把输出的例程用Exports 关键字引出。在调用DLL 的应用程序中,需要 声明用到的DLL 中的方法,声明格式要和DLL 中的声明一样。访问DLL 中的例程有静态调用和动态调用两种方式。静态调用方式就是在单元的 Interface 部分用External 指示字列出要从DLL 中引入的例程;动态调用方式就是通过调用Windows 的API 包括 LoadLibrary 函数、GetProcAddress 函数以及FreeLibrary 函数动态的引入DLL 中的例程。

      静态调用 方式所需的代码较动态调用方式所需的少,但存在着一些不足,一是如果要加载的DLL 不存在或者DLL 中没有要引入的例程,这时候程序就自动终止运行;二是 DLL 一旦加载就一直驻留在应用程序的地址空间,即使DLL 已不再需要了。动态调用方式就可解决以上问题,它在需要用到DLL 的时候才通过 LoadLibrary 函数引入,用完后通过FreeLibrary 函数从内存中卸载,而且通过调GetProcAddress 函数可以指定不同的例程。 最重要的是,如果指定的DLL 出错,至多是API调用失败,不会导致程序终止。以下将通过具体的实例说明说明这调用方式的使用方法。

      1 . 静态调用方式

      示例程序创建了一个DLL ,其中仅包含一个求两个整数的和的函数,在主程序中输入两个整数,通过调用该DLL ,即可求出两个整数的和,如图1 所示。


      该DLL 的程序代码如下:
    library AddNum;usesSysUtils,Classes;{$R *.res}function AddNumber(Num1,Num2:integer):integer;stdcall; // 定义求和函数 begin  result:=Num1+Num2; end;  exports  AddNumber; // 引出求和函数 beginend.

      主程序在调用该DLL 时,首先在interface 部分声明要调用的函数:
    function AddNum(Num1,Num2:integer):integer;stdcall;external 'AddNum.dll'name 'AddNumber';

      然后在按钮控件的事件中写入如下代码:
    procedure TForm1.Button1Click(Sender: TObject);var Number1,Number2:integer;  Sum:integer;begin Number1:=strtoint(Edit1.Text); Number2:=strtoint(Edit2.Text); Sum:=AddNum(Number1,Number2); // 调用求和函数计算结果 Edit3.Text:=inttostr(Sum);end;
     2 .动态调用方式

      这个示例程序创建了一个显示日期的DLL ,其中包含一个窗体,如图2 所示。


      程序中定义了一个ShowCalendar 函数,返回在这个窗体中设定的日期。函数定义如下:
    function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime;var DLLForm: TDLLForm;begin Application.Handle := AHandle; DLLForm := TDLLForm.Create(Application); // 创建并显示窗体 try  DLLForm.Caption := ACaption;  DLLForm.ShowModal; // 显示方式为模式化  Result := DLLForm.calDLLCalendar.CalendarDate; // 返回设定日期 finally  DLLForm.Free; // 用完后卸载该窗体 end;end;

      在DLL 的工程文件中用exports ShowCalendar; 语句引出该函数。下面通过一个简单的应用程序测试一下该DLL 文件。新建一个工程文件,在窗体中放置一个Label 控件和一个按钮控件,在按钮控件的OnClick 事件中编写如下代码:
    procedure TMainForm.Button1Click(Sender: TObject);var OneHandle : THandle; // 定义一个句柄变量begin OneHandle := LoadLibrary('Clendar.dll'); // 动态载入DLL ,并返回其句柄 try  if OneHandle <> 0 then // 如果载入成功则获取ShowCalendar 函数的地址   @ShowCalendar := GetProcAddress(OneHandle, 'ShowCalendar');   if not (@ShowCalendar = nil) then    // 如果找到该函数则在主窗体的Label1 中显示DLL 窗体中设定的日期    Label1.Caption := DateToStr(ShowCalendar(Application.Handle, Caption))   else    RaiseLastWin32Error; finally  FreeLibrary(OneHandle); // 调用完毕收回DLL 占用的资源 end;end;

       从以上程序中可以看到DLL 的动态调用方式比静态调用方式的优越之处。DLL 例程在用到时才被调入,用完后就被卸载,大大减少了系统资源的占用。在调用 LoadLibrary 函数时可以明确指定DLL 的完整路径,如果没有指定路径,运行时首先查找应用程序载入的目录,然后是Windows 系统的 System 目录和环境变量Path 设定的路径。

      五、结束语

      由于动态链接库可以实现代码和资源的共享,大大减少系统资源的占用,因此在当今的应用程序开发中起着非常重要的作用。Delphi 是现今流行的应用软件开发工具,本文就如何在Delphi 中使用动态链接库给出了一定程度上的阐述。

    引入文件


    DLL比较复杂时,可以为它的声明专门创建一个引入单元,这会使该DLL变得更加容易维护和查看。引入单元的格式如下:

      unit MyDllImport; {Import unit for MyDll.dll }
      interface
      procedure MyDllProc;
    
    …
    
    implementation
       procedure MyDllProc;external 'MyDll' index 1;
    
    …
    
    end.
    View Code

    这样以后想要使用MyDll中的例程时,只要简单的在程序模块中的uses子句中加上MyDllImport即可。其实这仅仅是种方便开发的技巧,大家打开Windows等引入windows API的单元,可以看到类似的做法。

    动态(显式)调用DLL


    前面讲述静态调用DLL时提到,DLL会在启动调用程序时即被调入。所以这样的做法只能起到公用DLL以及减小运行文件大小的作用,而且DLL装载出错会立刻导致整个启动过程终止,哪怕该DLL在运行中只起到微不足道的作用。

    使用动态调用DLL的方式,仅在调用外部例程时才将DLL装载内存(引用记数为0时自动将该DLL从内存中清除),从而节约了内存空间。而且可以判断装载是否正确以避免调用程序崩溃的情况,最多损失该例程功能而已。

    动态调用虽然有上述优点,但是对于频繁使用的例程,因DLL的调入和释放会有额外的性能损耗,所以这样的例程则适合使用静态引入。

    调用范例

    DLL动态调用的原理是首先声明一个函数/过程类型并创建一个指针变量。为了保证该指针与外部例程指针一致以确保赋值正确,函数/过程的声明必须和外部例程的原始声明兼容(兼容的意思是1、参数名称可以不一样;2、参数/返回值类型至少保持可以相互赋值,比如原始类型声明为Word,新的声明可以为Integer,假如传递的实参总是在Word的范围内,就不会出错)。

    接下来通过windows API函数LoadLibrary引入指定的库文件,LoadLibrary的参数是DLL文件名,返回一个THandle。如果该步骤成功,再通过另一个API函数GetProcAddress获得例程的入口地址,参数分别为LoadLibrary的指针和例程名,最终返回例程的入口指针。将该指针赋值给我们预先定义好的函数/过程指针,然后就可以使用这个函数/过程了。记住最后还要使用API函数FreeLibrary来减少DLL引用记数,以保证DLL使用结束后可以清除出内存。这三个API函数的Delphi声明如下:

    Function LoadLibrary(LibFileName:PChar):THandle;
    
    Function GetProcAddress(Module:THandle;ProcName:PChar):TfarProc;
    
    Procedure FreeLibrary(LibModule:THandle);
    View Code

    将前面静态调用DLL例程的代码更改为动态调用,如下所示:

    type
    
    TDllProc = function (PathName : Pchar):boolean;stdcall;
    
    var
    
    LibHandle: THandle;
    
    DelPath : TDllProc;
    
    begin
    
    LibHandle := LoadLibrary(PChar('FileOperate.dll'));
    
    if LibHandle >= 32 then begin
    
    try
    
    DelPath := GetProcAddress(LibHandle,PChar('DeleteDir'));
    
    if DirectoryExists(ShellTreeView.Path) then
    
    if Application.MessageBox(Pchar('确定删除目录'+QuotedStr(ShellTreeView.Path)+'吗?'), 'Information',MB_YESNO) = IDYes then
    
    if DelPath(PChar(ShellTreeView.Path)) then
    
    showmessage('删除成功');
    
    finally
    
    FreeLibrary(LibHandle);
    
    end;
    
    end;
    
    end;
    View Code

    16位DLL的动态调入

    下面将演示一个16位DLL例程调用的例子,该例程是windows9x中的一个隐藏API函数。代码混合了静态、动态调用两种方式,除了进一步熟悉外,还可以看到调用16位DLL的解决方法。先解释一下问题所在:

    我要实现的功能是获得win9x的“系统资源”。在winNT/2000下是没有“系统资源”这个概念的,因为winNT/2000中堆栈和句柄不再象win9X那样被限制在64K大小。为了取该值,可以使用win9x的user dll中一个隐藏的API函数GetFreeSystemResources。

    该DLL例程必须动态引入。如果静态声明的话,在win2000里执行就会立即出错。这个兼容性不解决是不行的。所以必须先判断系统版本,如果是win9x再动态加载。检查操作系统版本的代码是:

    var
    
    OSversion : _OSVERSIONINFOA;
    
    FWinVerIs9x: Boolean;
    
    begin
    
    OSversion.dwOSVersionInfoSize := sizeof(_OSVERSIONINFOA);
    GetVersionEx(OSversion);
    FWinVerIs9x := OSversion.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;
    
    End;
    View Code

    以上直接调用API函数,已在Windows单元中被声明。

    function LoadLibrary16(LibraryName: PChar): THandle; stdcall; external kernel32 index 35;
    
    procedure FreeLibrary16(HInstance: THandle); stdcall; external kernel32 index 36;
    
    function GetProcAddress16(Hinstance: THandle; ProcName: PChar): Pointer; stdcall; external kernel32 index 37;
    
     
    
    function TWinResMonitor.GetFreeSystemResources(SysResource: Word): Word;
    
    type
    
    TGetFreeSysRes = function (value : integer):integer;stdcall;
    
    TQtThunk = procedure();cdecl;
    
    var
    
    ProcHandle : THandle;
    
    GetFreeSysRes : TGetFreeSysRes;
    
    ProcThunkH : THandle;
    
    QtThunk : TQtThunk;
    
    ThunkTrash: array[0..$20] of Word;
    
    begin
    Result := 0;
    ThunkTrash[0] := ProcHandle;
    if FWinVerIs9x then begin
    ProcHandle := LoadLibrary16('user.exe');
    if ProcHandle >= 32 then begin
    GetFreeSysRes := GetProcAddress16(ProcHandle,Pchar('GetFreeSystemResources'));
    if assigned(GetFreeSysRes) then begin
    ProcThunkH := LoadLibrary(Pchar('kernel32.dll'));
    if ProcThunkH >= 32 then begin
    QtThunk := GetProcAddress(ProcThunkH,Pchar('QT_Thunk'));
    if assigned(QtThunk) then
    asm
    push SysResource //push arguments
    mov edx, GetFreeSysRes //load 16-bit procedure pointer
    call QtThunk //call thunk
    mov Result, ax //save the result
    end;
    end;
    FreeLibrary(ProcThunkH);
    
    end;
    end;
    FreeLibrary16(ProcHandle);
    end
    else Result := 100;
    end;
    View Code

    首先,LoadLibrary16等三个API是静态声明的(也可以动态声明,我这么做是为了减少代码)。由于LoadLibrary无法正常调入16位的例程(微软啊!),所以改用 LoadLibrary16、FreeLibrary16、GetProcAddress16,它们与LoadLibrary、FreeLibrary、GetProcAddress的意义、用法、参数都一致,唯一不同的是必须用它们才能正确加载16位的例程。

    在定义部分声明了函数指针TGetFreeSysRes 和TQtThunk。Stdcall、cdecl参数定义堆栈的行为,必须根据原函数定义,不能更改。

    假如类似一般的例程调用方式,跟踪到这一步:if assigned(GetFreeSysRes) then begin GetFreeSysRes已经正确加载并且有了函数地址,却无法正常使用GetFreeSysRes(int)!!!
    所以这里动态加载(理由也是在win2k下无法执行)了一个看似多余的过程QT_Thunk。对于一个32位的外部例程,是不需要QT_Thunk的, 但是,对于一个16位的例程,就必须使用如上汇编代码(不清楚的朋友请参考Delphi语法资料)

    asm 
    push SysResource 
    mov edx, GetFreeSysRes 
    call QtThunk 
    mov Result, ax
    end; 
    View Code

    它的作用是将压入参数压入堆栈,找到GetFreeSysRes的地址,用QtThunk来转换16位地址到32位,最后才能正确的执行并返回值!
    以上16位DLL的部分在小仓系列中曾经提到过
    Delphi开发DLL常见问题
    字符串参数
    前面曾提到过,为了保证DLL参数/返回值传递的正确性,尤其是为C++等其他语言开发的宿主程序使用时,应尽量使用指针或基本类型,因为其他语言与Delphi的变量存储分配方法可能是不一样的。C++中字符才是基本类型,串则是字符型的线形链表。所以最好将string强制转换为Pchar。
    如果DLL和宿主程序都用Delphi开发,且使用string(还有动态数组,它们的数据结构类似)作为导出例程的参数/返回值,那么添加ShareMem为工程文件uses语句的第一个引用单元。ShareMem是Borland共享的内存管理器Borlndmm.dll的接口单元。引用该单元的DLL的发布需要包括Borlndmm.dll,否则就得避免使用string。
    初始化COM库
    如果在DLL中使用了TADOConnection之类的COM组件,或者ActiveX控件,调用时会提示 “标记没有引用存储”等错误,这是因为没有初始化COM。DLL中不会调用CoInitilizeEx,初始化COM库被认为是应用程序的责任,这是Borland的实现策略。
    你需要做的是1、引用Activex单元,保证CoInitilizeEx函数被正确调用了
    2、在单元级加入初始化和退出代码:

    initialization
    Coinitialize(nil);
    finalization
    CoUninitialize;
    end.
    View Code

    3、 在结束时记住将连接和数据集关闭,否则也会报地址错误。
    在DLL中建立及显示窗体
    凡是基于窗体的Delphi应用程序都自动包含了一个全局对象Application,这点大家是很熟悉的。值得注意的是Delphi创建的DLL同样有一个独立的Application。所以若是在DLL中创建的窗体要成为应用程序的模式窗体的话,就必须将该Application替换为应用程序的,否则结果难以预料(该窗体创建后,对它的操作比如最小化将不会隶属于任何主窗体)。在DLL中要避免使用ShowMessage而用MessageBox。
    创建DLL中的模式窗体比较简单,把Application.Handle属性作为参数传递给DLL例程,将该句柄赋与Dll的Application.Handle,然后再用Application创建窗体就可以了。
    无模式窗体则要复杂一些,除了创建显示窗体例程,还必须有一个对应的释放窗体例程。对于无模式窗体需要十分小心,创建和释放例程的调用都需在调用程序中得到控制。这有两层意思:一要防止同一个窗体实例的多次创建;二由应用程序创建一个无模式窗体必须保证由应用程序释放,否则假如DLL中有另一处代码先行释放,再调用释放例程将会失败。
    下面是DLL窗体的代码:

    unit uSampleForm;
    interface
    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    
    Dialogs, ExtCtrls, StdCtrls;
    
    type
    TSampleForm = class(TForm)
    Panel: TPanel;
    end;
    procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);export;stdcall;
    function CreateAndShowForm(AppHandle : THandle):LongInt;export;stdcall;
    procedure CloseShowForm(AFormRef : LongInt);export;stdcall;
    implementation
    {$R *.dfm}
    
    //模式窗体
    
    procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);
    
    var
    Form : TSampleForm;
    str : string;
    begin
    Application.Handle := AppHandle;
    Form := TSampleForm.Create(Application);
    try
    str := Caption;
    Form.Caption := str;
    Form.ShowModal;
    finally
    Form.Free;
    end;
    end;
    //非模式窗体
    function CreateAndShowForm(AppHandle : THandle):LongInt;
    var
    Form : TSampleForm;
    begin
    Application.Handle := AppHandle;
    Form := TSampleForm.Create(Application);
    Result := LongInt(Form);
    Form.Show;
    end;
    procedure CloseShowForm(AFormRef : LongInt);
    begin
    if AFormRef > 0 then
    TSampleForm(AFormRef).Release;
    end;
    end.
    View Code

    DLL工程单元的引出声明:

    exports
    CloseShowForm,
    CreateAndShowForm,
    CreateAndShowModalForm;
    View Code

    应用程序调用声明:

    procedure CreateAndShowModalForm(Handle : THandle;Caption : PChar);stdcall;external 'FileOperate.dll';
    
    function CreateAndShowForm(AppHandle : THandle):LongInt;stdcall;external 'FileOperate.dll';
    
    procedure CloseShowForm(AFormRef : LongInt);stdcall;external 'FileOperate.dll';
    View Code

    除了普通窗体外,怎么在DLL中创建TMDIChildForm呢?其实与创建普通窗体类似,不过这次需要传递调用程序的Application.MainForm作为参数:

    function ShowForm(mainForm:TForm):integer;stdcall
    var
    Form1: TForm1;
    ptr:PLongInt;
    begin
    ptr:=@(Application.MainForm);//先把DLL的MainForm句柄保存起来,也无须释放,只不过是替换一下
    ptr^:=LongInt(mainForm);//用调用程序的mainForm替换DLL的MainForm
    Form1:=TForm1.Create(mainForm);//用参数建立
    end;
    View Code

    代码中用了一个临时指针的原因在Application.MainForm是只读属性。MDI窗体的FormStyle不用设为fmMDIChild。
    引出DLL中的对象
    从DLL窗体的例子中可以发现,将句柄做为参数传递给DLL,DLL能指向这个句柄的实例。同样的道理,从DLL中引出对象,基本思路是通过函数返回DLL中对象的指针,将该指针赋值到宿主程序的变量,使该变量指向内存中某对象的地址。对该变量的操作即对DLL中的对象的操作。
    本文不再详解代码,仅说明需要注意的几点规则:
    1、 应用程序只能访问对象中的虚拟方法,所以要引用的对象方法必须声明为虚方法;
    2、 DLL和应用程序中都需要相同的对象及方法定义,且方法定义顺序必须一致;
    3、 DLL中的对象无法继承;
    4、 对象实例只能在DLL中创建。
    声明虚方法的目的不是为了重载,而是为了将该方法加入虚拟方法表中。对象的方法与普通例程是不同的,这样做才能让应用程序得到方法的指针。

    DLL毕竟是结构化编程时代的产物,基于函数级的代码共享,实现对象化已经力不从心。现在类似DLL功能,但对对象提供强大支持的新方式已经得到普遍应用,象接口(COM/DCOM/COM+)之类的技术。进程内的服务端程序从外表看就是一个dll文件,但它不通过外部例程引出应用,而是通过注册发布一系列接口来提供支持。它与DLL从使用上有两个较大区别:需要注册,通过创建接口对象调用服务。可以看出,DLL虽然通过一些技巧也可以引出对象,但是使用不便,而且常常将对象化强制转为过程化的方式,这种情况下最好考虑新的实现方法。

    动态调用Dll

    用Delphi制作DLL
    一 Dll的制作一般步骤
    二 参数传递
    三 DLL的初始化和退出清理[如果需要初始化和退出清理]
    四 全局变量的使用
    五 调用静态载入
    六 调用动态载入
    七 在DLL建立一个TForM
    八 在DLL中建立一个TMDIChildForM
    九 示例:
    十 Delphi制作的Dll与其他语言的混合编程中常遇问题:
    十一 相关资料

    一 Dll的制作一般分为以下几步:
    1 在一个DLL工程里写一个过程或函数
    2 写一个Exports关键字,在其下写过程的名称。不用写参数和调用后缀。
    二 参数传递
    1 参数类型最好与window C++的参数类型一致。不要用DELPHI的数据类型。
    2 最好有返回值[即使是一个过程],来报出调用成功或失败,或状态。成功或失败的返回值最好为1[成功]或0[失败].一句话,与windows c++兼容。
    3 用stdcall声明后缀。
    4 最好大小写敏感。
    5 无须用far调用后缀,那只是为了与windows 16位程序兼容。

    三 DLL的初始化和退出清理[如果需要初始化和退出清理]
    1 DLLProc[SysUtils单元的一个Pointer]是DLL的入口。在此你可用你的函数替换了它的入口。但你的函数必须符合以下要求[其实就是一个回调函数]。如下:
    procedure DllEnterPoint(dwReason: DWORD);far;stdcall;
    dwReason参数有四种类型:
    DLL_PROCESS_ATTACH:进程进入时
    DLL_PROCESS_DETACH进程退出时
    DLL_THREAD_ATTACH 线程进入时
    DLL_THREAD_DETACH 线程退出时
    在初始化部分写:
    DLLProc := @DLLEnterPoint;
    DllEnterPoint(DLL_PROCESS_ATTACH);
    2 如Form上有TdcomConnection组件,就Uses Activex,在初始化时写一句CoInitialize (nil);
    3 在退出时一定保证DcomConnection.Connected := False,并且数据集已关闭。否则报地址错。

    四 全局变量的使用
    在widnows 32位程序中,两个应用程序的地址空间是相互没有联系的。虽然DLL在内存中是一份,但变量是在各进程的地址空间中,因此你不能借助dll的全局变量来达到两个应用程序间的数据传递,除非你用内存映像文件。

    五 调用静态载入
    1 客户端函数声名:
    1)大小写敏感。
    2)与DLL中的声明一样。
    如: showform(form:Tform);Far;external'yproject_dll.dll';
    3)调用时传过去的参数类型最好也与windows c++一样。
    4)调用时DLL必须在windows搜索路径中,顺序是:当前目录;Path路径;windows;widowssystem;windowsssystem32;

    六 调用动态载入
    1 建立一种过程类型[如果你对过程类型的变量只是一个指针的本质清楚的话,你就知道是怎么回事了]。如:

    type 
    mypointer=procedure(form:Tform);Far;external; 
    var 
    Hinst:Thandle; 
    showform:mypointer; 
    begin 
    Hinst:=loadlibrary('yproject_dll');//Load一个Dll,按文件名找。 
    showform:=getprocaddress(Hinst,'showform');//按函数名找,大小写敏感。如果你知道自动化对象的本质就清楚了。 
    showform(application.mainform);//找到函数入口指针就调用。 
    Freelibrary(Hinst); 
    end;
    View Code

    七 在DLL建立一个TForM
    1 把你的Form Uses到Dll中,你的Form用到的关联的单元也要Uses进来[这是最麻烦的一点,因为你的Form或许Uses了许多特殊的单元或函数]
    2 传递一个Application参数,用它建立Form.

    八 在DLL中建立一个TMDIChildForM
    1 Dll中的MDIForm.FormStyle不用为fmMDIChild.
    2 在CreateForm后写以下两句:

    function ShowForm(mainForm:TForm):integer;stdcall 
    var 
    Form1: TForm1; 
    ptr:PLongInt; 
    begin 
    ptr:=@(Application.MainForm);//先把dll的MainForm句柄保存起来,也无须释放,只不过是替换一下 
    ptr^:=LongInt(mainForm);//用主调程序的mainForm替换DLL的MainForm。MainForm是特殊的WINDOW,它专门管理Application中的Forms资源. 
    //为什么不直接Application.MainForm := mainForm,因为Application.MainForm是只读属性 
    Form1:=TForm1.Create(mainForm);//用参数建立 
    end; 
    View Code

    备注:参数是主调程序的Application.MainForm

    九 示例:
    DLL源代码:

    library Project2;
    
    uses 
    SysUtils, 
    Classes, 
    Dialogs, 
    Forms, 
    Unit2 in 'Unit2.pas' {Form2};
    
    {$R *.RES} 
    var 
    ccc: Pchar;
    
    procedure OpenForm(mainForm:TForm);stdcall; 
    var 
    Form1: TForm1; 
    ptr:PLongInt; 
    begin 
    ptr:=@(Application.MainForm); 
    ptr^:=LongInt(mainForm); 
    Form1:=TForm1.Create(mainForm); 
    end;
    
    procedure InputCCC(Text: Pchar);stdcall; 
    begin 
    ccc := Text; 
    end;
    
    procedure ShowCCC;stdcall; 
    begin 
    ShowMessage(String(ccc)); 
    end;
    
    exports 
    OpenForm; 
    InputCCC, 
    ShowCCC; 
    begin 
    end.
    View Code

    调用方源代码:

    unit Unit1;
    
    interface
    
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
    StdCtrls;
    
    type 
    TForm1 = class(TForm) 
    Button1: TButton; 
    Button2: TButton; 
    Edit1: TEdit; 
    procedure Button1Click(Sender: TObject); 
    procedure Button2Click(Sender: TObject); 
    private 
    { Private declarations } 
    public 
    { Public declarations } 
    end;
    
    var 
    Form1: TForm1;
    
    implementation
    
    {$R *.DFM} 
    procedure OpenForm(mainForm:TForm);stdcall;External'project2.dll'; 
    procedure ShowCCC;stdcall;External'project2.dll'; 
    procedure InputCCC(Text: Pchar);stdcall;External'project2.dll';
    
    procedure TForm1.Button1Click(Sender: TObject); 
    var 
    Text: Pchar; 
    begin 
    Text := Pchar(Edit1.Text); 
    // OpenForm(Application.MainForm);//为了调MDICHILD 
    InputCCC(Text);//为了实验DLL中的全局变量是否在各个应用程序间共享 
    end;
    
    procedure TForm1.Button2Click(Sender: TObject); 
    begin 
    ShowCCC;//这里表明WINDOWS 32位应用程序DLL中的全局变量也是在应用程序地址空间中,16位应用程序或许不同,没有做实验。 
    end;
    View Code

    十 Delphi制作的Dll与其他语言的混合编程中常遇问题:
    1 与PowerBuilder混合编程
    在定义不定长动态数组方面在函数退出清理堆栈时老出现不可重现的地址错,原因未明,大概与PB的编译器原理有关,即使PB编译成二进制代码也如此

  • 相关阅读:
    继承-方法重写
    继承2
    继承
    JAVA-基本数据类型与引用数据类型区别
    JS判断一个数是否为质数
    Yslow
    Sublime Less 自动编译成css
    chrom调试
    解决在微信中部分IOS不能自动播放背景音乐
    常用的jq插件
  • 原文地址:https://www.cnblogs.com/blogpro/p/11344314.html
Copyright © 2011-2022 走看看