• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

delphi制作dll

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

一 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 建立一种过程类型(或者是一个Function)[如果你对过程类型的变量只是一个指针的本质清楚的话,你就知道是怎么回事了]。如:

type
mypointer=procedure(form:Tform);Far;external; 
//mypointer=function(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编译成二进制代码也如此。 



在DELPHI中编写DLL时,如果DLL有创建ADO对象要被调用函数

开始处写:CoInitialize(nil);
结束时写:CoUninitialize;
如要返回字符串要用PChar,最好用PChar用out或var方式返回,

PChar的内存分配和释放在调用函数处理:GetMem(p, Size);FreeMem(p);

procedure CommonDLL(AHnd: THandle; //AApp: TApplication;
ADllFileName: PChar;
AConnStr: PChar; AUserID: integer; ABillTypeID: integer);
var
  LPtr:PLongint;
  strCon: widestring;
  strDllFileName: string;
begin
  CoInitialize(nil);
  strCon := StrPas(AConnstr);
  strDllFileName := StrPas(ADllFileName);
try
  Application.Handle := AHnd;
  Screen := AScr;
  LPtr := @Application.Mainform;
  LPtr^ := Longint(AApp.Mainform);

finally
end;
  CoUninitialize;
end;


在加载DLL时要保存原来的句柄,退出DLL时还原句柄:
var
OldHnd: THandle;
// OldApp: TApplication;
OldScr: TScreen;

procedure InitDll(dWseason: DWORD);
begin
  case dWseason of
  DLL_PROCESS_ATTACH:
begin
  OldHnd := Application.Handle;
// OldApp := Application;
  OldScr := Screen;
end;
DLL_PROCESS_DETACH:
begin
  Application.Handle := OldHnd;
// OldApp := GApplication;
  OldScr := GScreen;
end;
end;
end;

begin
  DllProc := @InitDll;
  InitDll(DLL_PROCESS_ATTACH);
end.


在DLL被用函数中有创建窗体对象,一定要记得传Application或者Application.Handle。
DLL中的窗体一般要用formClass.Create(application)来创建比较好。

我见过有DLL会传Screen对象到DLL,这个我不没具体了解为什么,也希望有知道朋友告诉我有一下传Screen对象有什么作用,有什么优缺点。



传Screen是为了使主程序主窗体的MDIChildCount正常增加,
否则,不管打开多少DLL中的MDIChild窗体,MDIChildCount都不会增加。


第一章 为什么要使用动态链接库(DLL) top 
提起DLL您一定不会陌生,在Windows中有着大量的以DLL为后缀的文件,它们是保证Windows正常运行和维护升级的重要保证。(举个例子,笔者的Win95 System目录下尽有500多个DLL文件。)其实,DLL是一种特殊的可执行文件。说它特殊主要是因为一般它都不能直接运行,需要宿主程序比如*.EXE程序或其他DLL的动态调用才能够使用。简单的说,在通常情况下DLL是经过编译的函数和过程的集合。 
使用DLL技术主要有以下几个原因: 

一、减小可执行文件大小。 
DLL技术的产生有很大一部分原因是为了减小可执行文件的大小。当操作系统进入Windows时代后,其大小已经达到几十兆乃至几百兆。试想如果还是使用DOS时代的单执行文件体系的话一个可执行文件的大小可能将达到数十兆,这是大家都不能接受的。解决的方法就是采用动态链接技术将一个大的可执行文件分割成许多小的可执行程序。 

二、实现资源共享。 
这里指的资源共享包括很多方面,最多的是内存共享、代码共享等等。早期的程序员经常碰到这样的事情,在不同的编程任务中编写同样的代码。这种方法显然浪费了很多时间,为了解决这个问题人们编写了各种各样的库。但由于编程语言和环境的不同这些库一般都不能通用,而且用户在运行程序时还需要这些库才行,极不方便。DLL的出现就像制定了一个标准一样,使这些库有了统一的规范。这样一来,用不同编程语言的程序员可以方便的使用用别的编程语言编写的DLL。另外,DLL还有一个突出的特点就是在内存中只装载一次,这一点可以节省有限的内存,而且可以同时为多个进程服务。 

