DLL PChar 参数如何转换成 String

iamduo 2013-01-17 04:30:11
在写DLL的时候,我们都知道,参数不能用 String,而用PChar替代。
我这里有一点不明白,
如果这个字符串不是立即就会处理,需要攒起来的,那么该如何转换呢?
(也许 New 一个 PChar,Move 一下内容也是一种不错的方法。这里不考虑这种方案。)

由于,很多情况下,我的设计都类似。
1. DLL 中创建一个全局的对象 A,处理这些字符串。
2. 不导出类,而仅仅是一个方法,参数用 PChar。该方法的实现中,用 A 的方法来执行。
3. PChar 用 StrPas 转换成 String 给 A 的方法。(!!!这里我总是在怀疑)
最近我总碰到莫名的地址报错。我更加怀疑这个地方了。

有没有同行研究过这种情况?
大家都是怎么处理的呢?

假设:有个主程序 B.exe。 写日志的类写在 C.dll 中,并且导出一个函数 Log(AStr:PChar)。有个子模块 D.dll 处理一些简单业务。
C.dll 在 exe 创建的时候就被动态加载。当 B 调用 D 之前,写一下日志,D在处理的时候,写一下日志,B调用完成后写一下日志。
“写日志”由于不是立即执行的,我们可以简单解释成 GetMem 分配内存。
那么,在 Log 这个函数里,每一次 GetMem 是不是都能由 C.dll 在被释放的时候,自己负责释放呢?
...全文
221 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
simonhehe 2013-01-17
  • 打赏
  • 举报
回复
引用 楼主 iamduo 的回复:
在写DLL的时候,我们都知道,参数不能用 String,而用PChar替代。 我这里有一点不明白, 如果这个字符串不是立即就会处理,需要攒起来的,那么该如何转换呢? (也许 New 一个 PChar,Move 一下内容也是一种不错的方法。这里不考虑这种方案。) 由于,很多情况下,我的设计都类似。 1. DLL 中创建一个全局的对象 A,处理这些字符串。 2……
StrPas函数不会出错, 你看下异常发生时传入的Pchar值是什么东东, 估计是被释了
function StrPas(const Str: PAnsiChar): AnsiString;
begin
  Result := Str;
end;
蓝色光芒 2013-01-17
  • 打赏
  • 举报
回复
StrPas函数,其实就是直接把 aStr := aPChar; 这样使用没问题,字符串的内存也是自动释放的。 内存错误可以跟踪得到的,把错误提示发上来
CCDDzclxy 2013-01-17
  • 打赏
  • 举报
回复
引用 7 楼 iamduo 的回复:
其实,我就是想问的,怎么把 PChar 转成 String ? 直接用 StrPas 赋值,这样做法靠谱吗?
我测试了下

var pc :PChar;
    str :string;
begin
  pc := 'AAA';
  Memo1.Lines.Add('pc : '+inttostr(integer(@pc[0]))+' , '+pc);

  str := pc;
  Memo1.Lines.Add('str : '+inttostr(integer(@str[1]))+' , '+str);

  str := StrPas(pc);
  Memo1.Lines.Add('str : '+inttostr(integer(@str[1]))+' , '+str);
end;
输出:

pc : 4623508 , AAA
str : 14305844 , AAA
str : 14321380 , AAA
看到 StrPas 应该是 重新分配地址的,应该是 靠谱的吧~~
xhz8000 2013-01-17
  • 打赏
  • 举报
回复
其实分配内存有谁发起都行的!记得是谁申请就由谁来释放。 比如你由DLL申请内存,由DLL来释放!
xhz8000 2013-01-17
  • 打赏
  • 举报
回复
不转都可以啦
iamduo 2013-01-17
  • 打赏
  • 举报
回复
引用 5 楼 CCDDzclxy 的回复:
exe 和 dll 都是 同版本delphi做的 的话,是否可以直接传 string?
可以,所有程序的 dpr 里,第一个就 Use ShareMM。是可以传 string 的。 不过,不建议这么做。 使用面太狭窄了,而且多发布东西,不能给其他语言使用。
iamduo 2013-01-17
  • 打赏
  • 举报
回复
引用 2 楼 xhz8000 的回复:
参数不能用 String,而用PChar替代。 记住只是参数不能用String;但在动态库里面是可以用String的
其实,我就是想问的,怎么把 PChar 转成 String ? 直接用 StrPas 赋值,这样做法靠谱吗?
iamduo 2013-01-17
  • 打赏
  • 举报
回复
引用 1 楼 kiboisme 的回复:
3. PChar 用 StrPas 转换成 String 给 A 的方法 在 Log 这个函数里,每一次 GetMem 是不是都能由 C.dll 在被释放的时候,自己负责释放呢? 何来的GetMem?
这么快就有那么多回复了,不错不错!!! 不好意思,这个是我没有说清楚。 这里是我的第四个问题,我想问的意思是,每次调用 Log 函数,都会分配一次内存,但是我不是很清楚,这个分配内存的动作是由谁发起的,B.exe ? C.dll ? D.dll ? 如果全都由 C.dll 释放会不会安全。
CCDDzclxy 2013-01-17
  • 打赏
  • 举报
