用Delphi制作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;widows\system;windows\ssystem32;

六 调用动态载入
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;

七 在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;
备注:参数是主调程序的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.

调用方源代码:
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;

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

 


Windows 的执行文件可以划分为两种形式程序和动态连接库
(DLLs)。一般程序运行是用.EXE文件,但应用程序有时也可以
调用存储在DLL 中的函数。
    当我们调用Windows 中的API 函数的时候,实际上就是调用
存储在DLL 中的函数。
    在如下几种情况下,调用DLL 是合理的:
    1) 不同的程序使用相同的DLL ,这样只需要将DLL 在内存
中装载一次,节省了内存的开销。
    2) 当某些内容需要升级的时候,如果使用DLL 只需要改变
DLL 就可以了,而不需要把整个程序都进行变动。
    3) 由于DLL 是独立于语言的,所以,当不同语言习惯的人
共同开发一个大型项目的时候,使用DLL 便于程序系统的交流,
当然,Delphi开发的DLL 也可以在诸如Visual BASIC,C++ 等系
统中使用。
    下面通过几个例子,说明Delphi开发动态连接库的方法和规
范。    

    第一节 动态连接库的构建和调用方法

    一、动态连接库构建

    File---New---Other---DLL Wizard

    这就创建了一个动态连接库的基本模块

    library Project2;

    uses
      SysUtils,
      Classes;

   {$R *.res}

    begin

    end.

    把工程名改为Mydll,并写入必要的函数

    library mydll;

    uses
      SysUtils,Classes,Dialogs,windows;

    function Triple(N:Integer):integer;stdcall;
    begin
      result:=N+3;
    end;

    function Double(N:Integer):integer;stdcall;
    begin
      result:=N+2;
    end;

    function Triple1(N:Integer):integer;stdcall;
    begin
      showmessage('计算N+3');
      result:=N+3;
    end;

    function Double1(N:Integer):integer;stdcall;
    begin
      messagebox(0,'计算N+2','计算N+2',mb_ok);
      result:=N+2;
    end;

   exports
     Triple name 'Tr',
     Double name 'Do',
     Triple1 name 'TrM',
     Double1 name 'DoM';

    Triple,Double,Triple1,Double1;

  {$R *.RES}

  begin

  end.

    其中函数:Triple:把传入值加三
              Double:把传入值加二
              Triple1:把传入值加三并显示提示
              Double1:把传入值加二并显示提示

    从这个例子中可以看出DLL 程序的几个规则:

    1) 在DLL 程序中,输出函数必须被声明为stdcall,以使用标
准的Win32 参数传递技术来代替优化的Register。
    (说明:在Delphi中Register方式是缺省的调用约定,这个约
定尽量采用寄存器来传递参数,传递次序从左到右,最多可用到3
个CPU 的寄存器,如果参数多于3 个,剩下的就通过栈来传送,使
用寄存器传送可保证参数传递的速度最快。
    而stdcall 方式是通过Windows 的标准调用来传递参数,传递
秩序从左到右,这种方式适合调用Windows 的API ,在DLL 中,当
然要使用这种方式)。

    2)所有的输出函数都必须列在exports子句下面,这使的子例程
在DLL外部就可以看到。

   exports

     Triple name 'Tr',
     Double name 'Do',
     Triple1 name 'TrM',
     Double1 name 'DoM';
   
    列出了用户使用这个函数的接口名字。虽然别名不是必须的,但
最好给个别名,以便用户程序更容易找到这个函数,同时还要指出,
Delphi 6.0取消了Delphi 5.0中允许使用的index ,如果还用Index
来指明接口名字,Delphi 6.0中将提示错误。

    实例中给出了两种提示方法,主要想说明一个问题:
    showmessage(''),是VCL 提供的函数,由于多次编译VCL,做出
的程序会比较大。
    而messagebox(0,'','',mb_ok)  是Windows提供的API 函数,做
出的程序会比较小。
    这就是说,编写DLL 程序的时候,要尽量避免多次编译VCL 。作