三、便于维护和升级。 
细心的朋友可能发现有一些DLL文件是有版本说明的。(查看DLL文件的属性可以看到,但不是每一个DLL文件都有)这是为了便于维护和升级。举个例子吧,早期的Win95中有一个BUG那就是在闰年不能正确显示2月29日这一天。后来,Microsoft发布了一个补丁程序纠正了这个BUG。值得一提的是,我们并没有重装Win95,而是用新版本的DLL代替了旧版本的DLL。(具体是哪一个DLL文件笔者一时想不起来了。)另一个常见的例子是驱动程序的升级。例如,著名的DirectX就多次升级,现在已经发展到了6.0版了。更妙的是,当我们试图安装较低版本的DLL时,系统会给我们提示,避免人为的操作错误。例如我们升级某硬件的驱动程序时,经常碰到Windows提示我们当前安装的驱动程序比原来的驱动程序旧。 

四、比较安全。 
这里说的安全也包括很多方面。比如,DLL文件遭受病毒的侵害机率要比普通的EXE文件低很多。另外,由于是动态链接的,这给一些从事破坏工作的“高手”们多少带来了一些反汇编的困难。 

第二章 在Delphi中编写DLL

注意:在这里笔者假定读者使用的是Delphi 3或Delphi 4开场白说了那么多,总该言归正传了。编写DLL其实也不是一件十分困难的事,只是要注意一些事项就够了。为便于说明,我们先举一个例子。 

library Delphi; 

uses 
SysUtils, 
Classes; 

function TestDll(i:integer):integer;stdcall; 
begin 
Result:=i; 
end; 

exports 
TestDll; 

begin 
end. 

只要编译上面的代码,就可以得到一个名为Delphi.dll的动态链接库。

现在,让我们来看看有哪些需要注意的地方。

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

二、所写的函数和过程应该用exports语句声明为外部函数。 
三、当使用了长字符串类型的参数、变量时要引用ShareMem。 
Delphi中的string类型很强大,我们知道普通的字符串长度最大为256个字符,但Delphi中string类型在默认情况下长度可以达到2G。(对,您没有看错,确实是两兆。)这时,如果您坚持要使用string类型的参数、变量甚至是记录信息时,就要引用ShareMem单元,而且必须是第一个引用的。既在uses语句后是第一个引用的单元。如下例: 


uses 
  ShareMem, 
SysUtils, 
Classes; 


还有一点,在您的工程文件(*.dpr)中而不是单元文件(*.pas)中也要做同样的工作,这一点Delphi自带的帮助文件没有说清楚,造成了很多误会。不这样做的话,您很有可能付出死机的代价。避免使用string类型的方法是将string类型的参数、变量等声明为Pchar或ShortString(如:s:string[10])类型。同样的问题会出现在当您使用了动态数组时,解决的方法同上所述。 

第三章 在Delphi中静态调用DLL

调用一个DLL比写一个DLL要容易一些。首先给大家介绍的是静态调用方法,稍后将介绍动态调用方法,并就两种方法做一个比较。同样的,我们先举一个静态调用的例子。 

unit Unit1; 

interface 

uses 
Windows, Messages, SysUtils, Classes, Graphics, 
Controls, forms, Dialogs, StdCtrls; 

type 
Tform1 = class(Tform) 
Edit1: TEdit; 
Button1: TButton; 
procedure Button1Click(Sender: TObject); 
private 
{ Private declarations } 
public 
{ Public declarations } 
end; 

var 
form1: Tform1; 

implementation 

{$R *.DFM} 

//本行以下代码为我们真正动手写的代码 

function TestDll(i:integer):integer;stdcall; external ’Delphi.dll’; 

procedure Tform1.Button1Click(Sender: TObject); 
begin 
  Edit1.Text:=IntToStr(TestDll(1)); 
end; 

end. 



 

一、调用参数用stdcall。 
和前面提到的一样,当引用DLL中的函数和过程时也要使用stdcall参数,原因和前面提到的一样。 

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

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

四、被调用的DLL必须存在。 


第四章 在Delphi中动态调用DLL

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

#include 

extern ”C” _declspec(dllexport) 
int WINAPI TestC(int i) 

return i; 


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

procedure Tform1.Button1Click(Sender: TObject); 
type 
TIntFunc=function(i:integer):integer;stdcall; 
var 
  Th:Thandle; 
  Tf:TIntFunc; 
  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调用参数。 

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

两种调用DLL的方法的优缺点

静态方法实现简单,易于掌握并且一般来说稍微快一点,也更加安全可靠一些;

