技术交流,DH讲解.
晚上突然有这个想法,该踏踏实实把所有的书读一遍,好吧.踏实一些.
这本书,我也只有电子版,要下的人可以去盒子,园地还有我的网盘都有.
第一章 DLL和数据共享
首先建立一个DLL工程:
导出函数的2种方式:
function Export1():Integer ; export; begin //add code here end;
就是在函数后面写个export.
function Export1():Integer ; begin //add code here end; function Export2():Cardinal ;stdcall; begin //add code here end; exports Export1,Export2;
在exports后面把所有要导出的函数名字写上.
exe调用Dll也有2种方式:
显式调用:
function Export1():Integer;external 'Project2.dll'; implementation
定义一个函数,然后external 说明这个函数来之哪个dll.这样就不用实现了.
隐式调用:
type TExport2 = function : Cardinal;stdcall; procedure TForm1.FormCreate(Sender: TObject); var hLib:Cardinal; Export2:TExport2; begin hLib:=LoadLibrary('Project2.dll'); Export2:=TExport2(GetProcAddress(hLib,'Export2')); //执行函数 Export2; FreeLibrary(hLib); end;
先用LoadLibary加载Dll然后GetProcAddress来取得函数指针,然后执行,最后释放dll句柄,记得.
既然说到了调用,这里就要说调用约定了:
register 函数体 从左到右,优先使用寄存器(EAX,EDX,ECX),然后使用堆栈
pascal 函数体 从左到右,通过堆栈传递
cdecl 调用者 从右到左,通过堆栈传递(与C\C++默认调用约定兼容)
stdcall 函数体 从右到左,通过堆栈传递(与VC中的__stdcall兼容)
safecall 函数体 从右到左,通过堆栈传递(同stdcall)
我们看见传参不一样我调用的方式要是不一样,那么参数就可能不对了,函数最后的结果肯定不一样了.
如果我们想在Dll初始化干点儿事,结束时干点儿事,怎么办?
//Dll工程文件的末尾的Begin End中间 begin //这里写代码就是初始化了,但是这样只能初始化干事情 end.
只能初始化...那怎么办?
解决方法又有2个,哈哈:
第一个,我们添加一个引用单元,然后这个单元initialization和finalization进行初始化和收尾工作塞.
uses Unit2 in 'Unit2.pas';//Dll添加引用单元
unit Unit2; interface uses SysUtils; implementation var L:TObject; initialization L:=TObject.Create;//初始化工作 finalization L.Free; //收尾工作 end.
第二个,调用main函数,是的,dllmain(),不过Delphi里面不是叫这个,叫dllproc().
procedure MyDllMain(n:Integer); begin case n of DLL_PROCESS_ATTACH: begin //进程加载Dll的时候 end; DLL_PROCESS_DETACH: begin //进程卸载Dll的时候 end; DLL_THREAD_ATTACH: begin //线程加载Dll的时候 end; DLL_THREAD_DETACH: begin //线程卸载Dll的时候 end; end; end; begin DllProc:=MyDllMain; MyDllMain(DLL_PROCESS_ATTACH); end.
当Dll被一个进程加载之后,DLL的变量的空间就是这个进程里面了.也就是如果2个进程加载一个DLL,那么DLL的全局变量就有2个备份了,互不相干,是吧.
要多个DLL共享一个全局变量怎么办?接下来要说的就是用内存映射文件.
type TData = record A:Integer; //其他数据 end; PData = ^TData; var hMap:Cardinal; data:PData; procedure MyDllMain(n:Integer); begin case n of DLL_PROCESS_ATTACH: begin hMap:=OpenFileMapping(FILE_MAP_ALL_ACCESS,False,'名字'); if hMap = 0 then//没有就创建 begin //内存映射的话,第一个参数必须是$FFFFFFFF,文件映射的话就是文件句柄 //第二个参数:安全,一般为nil //第三个参数:映射文件的属性,我们要可读可写 //第四个参数:要映射数据大小的高4个字节 //第五个参数:要映射数据大小的低4个字节 //第六个参数:唯一的名字 hMap:=CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,SizeOf(TData),'名字'); if hMap=0 then//创建失败 Exit; end; //映射数据 data:=MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,0);//全部映射出来 if data = nil then//映射失败 Exit; end; DLL_PROCESS_DETACH: begin if Boolean(data) then begin UnmapViewOfFile(data); CloseHandle(hMap); end; end; end; end;
现在就映射好了,只要对data进行操作就会写到共享内存区了.因为可能多个DLL回去写,所以这里我们需要弄个互斥对象来同步.我就不演示了.这书上的代码里面会有的.
还有一个地方,映射的时候都只有一个变量一个结构体,结构体里面不要有指针,也就是像String这样的,因为指针指向的地址又是在进程空间中了.如果有多个结构体要共享,就需要先把多个结构体定义成一个结构体.
其他共享数据的方式:
1 SendMessage 或者 PostMessage,然后靠LPARAM和WPARAM来传递,但是LPARAM和WPARAM只是2个Integer,也就是传递的数据也就只有2个数,地址,指针没戏,因为你指针还是进程空间里面的,没有映射出来.
2 WM_COPYDATA消息可以传一个指针出来.
function SendCopyData(h:HWND;s:AnsiString):Integer ; var p:PAnsiChar; bufsize:Integer; data:TCopyDataStruct; begin bufsize:=Length(s) + 1; p:=AllocMem(bufsize); StrCopy(p,PAnsiChar(s)); with data do begin cbData:=bufsize; dwData:=12315;//自己定义 lpData:=p; end; Result:=SendMessage(h,WM_COPYDATA,0,Integer(@data)) end;
好的第一章就这样,准备睡觉.明天不能迟到了...