为一个实例,这里把两种方法都列出来了。

    保存

    编译:Projrct---Build Mydll

    这就完成了一个简单的动态连接库的编写。

    二、动态连接库的调用

    首先在implementation下做调用声明

const
  gdi32='mydll.dll';

function triple(n:integer):integer;stdcall;external gdi32 name 'Tr';
function Double(N:Integer):integer;stdcall;external gdi32 name 'Do';
function triple1(n:integer):integer;stdcall;external gdi32 name 'TrM';
function Double1(N:Integer):integer;stdcall;external gdi32 name 'DoM';

    以后程序中就可以作为普通的函数使用了,例如:

procedure TForm1.Button1Click(Sender: TObject);
var N:integer;
begin
  N:=updown1.position;
  edit1.text:=inttostr(triple(N));
end;

    第二节 DLL 中的Delphi窗体

    一、在DLL 中放置窗的的方法

    在DLL 中,除了放置标准的函数和过程以外,也可以放置
已经做好的的delphi窗体,也可以把做好的窗体供其它程序使
用,方法是:

   1)首先按普通方法制作窗体,不过在interface区域,对接
口函数做如下声明

   function Createform(capt:string):string;stdcall;
 
   2)在implementation下加入接口函数

function Createform(capt:string):string;stdcall;
var  Form1: TForm1;
begin
  form1:=Tform1.Create(application);
  form1.show;
  form1.caption:=capt;
end;

  3)制作DLL 动态连接库,但要声明:

uses
  unit1 in 'unit1.pas';

exports

{写入接口标示符}
Createform name 'Myform';

  4)调用窗体的程序按普通方法制作,但是 在implementation下首
先声明要调用的DLL函数

const
  gdi32='myFormdll.dll';
  function Createform(capt:string):string;stdcall;external gdi32 name 'Myform';

procedure TForm3.Button1Click(Sender: TObject);
var n,m:string;
begin
  m:='我的窗体';
  Createform(m);var n,m:string;
end;

    二、DLL 中的调用窗体时的数据传递

    

    在窗体调用时,可以用普通的函数方法传递数据,下面举
个例子。

    1)建立窗体

    做一个改变颜色窗体,放在DLL 中,可以用普通的方法来
做,但要作如下声明:

    function mycolor(col:longint):longint;stdcall;
    function Getcolor:longint;stdcall;

    其中,mycolor为构造窗体;Getcolor为传递颜色数据。

    在implementation区声明一个窗体内全局的变量

    var color1:longint;

    下面写出相应的程序

function mycolor(col:longint):longint;stdcall;
var  Form1: TForm1;
begin
  form1:=Tform1.Create(application);
  form1.show;
  form1.panel1.Color:=col;
  form1.edit1.Text:=inttostr(form1.panel1.Color);
  result:=color1;
end;

function Getcolor:longint;stdcall;
begin
  result:=color1;
end;

procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
  panel2.Color:=RGB(ScrollBar1.Position,ScrollBar2.Position,ScrollBar3.Position);
  edit2.Text:=inttostr(panel2.Color);
  color1:=panel2.Color;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
   Free;  //析构Form1
end;

    2)建立动态连接库

    运行成功后,再建立动态连接库:

library FormDLL;

{从文件调入}
uses
  unit1 in 'unit1.pas';

exports

{写入接口标示符}
Mycolor name 'My',
Getcolor name 'Get';

begin
end.

    3)建立调用的程序

    首先声明要调用的DLL函数

const
  gdi32='formDll.dll';
  function Mycolor(col:longint):longint;stdcall;external gdi32 name 'My';
  function Getcolor:longint;stdcall;external gdi32 name 'Get';

    然后写出相应的程序

procedure TForm1.Button1Click(Sender: TObject);
begin
  Mycolor(color);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
   color:=getcolor;
end;

    我们可以看到,在改变颜色的窗体中做了颜色变化后,当前窗
体的颜色将发生变化。

posted @ 2013-01-06 15:55  骑着骆驼逛沙漠  阅读(199)  评论(0编辑  收藏  举报