zoukankan      html  css  js  c++  java
  • Delphi开发DLL

     Delphi开发DLL

    1.一般工程文件的头标用program关键字,而DLLs工程文件头标用library 关键字。不同的关键字通知编译器生成不同的可执行文件。用program关键字生成的是.exe文件,而用library关键字生成的是.dll文件;

      2.假如DLLs要输出供其它应用程序使用的函数或过程,则必须将这些函数或过程列在exports子句中。而这些函数或过程本身必须用export编译指令进行编译。

      根据DLLs完成的功能,我们把DLLs分为如下的三类:

    1.完成一般功能的DLLs;2.用于数据交换的DLLs;3.用于窗体重用的DLLs。

    一 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的入口。在此你可用你的函数替换了它的入口。但你的函数必须符合以下要求[其实就是一个回调函数]。如下:

    procedurefar;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中的声明一样。如:Far;external'yproject_dll.dll';

      3)调用时传过去的参数类型最好也与windows c++一样。

      4)调用时DLL必须在windows搜索路径中,顺序是:当前目录;Path路径;windows;widows\system;windows\ssystem32;

    六 调用动态载入

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

    type

    mypointer=procedure(form:Tform);Far;external;

    var

    Hinst:Thandle;

    begin

    Hinst:=loadlibrary('yproject_dll');//Load一个Dll,按文件名找。

    showform:=getprocaddress(Hinst,'showform');//按函数名找,大小写敏感。如果你知道自动化对象的本质就清楚了。

    showform(application.mainform);//找到函数入口指针就调用。

    Freelibrary(Hinst);

    end;

    例:

    一、开始第一个DLL

      1.File->Close all->File->New﹝DLL﹞

    代码:  //自动产生Code如下

    library Project2;

          uses

          SysUtils,   Classes;

          {$R *.RES}

          begin

          end.

      2.加个Func进来:

          代码:

          library Project2;

          uses

          SysUtils,   Classes;

      Function stdcall ;

      begin

      if X > Y then      Result := X

      else                Result := Y ;

      end ;

    //切记:Library 的名字大小写没关系,可是DLL-Func的大小写就有关系了。 在 DLL-Func-Name写成MyMax与myMAX是不同的。如果写错了,结果是此DLL的AP根本打不开。参数的大小写就没关系了。甚至不必同名。如原型中是 (X,Y:integer)但引用时写成(A,B:integer),那是没关系的。

    //切记:要再加个stdcall。书上讲,如果你是用delphi写DLL,且希望不仅给delphi-AP也希望BCB/VC-AP等使用的话,那你最好加个Stdcall ; 的指示

    //参数型态:delphi有很多种它自己的变量型态,这些当然不是DLL所喜欢的,Windows/DLL的母语是C。所以如果要传进传出DLL的参数,我们尽可能照规矩来用。

      {$R *.RES}

      begin

      end.

      3.将这些可共享的Func送出DLL,让外界﹝就是你的delphi-AP啦﹞使用:光如此,你的AP还不能用到这些,你还要加个Exports才行。

      代码:

      {$R *.RES}

      exports

      MyMax ;

      begin

      end.

      4.好了,可以按 Ctrl-F9编译了。此时可不要按F9。DLL不是EXE不可单独执行的,如果你按F9,会有ErrorMsg的。这时如果DLL有Error,请修正之。再按Ctrl-F9。此时可能有Warning,不要紧,研究一下,看看就好。再按Ctrl-F9,此时就『Done , Compiled 』。同目录就会有个 *.dll 。恭喜,大功告成了。

    二、进行测试:开个新application:

      1.加个TButton

              代码:  ShowMessage ( IntToStr(MyMax(30,50)) ) ;

      2.告知Exe到那里抓个Func

              代码:  //在Form,interface,var后加

      Function stdcall ; external 'MyTestDLL.dll' ;

      // MyTestDLL.dll为你前时写的DLL项目名字

    // DLL名字大小写没关系。不过记得要加 extension的 .DLL。在Win95或NT,是不必加 extension,但这两种OS,可能越来越少了吧。要加extension

    三、dll注意事项

    在delphi 1或delphi 2环境下该调用参数是far。从delphi 3以后将这个参数变为了stdcall,目的是为了使用标准的Win32参数传递技术来代替优化的register参数。

    1.在DLL中编写的函数或过程都必须加上stdcall调用参数。

    2.所写的函数和过程应该用exports语句声明为外部函数。

    3.当使用了长字符串类型的参数、变量时要引用ShareMem。

    一、正如大家看到的,我们在external语句中指定了所要调用的DLL文件的名称。没有写路径是因为该DLL文件和调用它的主程序在同一目录下。如果该DLL文件在C:\,则我们可将上面的引用语句写为external ’C:\delphi.dll’。注意文件的后缀.dll必须写上。

    二、不能从DLL中调用全局变量

      如果我们在DLL中声明了某种全局变量,如:var s:byte 。这样在DLL中s这个全局变量是可以正常使用的,但s不能被调用程序使用,既s不能作为全局变量传递给调用程序。不过在调用程序中声明的变量可以作为参数传递给DLL。

    三、被调用的DLL必须存在

      这一点很重要,使用静态调用方法时要求所调用的DLL文件以及要调用的函数或过程等等必须存在。如果不存在或指定的路径和文件名不正确的话,运行主程序时系统会提示“启动程序时出错”或“找不到*.dll文件”等运行错误。

    四、调试技巧

      1 、我们知道DLL在编写时是不能运行和单步调试的。有一个办法可以,那就是在Run|parameters菜单中设置一个宿主程序。在Local页的Host Application栏中添上宿主程序的名字就可进行单步调试、断点观察和运行了。

      2 、添加DLL的版本信息。开场白中提到了版本信息对于DLL是很重要的,如果包含了版本信息,DLL的大小会增加2Kb。增加这么一点空间是值得的。很不幸我们如果直接使用Project|options菜单中Version选项是不行的,这一点delphi的帮助文件中没有提到,经笔者研究发现,只要加一行代码就可以了。如下例:

    library delphi;

    uses

    SysUtils, Classes;

    {$R *.RES}

    //注意,上面这行代码必须加在这个位置

    functionstdcall;

    begin    Result:=i;    end;

    exports TestDll;

    begin

    end.

    五、调用技巧

      1 、在用静态方法时,可以给被调用的函数或过程更名。在前面提到的C++编写的DLL例子中,如果去掉extern ”C”语句,C++会编译出一些奇怪的函数名,原来的TestC函数会被命名为@TestC$s等等可笑的怪名字,这是由于C++采用了C++ name mangling技术。这个函数名在delphi中是非法的,我们可以这样解决这个问题:

    改写引用函数为

    functionstdcall;

    external ’Cpp.dll’;name ’@TestC$s’;    其中name的作用就是重命名。

      2 、可把我们编写的DLL放到Windows目录下或者Windows\system目录下。这样做可以在external语句中或LoadLibrary语句中不写路径而只写DLL的名称。但这样做有些不妥,这两个目录下有大量重要的系统DLL,如果您编的DLL与它们重名的话其后果简直不堪设想,况且您的编程技术还不至于达到将自己编写的DLL放到系统目录中的地步吧!

    六、在delphi中动态调用DLL top

      动态调用DLL相对复杂很多,但非常灵活。为了全面的说明该问题,这次我们举一个调用由C++编写的DLL的例子。首先在C++中编译下面的DLL源程序。

    #include

    extern ”C” _declspec(dllexport)

    int WINAPI TestC(int i)

    {      return i;      }

      编译后生成一个DLL文件,在这里我们称该文件为Cpp.dll,该DLL中只有一个返回整数类型的函数TestC。为了方便说明,我们仍然引用上面的调用程序,只是将原来的Button1Click过程中的语句用下面的代码替换掉了。

    procedure

    type

    TIntFunc=function(i:integer):integer;stdcall;

    var

    Th:Thandle; Tp:TFarProc;

    begin

    Th:=LoadLibrary(’Cpp.dll’); {装载DLL}

    if Th>0 then

    try

    Tp:=GetProcAddress(Th,PChar(’TestC’));    //按函数名找,大小写敏感。

    if Tp<>nil then

    begin

    Tf:=TIntFunc(Tp);

    Edit1.Text:=IntToStr(Tf(1)); {调用TestC函数}

    end

    else  ShowMessage(’TestC函数没有找到’);

    finally

    FreeLibrary(Th); {释放DLL}

    end

    else    ShowMessage(’Cpp.dll没有找到’);

    end;

      大家已经看到了,这种动态调用技术很复杂,但只要修改参数,如修改LoadLibrary(’Cpp.dll’)中的DLL名称为’delphi.dll’就可动态更改所调用的DLL。

    一、定义所要调用的函数或过程的类型

      在上面的代码中我们定义了一个TIntFunc类型,这是对应我们将要调用的函数TestC的。在其他调用情况下也要做同样的定义工作。并且也要加上stdcall调用参数。

    二、释放 饔玫腄LL

      我们用LoadLibrary动态的调用了一个DLL,但要记住必须在使用完后手动地用FreeLibrary将该DLL释放掉,否则该DLL将一直占用内存直到您退出Windows或关机为止。

      现在我们来评价一下两种调用DLL的方法的优缺点。静态方法实现简单,易于掌握并且一般来说稍微快一点,也更加安全可靠一些;但是静态方法不能灵活地在运行时装卸所需的DLL,而是在主程序开始运行时就装载指定的DLL直到程序结束时才释放该DLL,另外只有基于编译器和链接器的系统(如delphi)才可以使用该方法。动态方法较好地解决了静态方法中存在的不足,可以方便地访问DLL中的函数和过程,甚至一些老版本DLL中新添加的函数或过程;但动态方法难以完全掌握,使用时因为不同的函数或过程要定义很多很复杂的类型和调用方法。对于初学者,笔者建议您使用静态方法,待熟练后再使用动态调用方法。

    七、例1:在DLL建立一个TForM

    1 把你的Form Uses到Dll中,你的Form用到的关联的单元也要Uses进来[这是最麻烦的一点,因为你的Form或许Uses了许多特殊的单元或函数]

    2 传递一个Application参数,用它建立Form.

    在DLL中建立一个TMDIChildForM

    1 Dll中的MDIForm.FormStyle不用为fmMDIChild.

    2 在CreateForm后写以下两句:

    functionstdcall

    var

    Form1: TForm1;  

    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;

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

  • 相关阅读:
    HDU 3123-GCC(递推)
    新交互英语外挂全自己主动版
    BZOJ 2716 Violet 3 天使玩偶 CDQ分治
    关于 FPGA 和 外部芯片接口时序设计
    Ubuntu启动、停止、重新启动MySQL,查看MySQL错误日志、中文编码错误
    Drupal 7 建站学习手记(四):怎样改动Nivo Slider模块的宽高
    Linux下安装Oracle的过程和涉及的知识点-系列4
    游戏开场镜头拉近(Unity3D开发之四)
    并发编程
    给线程发送消息让它执行不同的处理
  • 原文地址:https://www.cnblogs.com/karkash/p/3107867.html
Copyright © 2011-2022 走看看