回复
exe 和 dll 都是 同版本delphi做的 的话,是否可以直接传 string?
xhz8000 2013-01-17
  • 打赏
  • 举报
回复
我说的是在d7版本之前的!
xhz8000 2013-01-17
  • 打赏
  • 举报
回复
在我用delphi做这么久的DLL来看,就是觉得传入unicode不好处理!
xhz8000 2013-01-17
  • 打赏
  • 举报
回复
参数不能用 String,而用PChar替代。 记住只是参数不能用String;但在动态库里面是可以用String的
蓝色光芒 2013-01-17
  • 打赏
  • 举报
回复
3. PChar 用 StrPas 转换成 String 给 A 的方法 在 Log 这个函数里,每一次 GetMem 是不是都能由 C.dll 在被释放的时候,自己负责释放呢? 何来的GetMem?
远程注入DLL方法有很多种,也是很多木马病毒所使用的隐藏进程的方法,因为通过程序加载的DLL在进程管理器是没有显示的.这里介绍一种用 CreateRemoteThread 远程建立线程的方式注入DLL. 首先,我们要提升自己的权限,因为远程注入必不可免的要访问到目标进程的内存空间,如果没有足够的系统权限,将无法作任何事.下面是这个函数是用来提升我们想要的权限用的. function EnableDebugPriv: Boolean; var hToken: THandle; tp: TTokenPrivileges; rl: Cardinal; begin Result := false; //打开进程令牌环 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken); //获得进程本地唯一ID if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin tp.PrivilegeCount := 1; tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; //调整权限 Result := AdjustTokenPrivileges(hToken, false, tp, SizeOf(tp), nil, rl); end; end; 关于 OpenProcessToken() 和 AdjustTokenPrivileges() 两个 API 的简单介绍: OpenProcessToken():获得进程访问令牌的句柄. function OpenProcessToken( ProcessHandle: THandle; //要修改访问权限的进程句柄 DesiredAccess: DWORD; //指定你要进行的操作类型 var TokenHandle: THandle//返回的访问令牌指针 ): BOOL; AdjustTokenPrivileges() :调整进程的权限. function AdjustTokenPrivileges( TokenHandle: THandle; // 访问令牌的句柄 DisableAllPrivileges: BOOL; // 决定是进行权限修改还是除能(Disable)所有权限 const NewState: TTokenPrivileges; { 指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组, 数据组的每个项指明了权限的类型和要进行的操作; } BufferLength: DWORD; //结构PreviousState的长度,如果PreviousState为空,该参数应为 0 var PreviousState: TTokenPrivileges; // 指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息 var ReturnLength: DWORD //实际PreviousState结构返回的大小 ) : BOOL; 远程注入DLL其实是通过 CreateRemoteThread 建立一个远程线程调用 LoadLibrary 函数来加载我们指定的DLL,可是如何能让远程线程知道我要加载DLL呢,要知道在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。所我们需要在远程进程的内存空间里申请一块内存空间,写入我们的需要注入的 DLL 的路径. 需要用到的 API 函数有: OpenProcess():打开目标进程,得到目标进程的操作权限,详细参看MSDN function OpenProcess( dwDesiredAccess: DWORD; // 希望获得的访问权限 bInheritHandle: BOOL; // 指明是否希望所获得的句柄可以继承 dwProcessId: DWORD // 要访问的进程ID ): THandle; VirtualAllocEx():用于在目标进程内存空间中申请内存空间以写入DLL的文件名 function VirtualAllocEx( hProcess: THandle; // 申请内存所在的进程句柄 lpAddress: Pointer; // 保留页面的内存地址;一般用nil自动分配 dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍 flAllocationType: DWORD; flProtect: DWORD ): Pointer; WriteProcessMemory():往申请到的空间中写入DLL的文件名 function WriteProcessMemory( hProcess: THandle; //要写入内存数据的目标进程句柄 const lpBaseAddress: Pointer; //要写入的目标进程的内存指针, 需以 VirtualAllocEx() 来申请 lpBuffer: Pointer; //要写入的数据 nSize: DWORD; //写入数据的大小 var lpNumberOfBytesWritten: DWORD //实际写入的大小 ): BOOL; 然后就可以调用 CreateRemoteThread 建立远程线程调用 LoadLibrary 函数来加载我们指定的DLL. CreateRemoteThread() //在一个远程进程中建立线程 function CreateRemoteThread( hProcess: THandle; //远程进程的句柄 lpThreadAttributes: Pointer; //线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针 dwStackSize: DWORD; //线程栈大小,以字节表示 lpStartAddress: TFNThreadStartRoutine; // 一个TFNThreadStartRoutine类型的指针,指向在远程进程中执行的函数地址 lpParameter: Pointer; //传入参数的指针 dwCreationFlags: DWORD; //创建线程的其它标志 var lpThreadId: DWORD //线程身份标志,如果为0, 则不返回 ): THandle; 整个远程注入DLL的具体实现代码如下: function InjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: Pointer; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入的dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin lpThreadId := 0; // 计算LoadLibraryW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'LoadLibraryW'); // 启动远程线程LoadLbraryW,通过远程线程调用创建新的线程 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 如果执行成功返回 True; if (hRemoteThread 0) then Result := true; // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end; 接下来要说的是如何卸载注入目标进程中的DLL,其实原理和注入DLL是完全相同的,只是远程调用调用的函数不同而已,这里要调用的是FreeLibrary,代码如下: function UnInjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; // 进程注入和取消注入其实都差不多,只是运行的函数不同而已 var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: PChar; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId, dwHandle: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入的dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin // 计算GetModuleHandleW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'GetModuleHandleW'); //使目标进程调用GetModuleHandleW,获得DLL在目标进程中的句柄 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 等待GetModuleHandle运行完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 获得GetModuleHandle的返回值,存在dwHandle变量中 GetExitCodeThread(hRemoteThread, dwHandle); // 计算FreeLibrary的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'FreeLibrary'); // 使目标进程调用FreeLibrary,卸载DLL hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, Pointer(dwHandle), 0, lpThreadId); // 等待FreeLibrary卸载完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 如果执行成功返回 True; if hRemoteProcess 0 then Result := true; // 释放目标进程中申请的空间 VirtualFreeEx(hRemoteProcess, pszLibFileRemote, Length(DllFullPath) + 1, MEM_DECOMMIT); // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end;
library fundll; { Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. } uses ShareMem, // ShareMem 一定放第一个 Windows, Messages, SysUtils, math, strutils, registry , StdCtrls, ExtCtrls, ADODB, DB,dateutils,Dialogs; // fucs in 'fucs.pas'; const INPASSSTR='89ABCDEFGcdefghijkHIJ%^KLMN0123opqrstuOP -_\|/?@#$&*' ; //切记: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参数,我们尽可能照规矩来用。这两者写起来,后者会麻烦不少。如果你对C不熟的话,那也没关系。我们以后再讲。 //3.将这些可共享的Func送出DLL,让外界﹝就是你的Delphi-AP啦﹞使用: //光如此,你的AP还不能用到这些,你还要加个Exports才行。 代码: //=============比较大小的函数=============== Function MyMax ( X , Y : integer ) : integer ; stdcall ; //stdcall 可以让 BCB/VC-AP等使用的 begin if X > Y then Result := X else Result := Y ; end ; //==============加密======================= function Inpass(s:string):string; stdcall ; var i:integer; passstr,dd:string; begin for i:=1 to length(s) do begin dd:=inttohex(ansipos(s[i],inpassstr),4); if dd='0000' then begin result:='0';exit end; passstr:=passstr+dd ; end; Result :=passstr; end; //==============解密======================= function Outpass(s:string):string;stdcall ; var pass,dd:string; i,leng:integer; begin leng:= floor(length(s)/4); pass:=''; for i:=1 to leng do begin dd:=ansimidstr(s,(i-1)*4+1,4); if strtoint('$'+dd)=0 then begin result:='0';exit;end; if strtoint('$'+dd)>78 then begin result:='0'; exit end; pass:=pass+ansimidstr(inpassstr,strtoint('$'+dd),1) ; end; Result :=pass ; end; //==========test========================= function jsjyh(strym:string):string;stdcall; var newstr1,he,oldstr:string; tj:boolean; i:integer; begin i:=1; he:=''; tj:=true; // 取出要参与校验和计算的字符串给oldstr if (length(strym) mod 2)0 then begin showmessage('你输入的源码个数有错,不能是奇数个,请重输入!'); exit; end; oldstr:=trim(strym); while tj=true do begin newstr1:=copy(oldstr,i,2); oldstr:=copy(oldstr,i+2,length(oldstr)-2); //开始计算校验和并给he变量 if he='' then begin he:=inttohex(strtointdef('$'+newstr1,16)+ strtointdef('$'+'00',16),2); he:=rightstr(he,2); end else begin he:=inttohex(strtointdef('$'+newstr1,16)+ strtointdef('$'+he,16),2); he:=rightstr(he,2); end; if length(oldstr) =0 then tj:=false; end; result:=strym+he; end; //============================================== {$R *.RES} //将这些可共享的Func送出DLL,让外界﹝就是你的Delphi-AP啦﹞使用: //光如此,你的AP还不能用到这些,你还要加个Exports才行。 代码: exports MyMax,Inpass,Outpass,jsjyh; begin end.

1,183

社区成员

发帖
与我相关
我的任务
社区描述
Delphi Windows SDK/API
社区管理员
  • Windows SDK/API社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