rkjx's Blog

小窝~~~
文章 - 51,收藏 - , 评论 - 0, trackbacks - 1

2007年03月



    摘要:

调用DLL文件中的FORM,具体实现过程如下:

library Project1;

uses
  SysUtils,
   Classes,Forms,windows,dialogs,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}
function showform(formname:string):boolean;stdcall;
var
  TheClass: TPersistentClass;
  aForm: TForm;
begin
 result:=false;
 {如果您的Dll中有很多FORM,请在这儿注册哦
 RegisterClasses([TForm1,TForm2,TForm3,...]);
 }
 RegisterClasses([    (全文共2619字)——点击此处阅读全文





    摘要:

声明
Public Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Integer, ByVal lpBaseAddress As Integer, ByVal lpBuffer() As Byte, ByVal nSize As Integer, ByRef lpNumberOfBytesWritten As Integer) As Integer

示例:
Private Const PROCESS_VM_READ As Long = &H10
Sub Main()
Dim ph As Integer
Dim i As Integer
Dim h As Integer
Dim buffer(20) As Byte
Dim bytesRead As Integer
'获得进程
ph = OpenProcess(PROCESS_A    (全文共831字)——点击此处阅读全文




[转载]深入Delphi下的DLL编程

深入Delphi下的DLL编程
作者:岑心


引 言

相信有些计算机知识的朋友都应该听说过“DLL”。尤其是那些使用过windows操作系统的人,都应该有过多次重装系统的“悲惨”经历——无论再怎样小心,没有驱动损坏,没有病毒侵扰,仍然在使用(安装)了一段时间软件后,发现windows系统越来越庞大,操作越来越慢,还不时的出现曾经能使用的软件无法使用的情况,导致最终不得不重装系统。这种情况常常是由于dll文件的大量安装和冲突造成的。这一方面说明DLL的不足,另一方面也说明DLL的重要地位,以至我们无法杜绝它的使用。
DLL(动态链接库,Dynamic Link Library)简单来说是一种可通过调用执行的已编译的代码模块。DLL是windows系统的早期产物。当时的主要目的是为了减少应用程序对内存的使用。只有当某个函数或过程需要被使用时,才从硬盘调用它进入内存,一旦没有程序再调用该DLL了,才将其从内存中清除。光说整个windows系统,就包括了成百上千个dll文件,有些dll文件的功能是比较专业(比如网络、数据库驱动)甚至可以不安装的。假如这些功能全部要包括在一个应用程序(Application program)里,windows将是一个数百M大小的exe文件。这个简单的例子很容易解释DLL的作用,而调用DLL带来的性能损失则变得可被忽略不计。
多个应用程序调用同一个DLL,在内存里只有一个代码副本。而不会象静态编译的程序那样每一个都必须全部的被装入。装载DLL时,它将被映射到进程的地址空间,同时使用DLL的动态链接并非将库代码拷贝,而仅仅记录函数的入口点和接口。
同时DLL还能带来的共享的好处。一家公司开发的不同软件可能需要一些公用的函数/过程,这些函数/过程可能是直接的使用一些内部开发的DLL;一些常用的功能则可以直接使用windows的标准DLL,我们常说的windows API就是包含在windows几个公用DLL文件里的函数/过程;理论上(如果不牵涉作者的版权),知道一个DLL的声明及作用(函数定义的输入参数及返回值),我们完全可以在不清楚其实现(算法或编译方式)的情况下直接使用它。
假如一个DLL中函数/过程的算法得到了更新,BUG得到了修正,整个dll文件会得到升级。一般来说为了保证向下兼容,调用声明与返回结果应该保持不变。但实际上,即使是同一家开发的DLL,随着功能的改善,也很难保证某个调用执行完全不变。在使用其他人开发的DLL时这种糟糕情况更加的严重。比如我在一个绘图程序里使用了某著名图形软件商旧版本的DLL包,我所有的调用都是根据他发布的旧版的声明来执行的。假设用户安装了该软件商的一个新软件,导致其中部分DLL被更新升级,假如这些DLL已经有过改动,直接后果将是我的软件不再稳定甚至无法运行!不要轻视这种情况,事实上它是很普遍的,比如windows在修正BUG和升级过程中,就不断改动它包含的那些DLL。往往新版DLL不是简单的增加新的函数/过程,而是更换甚至取消了原有的声明,这时候我们再也无法保证所有程序都运行正常。
DLL除了上面提到的改善计算机资源利用率、增加开发效率、隐藏实现细节外,还可以包含数据和各种资源。比如开发一个软件的多国语言版,就可以使用DLL将依赖于语言的函数和资源分离出来,然后让各地的用户安装不同对应的DLL,以获取本地字符集的支持。再比如一个软件必须的图形、图标等资源,也可以直接放在dll文件中统一安装管理。


创建一个DLL

在进行后面的讲解之前,我想大家应该先清楚一个概念:例程声明的是一个指针变量,调用函数/过程,其实是通过指针转入该函数/过程的执行代码。
我们先尝试用Delphi来建立一个自己的DLL文件。这个DLL包含一个标准的目录删除(包含子目录及文件)函数。

建立DLL
通过Delphi建立一个DLL是很容易的。New一个新Project,选择DLL Wizard,然后会生成一个非常简单的单元。该单元不象一般的工程文件以program开始,而是以library开始的。
该工程单元缺省引用了SysUtils、Classes两个单元。可以直接在该单元的uses之后,begin … end部分之前添加函数/过程代码,也可以在工程中添加包含代码的单元,然后该单元将会被自动uses。
接下来是编写DLL例程的代码。如果是引用单元里的例程,需要通过声明时添加export后缀引出。假如是直接写在library单元中的,则不必再写export了。
最后一步是在library单元的begin语句之上,uses部分及函数定义之下添加exports部分,并列举需要引出的例程名称。注意仅仅是名称,不包含procedure或function关键字,也不需要参数、返回值和后缀。
exports语句后的语法有三种形式(例程指具体的函数/过程):
exports例程名;
exports例程名 index 索引值;
exports例程名 name新名称;
索引值和新名称便于其他程序确定函数地址;也可以不指定,如果没有使用Index关键字,Delphi将按照exports后的顺序从1开始自动分配索引号。Exports后可跟多个例程,之间以逗号分隔。
编译,build最终的dll文件。

需注意的格式
为了保证生成的DLL能正确与C++等语言兼容,需要注意以下几点:
尽量使用简单类型或指针作为参数及返回值的类型。这里的简单类型是指C++的简单类型,所以string字符串类型最好转换成Pchar字符指针。直接使用string的DLL例程在Delphi开发的程序中调用是没有问题的(有资料指出需加入ShareMem做为第一单元以确保正确),但如果使用C++或其他语言开发的程序调用,则不能保证参数传递正确;
虽然过程是允许的,但是最好习惯全部写成函数。过程则返回执行正确与否的true/false;
对于参数的指示字比如const(只读)、out(只写)等等,为保证调用的兼容性,最好使用缺省方式(缺省var,即可读写的地址);
使用stdcall声明后缀,以保证正确的异常处理。16位DLL无法通过这种方式处理异常,所以还得在例程最外层用Try … Except将异常处理掉;
一般不使用far后缀,除非为了保持与16位兼容。

范例代码
DLL工程单元:
library FileOperate;

uses
  SysUtils,
  Classes,
  uDirectory in 'uDirectory.pas';

{$R *.res}

exports
  DeleteDir;

begin
end.

函数功能实现单元:
unit uDirectory;

interface

uses
  Classes, SysUtils;

  function DeleteDir(DirName : Pchar):boolean;export;stdcall;

implementation

function DeleteDir(DirName : Pchar):boolean;
var
  FindFile: TSearchRec;
  s : string;
begin
  s := DirName;
  if copy(s,length(s),1) <> '\' then s := s+ '\';
  if DirectoryExists(s) then begin
    if FindFirst(s + '*.*', faAnyFile, FindFile) = 0 then begin
      repeat
        if FindFile.Attr <> faDirectory then begin
          //文件则删除
          DeleteFile(s + FindFile.Name);
        end
        else begin
          //目录则嵌套自身
          if (FindFile.Name <> '.') and (FindFile.Name <> '..') then
            DeleteDir(Pchar(s + FindFile.Name));
        end;
      until FindNext(FindFile) <> 0;
      FindCLose(FindFile);
    end;
  end;

  Result := RemoveDir(s);
end;

end.

初始化及释放资源
Delphi中初始化有几种方法。一种是利用Unit的Initalization与Finalization这两个小节(不知道“单元小节”?你该先去恶补Delphi语法了)进行该单元中变量的初始化工作。注意,DLL虽然在内存中只有一个副本,但是例程隶属于调用者的不同进程空间。如果想初始化公共变量来达到多进程共享是不可行的,同时也要注意公共变量带来的冲突问题。
二是在library单元的begin … end部分进行DLL的初始化。假如想在DLL结束时有对应代码,则可以利用DLL自动创建的一个ExitProc过程变量,这是一个退出过程的指针。建立一个自己的过程,并将该过程的地址赋与ExitProc。因为ExitProc是DLL创建时就存在的,所以在begin … end部分就应该进行此步操作。同时建立一个临时指针变量保存最初的ExitProc值,在自己的退出过程中将ExitProc值赋回来。这样做是为了进行自己的退出操作后,能完成缺省的DLL退出操作(与在重载的Destory方法中inherated的意义是一样的,完成了自己的destory,还需要进行缺省的父类destory才完整)。
示例如下:
library MyDLL;
  ...
var
OldExitProc: pointer;  //公共变量,为的保存最初的ExitProc指针以便赋回
procedure MyExitProc;
  begin
…//对应初始化的结束代码
    ExitProc := OldExitProc; //自己的退出过程中要记住将ExitProc赋回
end;
  ...
  begin
    ... //初始化代码
    OldExitProc := ExitProc;
    ExitProc := @MyExitProc;
  end.
第三种方法和ExitProc类似,在System单元中预定义了一个指针变量DllProc(该方法需要引用 Windows单元)。在使用DLLProc时, 必须先写好一个具有以下原型的程序:
  procedure DLLHandler(dwReason: DWORD); stdcall;
并在library的begin..end.之间, 将这个DLLHandler程序的执行地址赋给DLLProc中, 这时就可以根据参数Reason的值分别作出相应的处理。示例如下:
  library MyDLL;
  ...
  procedure MyDLLHandler(dwReason: DWORD);
  begin
   case dwReason of
    DLL_Process_Attach: //进程进入时
    DLL_Process_Detach: //进程退出时
    DLL_Thread_Attach: //线程进入时
    DLL_Thread_Detach: //线程退出时
   end;
  end;
  ...
  begin
    ... //初始化代码
    DLLProc := @MyDLLHandler;
    MyDLLHandle(DLL_Process_Attach);
  end.
可见,通过DLLProc 在处理多进程时比ExitProc更加强大和灵活。


静态(隐式)调用DLL

DLL已经有了,接下来我们看如何调用并调试它。普通的DLL是不需要注册的,但是要包含在windows搜索路径中才能被找到。搜索路径的顺序是:当前目录;Path路径;windows目录;widows系统目录(system、system32)。

引入DLL例程的声明方法
在需要使用外部例程(DLL函数/过程)的代码之前预定义该函数/过程。即按DLL中的定义原样声明,且仅需要声明。同时加上external后缀引入,与export引出相对应。根据exports的三种索引语法,也有三种确定例程的方式(以函数声明为例):
function 函数名(参数表):返回值;external ’DLL文件名’;
function 函数名(参数表):返回值;external ’DLL文件名’ index 索引号;
function 函数名(参数表):返回值;external ’DLL文件名’ name 新名称;
如果不确定例程名称,可以用索引方式引入。如果按原名引入会发生冲突,则可以用“新名称”引入。
进行声明后DLL函数的使用就和一般函数相同了。静态调用方式简单,但在启动调用程序时即调入DLL作为备用过程。如果此DLL文件不存在,那么启动时即会提示错误并立刻终止程序,不管定义是否使用。
快速查看DLL例程定义可以使用Borland附带的工具tdump.exe(在Delphi或BCB的bin目录下),示例如下:
Tdump c:\windows\system\user32.dll > user32.txt
然后打开user32.txt文件,找到Exports from USER32.dll行,之下的部分就是DLL例程定义了,比如:
    RVA      Ord. Hint Name
    -------- ---- ---- ----
    00001371    1 0000 ActivateKeyboardLayout
    00005C20    2 0001 AdjustWindowRect
    0000161B    3 0002 AdjustWindowRectEx
Name列就是例程的名称,Ord就是该例程索引号。注意,该工具是不能得到例程的参数表的。如果参数错误,调用DLL例程会引起堆栈错误而导致调用程序崩溃。

调用代码
建立一个普通工程,在Main窗体上放置一个TShellTreeView控件(Samples页),再放置一个按钮,添加代码如下:
function DeleteDir(DirName : Pchar):boolean;stdcall;external 'FileOperate.dll';

procedure TForm1.Button1Click(Sender: TObject);
begin
  if DirectoryExists(ShellTreeView.Path) then
    if Application.MessageBox(Pchar('确定删除目录'+QuotedStr(ShellTreeView.Path)+'吗?'), 'Information',MB_YESNO) = IDYes then
      if DeleteDir(PChar(ShellTreeView.Path)) then
        showmessage('删除成功');
end;
该范例调用的就是前面建立的DLL。
注意,声明时要包括stdcall后缀,这样才能保证调用Delphi开发的DLL的例程中类似PChar这样的参数值传递正确。大家有兴趣可以试验一下,不加入stdcall或者safecall后缀执行上面代码,将不能保证成功传递字符串参数给DLL函数。

调试方法
在Delphi主菜单Run项目中选择Parameters,打开“Run Parameters”对话框。在Host Application中填入一个宿主程序(该程序调用了将要调试的DLL),还可以在Parameters中输入参数。保存内容,然后就可以在DLL工程中设置断点、跟踪/单步执行了。
Run该DLL工程,然后将运行宿主程序。执行会调用DLL的操作,然后就能跟踪进入该DLL的代码,接下来的调试操作和普通程序是一样的。
因为操作系统或其他软件影响的原因,可能会出现进行了上述步骤仍然无法正常跟踪/中断DLL代码的情况。这时可以试试在菜单Project |Options 对话框的 Linker 页面里将 EXE and DLL Options 中的Include TD32 debug info及include remote debug symbols两个选项选中。
假如还是不能中断 -_____-||| 那只好另外建立一个引用执行代码单元的应用程序,写代码调用例程调试完成后再编译DLL了(其实该方法有时候蛮方便的,但有时候亦非常麻烦)。

引入文件
DLL比较复杂时,可以为它的声明专门创建一个引入单元,这会使该DLL变得更加容易维护和查看。引入单元的格式如下:
  unit MyDllImport; {Import unit for MyDll.dll }
  interface
    procedure MyDllProc;
  …
implementation
    procedure MyDllProc;external 'MyDll' index 1;

end.
这样以后想要使用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);

将前面静态调用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;

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;
以上直接调用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;
首先,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;
它的作用是将压入参数压入堆栈,找到GetFreeSysRes的地址,用QtThunk来转换16位地址到32位,最后才能正确的执行并返回值!


Delphi开发DLL常见问题

字符串参数
前面曾提到过,为了保证DLL参数/返回值传递的正确性,尤其是为C++等其他语言开发的宿主程序使用时,应尽量使用指针或基本类型,因为其他语言与Delphi的变量存储分配方法可能是不一样的。C++中字符才是基本类型,串则是字符型的线形链表。所以最好将string强制转换为Pchar。
如果DLL和宿主程序都用Delphi开发,且使用string(还有动态数组,它们的数据结构类似)作为导出例程的参数/返回值,那么添加ShareMem为工程文件uses语句的第一个引用单元。ShareMem是Borland共享的内存管理器Borlndmm.dll的接口单元。引用该单元的DLL的发布需要包括Borlndmm.dll,否则就得避免使用string。

在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.

DLL工程单元的引出声明:
exports
  CloseShowForm,
  CreateAndShowForm,
  CreateAndShowModalForm;

应用程序调用声明:
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';

除了普通窗体外,怎么在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;
代码中用了一个临时指针的原因在Application.MainForm是只读属性。MDI窗体的FormStyle不用设为fmMDIChild。

初始化COM库
如果在DLL中使用了TADOConnection之类的COM组件,或者ActiveX控件,调用时会提示 “标记没有引用存储”等错误,这是因为没有初始化COM。DLL中不会调用CoInitilizeEx,初始化COM库被认为是应用程序的责任,这是Borland的实现策略。
你需要做的是1、引用Activex单元,保证CoInitilizeEx函数被正确调用了
2、在单元级加入初始化和退出代码:
initialization
   Coinitialize(nil);
finalization
   CoUninitialize;
end.
3、 在结束时记住将连接和数据集关闭,否则也会报地址错误。

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

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

注:本文代码在Delphi6、7中调试通过。
附:本文参考了“Delphi5开发人员指南”等书及资料。

http://2006ray.spaces.live.com/?_c11_blogpart_blogpart=blogview&_c=blogpart&partqs=cat%3dDelphi%25e8%25b5%2584%25e6%2596%2599




内存影射的使用流程

第一步:利用FileOpen 或FileCreate函数打开内存文件,得到文件句柄;
第二步:利用CreateFileMapping()函数创建文件内存映射对象,得到映射对象的句柄;
第三步:利用CloseHandle关闭第一步创建的对象;
第四步:利用MapViewOfFile建立文件视图的起始地址;
第五步:利用CloseHandle关闭第二步创建的对象;
第六步:访问映射到内存文件的数据;
第七步:利用UnMapViewOfFile取消文件视图映射。



我今年39岁了, 25岁研究生毕业,工作14年,回头看看,应该说走了不少的弯路,有一些经验和教训。现在开一个小公司,赚的钱刚够养家糊口的。看看这些刚毕业的学生,对前景也很迷茫,想抛砖引玉,谈谈自己的看法,局限于理工科的学生,我对文科的不懂,身边的朋友也没有这一类型的。

  91年研究生毕业,那时出路就是1种:留在北京的国营单位,搞一个北京户口,这是最好的选择。到后来的23年内,户口落定了,又分成4条出路:

  1、 上国内的大企业,如:华为

  2、 自己做公司,做产品开发;

  3、 上外企,比如:爱立信、诺基亚

  4、 自己做公司,做买卖;

  5、 移民加拿大

  我想,首先要看自己适合做什么?做技术还是做买卖。

  做技术,需要你对技术感兴趣。我掰着数了一遍,我们研究生班的30来号人,实际上,适合做技术的,大概只有34个人,这几个人,1个现在还在华为, 3个移民加拿大了,现在这4个人混的还可以,在华为的同学也移民加拿大了,他在华为呆了6年,在华为奖金工资加起来大概30万吧,还有华为的股票,再过几年,华为的股票一上市,也能值个100200万。要是一毕业就去华为,那现在就绝对不是这个数字了。

  要是做技术,最好的就是上大公司,国内的大型企业,象华为中兴肯定是首选,能学到很多东西。华为虽然累,但是,年轻人不能怕累,要是到老了,还需要去打拼,那才是真的累啊。

  在外企,我想他们主要就是技术支持和销售,但是技术是学不到的,当然不能一概而论,我指的是象爱立信和诺基亚,真正的研发不会在中国做的,学到的也不如在华为多,其它的中兴我不是很了解,我想应该也不错啊。

 一个人都有一技之长,有傍身之技,那是最好的,走到哪里,都能有一口饭吃,还吃的不错,这是传统的观点。

  任何技术都是要在某个行业去应用,这个行业市场越大当然越好;要在一个领域之内,做深做精,成为绝对的专家,这是走技术道路的人的选择。不要跳来跳去,在中国,再小的行业你要做精深了,都可以产生很大的利润。

  研究生刚毕业的时候,做产品开发的有不少人,都是自己拍拍脑子,觉得这个产品有市场,就自己出来做。现在看来,我的这些同学,做产品开发的成功的没有一例,为什么?资源不足。

  1. 资金,刚毕业的学生啊,就是没钱;没钱,也意味着你开发的东西都是小产品;而且只能哥几个自己上,研发、生产、销售都是一个人或者几个人自己来,没有积累,什么都是重新来过。

  2. 人脉,任何一个行业,要想进去,需要有很深的人脉,否则,谁会用你的东西啊?谁敢用你的东西啊?

  我看到的,我这个班上开发产品的,自己还在坚持的,只剩下一个人了,说实在的,到现在,没有自己的汽车,也没有自己的房子,混的挺惨的。现在出国的不说了,在外企、在华为,至少都是几十万的年薪了,还有各种福利,就是产品开发成功了,又能如何?也就是这样了,但是以前那些年,都没有金钱的积累,等于白干。

  我身边的一个自动化系的研究生班的同学,能靠自己开发产品活得还可以的,也只有2个人。说明这条路不是那么好走的啊。

  其次就是上外企。我的2个同学,一个上了爱立信,一个先到爱立信后到诺基亚,都混的不错。到诺基亚的后来利用在诺基亚结识的人脉(就是哪些电信的头头脑脑),自己开了公司,也赚了不少的钱。

  外企最大的好处就是除了能学到比较规范的管理外,还能给你的职业生涯镀金。到了一个外企外,再到同行业的外企我想就很容易了。而且外企的收入高啊。

  自己做公司,做买卖,一开始有34个人走这条路,但是真正发财的只有一个人,其他人后来上外企了。做买卖,还是要有一定的天赋,还有机遇。要有对金钱的赤裸裸的欲望,要有商业上的头脑。后来我们同学在一起谈,说,我们即使给自己这个机遇,也未必能做的好。何况当时那个同学看好的产品(做一个台湾产品的代理),我们大家都没有看好,说明,真理还是掌握在少数人手里。

  到后来,同学们纷纷移民移民加拿大。

  移民加拿大对搞技术的人来说,还是一个不错的选择,但是要尽早,练了几年的技术,就赶紧出去,大概是在1996年走了不到10个,现在都还可以,买了房子和车了。要是晚了,语言再学也难了,而且在国内都混的还可以了,也就没有必要出去了。

我自己呢,先是在国营的研究所混了4年,后来到一家公司干了6年,2002年出来自己做公司,现在也就是混了一个温饱吧,算是有房有车,有点积蓄,但是不多,还有一个可爱的女儿。回首这10来年,有一些经验和教训。

  1. 要有一个职业生涯的规划。首先需要定位自己做什么合适,是做买卖还是做技术,一条路走到黑;当然,做了技术,后来改行也行;

  2. 做技术,就是要做精做深,成为这个行业的这个技术的专家;最好就是去国内的大公司,才能全面学到东西,能够给你培训的机会;如果大公司进不去,先到小公司练技术,找机会再到大公司去镀金,学高深的技术。千万不要自己做产品,要做也是对这个行业熟悉了,再去做。

  3.积极争取机会。积极争取学习和进步的机会。比如,做技术,就需要多锻炼,多学习,来提高自己的水平。一门技术,只要有机会去学习,都能学的会;要是没有机会,天才也没有办法学到这个技术。柳传志就说,杨元庆就是"哭着喊着要进步",实际上,就是争取自己的机会;当然,这种强烈的进步欲望,也是领导看重的地方。每一步都走在前面,积累10年,你就有了比其他人更多的机会了。

  4. 积累个人的信誉。从你的职业生涯的第一天,就要按照诚信的原则办事。要做到,当人们提起你的名字的时候,说,这哥们还不错,做事还行。

  5. 注意利用资源。如果你有有钱的亲戚、成功的长辈或者朋友,可以充分利用这些机会,得到更加顺利的发展前景。

  6.注意财富的不断积累。人生要想得到自由,财富是很关键的。否则,永远仰人鼻息,永远看人脸色。人都是势利眼。今后的家庭、职业生涯,金钱的积累很重要,没有钱,永远不能开张自己的事业,得到更多的机会;财富要做到逐年积累,你才能家庭生活幸福。没有钱是不可能有幸福的家庭的。

  7. 注意人脉的积累。最终,事业要靠在社会上的人脉的资源。要注意认识在你这个行业的人,结交他们,最终他们会成为你事业上的助力。

  8. 寻求贵人相助。要找大老板来帮助你,得到大老板的赏识。想想看,大蛋糕,切一点就够了,小蛋糕,都给你也吃不饱啊。

  9. 多听听成功的前辈和成功的朋友的意见。注意少听家里长辈的意见,尤其是都已经退休的长辈,他们对社会的认识还停留在很久以前,而这个社会已经发生很大的变化呢。最重要的是,长辈有时候会强求你做一些事情,但是,最终的结果他们是不负责的。只有你才能对自己负责。

  欢迎朋友们都能提出自己的看法。




    摘要:

▼老板与秘书

“听说你把女秘书辞了,她犯了什么错?”
“我对她说‘我爱你。’不一会儿,她把这句话打了出来,并让我在上面签字。”

毛病所在

 妇人在门口对刚到的电视修理工人说:“没事了。我只不过是错戴了别人的眼镜罢了。”

 
孔方兄的弟子

  有以银钱捐官学者,拜谒孔庙。孔子下席,坚辞不受其礼。

  士曰:“此是夫子弟子之礼,应坐受。”孔子曰:“岂敢。你是我孔方兄的弟子,断不敢

受拜。”
 
两个坏习惯

  某甲很郁闷地对某乙诉说。

  甲:“我有两个坏习惯,令我感到很困扰。”

  乙:“是什么坏习惯呢?”

  甲:“第一个坏习惯是裸    (全文共3701字)——点击此处阅读全文




 
Chr 将一个有序数据转换为一个ANSI字符 
Ord 将一个有序类型值转换为它的序号 
Round 转换一个实型值为四舍五入后的整型值 
Trunc 转换一个实型值为小数截断后的整型值 
Int 返回浮点数的整数部分 
IntToStr 将数值转换为字符串 
IntToHex 将数值转换为十六进制数字符串 
StrToInt 将字符串转换为一个整型数,如字符串不是一个合法的整型将引发异常 
StrToIntDef 将字符串转换为一个整数,如字符串不合法返回一个缺省值 
Val 将字符串转换为一个数字(传统Turbo Pascal例程用于向后兼容) 
Str 将数字转换为格式化字符串(传统Turbo Pascal例程用于向后兼容) 
StrPas 将零终止字符串转换为Pascal类型字符串,在32位Delphi中这种类型转换是自动进行的 
StrPCopy 拷贝一个Pascal类型字符串到一个零终止字符串, 在32位Delphi中这种类型转换是自动进行的 
StrPLCopy 拷贝Pascal类型字符串的一部分到一个零终止字符串 
FloatToDecimal 将一个浮点数转换为包含指数、数字及符号的十进制浮点记录类型 
FloatToStr 将浮点值转换为缺省格式的字符串 
FloatToStrF 将浮点值转换为特定格式的字符串 
FloatToText 使用特定格式,将一个浮点值拷贝到一个字符串缓冲区 
FloatToTextFmt 同上面例程,使用特定格式,将一个浮点值拷贝到一个字符串缓冲区 
StrToFloat 将一个Pascal字符串转换为浮点数 
TextToFloat 将一个零终止字符串转换为浮点数  




    摘要:


INI文件在系统配置及应用程序参数保存与设置方面,具有很重要的作用,
所以可视化的编程一族,如VB、VC、VFP、Delphi等都提供了读写INI文件
的方法,其中Delphi中操作INI文件,最为简洁,这是因为Delphi3提供了
一个TInifile类,使我们可以非常灵活的处理INI文件。
一、有必要了解INI文件的结构:

;注释
[小节名]
关键字=值
...

INI文件允许有多个小节,每个小节又允许有多个关键字,“=”后面是
该关键字的值。
值的类型有三种:字符串、整型数值和布尔值。其中字符串存贮在INI文
件中时没有引号,布尔真值用1表示,布尔假值用0表示。

注释以分号“;”开头。

二、定义
1、在Interface的Uses节增加IniFiles;
2、在Var变量定义部分增加一行:


my    (全文共3925字)——点击此处阅读全文





    摘要:

世界上最远的距离
不是 生与死的距离
  而是 我站在你面前
  你不知道我爱你
  世界上最远的距离
  不是 我站在你面前
  你不知道我爱你
  而是 爱到痴迷
  却不能说我爱你
  世界上最远的距离
  不是 我不能说我爱你
  而是 想你痛彻心脾
  却只能深埋心底
  世界上最远的距离
  不是 我不能说我想你
  而是 彼此相爱
  却不能够在一起
  世界上最远的距离
  不是 彼此相爱
  却不能够在一起
  而是明知道真爱无敌
  却装作毫不在意
  世界上最远的距离
  不是 树与树的距离
  而是 同根生长的树枝
  却无法在风中相依
  世界上最远的距离
  不是 树枝无法相依
  而是 相互了望的星星
  却    (全文共721字)——点击此处阅读全文