但是静态方法不能灵活地在运行时装卸所需的DLL,而是在主程序开始运行时就装载指定的DLL直到程序结束时才释放该DLL,另外只有基于编译器和链接器的系统(如Delphi)才可以使用该方法。

动态方法较好地解决了静态方法中存在的不足,可以方便地访问DLL中的函数和过程,甚至一些老版本DLL中新添加的函数或过程;

但动态方法难以完全掌握,使用时因为不同的函数或过程要定义很多很复杂的类型和调用方法。对于初学者,笔者建议您使用静态方法,待熟练后再使用动态调用方法。 


第五章 使用DLL的实用技巧

一、编写技巧。 
1 、为了保证DLL的正确性,可先编写成普通的应用程序的一部分,调试无误后再从主程序中分离出来,编译成DLL。 

2 、为了保证DLL的通用性,应该在自己编写的DLL中杜绝出现可视化控件的名称,如:Edit1.Text中的Edit1名称;或者自定义非Windows定义的类型,如某种记录。 

3 、为便于调试,每个函数和过程应该尽可能短小精悍,并配合具体详细的注释。 

4 、应多利用try-finally来处理可能出现的错误和异常,注意这时要引用SysUtils单元。 

5 、尽可能少引用单元以减小DLL的大小,特别是不要引用可视化单元,如Dialogs单元。例如一般情况下,我们可以不引用Classes单元,这样可使编译后的DLL减小大约16Kb。 

二、调用技巧。 
1 、在用静态方法时,可以给被调用的函数或过程更名。在前面提到的C++编写的DLL例子中,如果去掉extern ”C”语句,C++会编译出一些奇怪的函数名,原来的TestC函数会被命名为@TestC$s等等可笑的怪名字,这是由于C++采用了C++ name mangling技术。这个函数名在Delphi中是非法的,我们可以这样解决这个问题: 
改写引用函数为 
function TestC(i:integer):integer;stdcall; 
external ’Cpp.dll’;name ’@TestC$s’; 
其中name的作用就是重命名。 

2 、可把我们编写的DLL放到Windows目录下或者Windows\system目录下。这样做可以在external语句中或LoadLibrary语句中不写路径而只写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} 
//注意,上面这行代码必须加在这个位置 

function TestDll(i:integer):integer;stdcall; 
begin 
  Result:=i; 
end; 

exports 
TestDll; 

begin 
end. 

3 、为了避免与别的DLL重名,在给自己编写的DLL起名字的时候最好采用字符数字和下划线混合的方式。如:jl_try16.dll。 

4 、如果您原来在Delphi 1或Delphi 2中已经编译了某些DLL的话,您原来编译的DLL是16位的。只要将源代码在新的Delphi 3或Delphi 4环境下重新编译,就可以得到32位的DLL了。 

[后记]:除了上面介绍的DLL最常用的使用方法外,DLL还可以用于做资源的载体。例如,在Windows中更改图标就是使用的DLL中的资源。另外,熟练掌握了DLL的设计技术,对使用更为高级的OLE、COM以及ActiveX编程都有很多益处。 


Delphi中如何调用DLL 
马上想得到的使用说明有以下几点:

1. 所需动态连结的 DLL 须置放在与执行档同一目录或Windows System 目录2. 确认 DLL export 出来的函式的原型, 以目前的情况而言, 通常只拿得到 C语言的函数原型,这时要注意 C 与 object Pascal 相对应的型别, 如果需要, 在interface 一节定义所需的资料类别

3. 在 implementation 节中宣告欲使用的函式, 语法大致如下:

procedure ProcName(Argu...); far; external ’DLL档名’;

index n;

function FuncName(Argr...): DataType; far;

external ’DLL档名’; index n;

宣告时, index n 如果不写, 便是参考资料中所谓 import by name 的方式, 此时, 由於需要从 DLL 的 name table 中找出这个函式, 因此, 连结执行速度比import by ordinal稍慢一些, 此外, 还有一种 by new name, 由於我没用过, 您可以查一参考资料, 大意是可以 import 後改用另一个程式命名呼叫这个函式

4. 然後, 呼叫与使用就与一般的Delphi 没有两样5. 上述是直接写到呼叫DLL函式的程式单元中, 此外,也可以将DLL的呼叫宣告集中到一个程式单元(Import unit), Delphi 内附的 WinTypes, WinProcs是一个例子,

您可以参考一下,同时观察一下 C 与 Pascal 互相对应的资料型态6. 除了上述的 static import 的方式, 另外有一种 dynamic import 的写法,先宣告一个程序类型(procedural-type),程式执行时, 以LoadLibrary() API Load进来後, 再以 GetProcAddress() API 取得函式的位址的方式来连结呼叫, 在ObjectPascal Language Guide P.132-133 有一个例子, 您可以参考看看

如果要举个例子, 以下是从我以前的程式节录出来的片断:

(* for CWindows 3.1 *)

unit Ime31;

interface

uses

SysUtils, WinTypes, WinProcs, Dialogs;

type

(* 必要的资料型态宣告 *)

  tDateNTime = record
  wYear, wMonth, wDay: word;
  wHour, wMin, wSec: word;
end;
  TImePro = record
  hWndIme: HWnd; { IME handle }
  dtInstDate: tDateNTime; { Date and time of installation }
  wVersion: word; { the version of IME }

  szDescription: array[0..49] of byte; { Description of IME module}
  szName: array[0..79] of byte; { Module name of the IME }
  szOptions: array[0..29] of byte; { options of IME at startup}
 fEnable: boolean; { IME status; True=activated,False=deactivated }
end;

pTImePro = ^TImePro;

function SetIme(const sImeFileName: string): boolean; far;

implementation

(* begin 呼叫 winnls.dll export 函数的宣告 *)

function ImpSetIme(hWndIme: HWND; lpImePro: pTImePro): boolean;far; external ’winnls.dll’;

(* end 呼叫 winnls.dll export 函数的宣告 *)

(* -------------------------------------------------- *)

(* SetIme(const sImeFileName: string): boolean;

(* ======

(* 切换到某一特定的输入法

(*

(* 传入引数:

(* sImeFileName: 输入法 IME 档名, 例: phon.ime;

(* 空字串: 英数输入法

(*

(* 传回值:

(* True: 切换成功

(* False: 失败

(* -------------------------------------------------- *)

function SetIme(const sImeFileName: string): boolean;
var
  pImePro: pTImePro;
begin
Result := False;
if MaxAvail < SizeOf(TImePro) then
begin
MessageDlg(’记忆体不足’, mtWarning, [mbOk], 0);
Exit;
end
else
begin
New(pImePro);
try
if sImeFileName = ’’ then (* 空字串, 还原到英数输入法 *)
pImePro^.szName[0] := 0
else
StrPCopy(@pImePro^.szName, sImeFileName);
Result := ImpSetIme(0, pImePro); (* 呼叫 ImpSetIme *)
finally
Dispose(pImePro);
end; { of try }
end;
end; { of SetIme }
end.

; 


 

DELPHI 中DLL开发常见问题的讨论:
http://www.delphibbs.com/delphibbs/DispQ.asp?LID=3685176 


2007-4-6 19:25:51 如要返回字符串要用PChar,最好用PChar用out或var方式返回,PChar的内存分配和释放在调用函数处理:GetMem(p, Size); FreeMem(p);

而在被调用函数写的方式应该是:


procedure GetStr(var Pstr: PChar);
var
str: string
begin
str := 'return string';
strCopy(PStr, PChar(str));
end;

调用函数写法:
TGetStr= procedure(var Pstr: PChar);

funtion GetDllStr: string
var
DllHnd: THandle;
GetStr: TGetStr;
Str: PChar;
strPath: string;
begin
AHaveWhere := 0;
DllHnd := LoadLibrary(PChar('testdll'));
try
if (DllHnd <> 0) then
begin
@GetStr :=GetProcAddress(DllHnd, 'GetStr');
if (@GetStr<>nil) then
begin
GetMem(Str, 1024);
try
GetStr(Filter);
result := StrPas(Filter);
finally
FreeMem(Str);
end;
end
else
begin
application.MessageBox(PChar('DLL加载出错,DLL可能不存在!'), PChar('错误'),
MB_ICONWARNING or MB_OK);
end;
end;
finally
FreeLibrary(DllHnd);

end;

一 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 建立一种过程类型(或者是一个Function)[如果你对过程类型的变量只是一个指针的本质清楚的话,你就知道是怎么回事了]。如:

type
mypointer=procedure(form:Tform);Far;external; 
//mypointer=function(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;

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
delphi常用属性列表(不断更新中……)发布时间:2022-07-18
下一篇:
[教学]DelphiIDE文件搜寻功能发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap