PDA

View Full Version : سوال: به دست آوردن مشخصات کامل برنامه ای که کاربر اجرا می کند



aisuda
یک شنبه 26 دی 1389, 10:45 صبح
با سلام،
برای تکمیل برنامه ای، نیاز است که مشخصات کامل برنامه هایی که کاربر از زمان اجرای این برنامه باز و اجرا می نماید، مشخص شود. خواهشمندم در این مورد من را راهنمایی بفرمائید.
(برای مثال اسم برنامه من x است. از زمان اجرا شدن برنامه x، تا زمان خاتمه آن، لیست برنامه هایی که اجرا می شوند، مشخص شود)
با تشکر

Felony
یک شنبه 26 دی 1389, 12:06 عصر
میتونید از کامپوننت Process Info (http://vcldeveloper.com/) استفاده کنید .

aisuda
یک شنبه 26 دی 1389, 18:18 عصر
ممنون از پاسخ شما، من مشکلی با به دست آوردن اطلاعات پردازش ندارم:


Procedure FillList;
var
aHandle : THandle;
FoundOne : bool;
ProcessEntry32 : TProcessEntry32;
ExeFile : string;
PH: THandle;
PIDName : array [0..MAX_PATH - 1] of char;
hMod : HMODULE;
dwSize2 , psapi: DWORD;
myList:Tlistitem;
tmp:string;
begin
psapi := loadlibrary('psapi.dll');
@EnumProcesses := GetProcAddress(psapi, 'EnumProcesses');
@GetModuleBaseNameA := GetProcAddress(psapi, 'GetModuleBaseNameA');
@GetModuleFileNameExA := GetProcAddress(psapi, 'GetModuleFileNameExA');
@EnumProcessModules := GetProcAddress(psapi, 'EnumProcessModules');
listview1.Items.Clear;
aHandle := CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
if aHandle <> 0 then
try
ProcessEntry32.dwSize := SizeOf(TProcessEntry32);
FoundOne := Process32First(aHandle, ProcessEntry32);
while FoundOne do
begin
ExeFile := ProcessEntry32.szExeFile;
PH:=OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, false, ProcessEntry32.th32ProcessID);
if GetModuleFileNameExA(PH, 0, PIDName, SizeOf(PIDName)) > 0 then
begin
if EnumProcessModules(PH, @hMod, SizeOf(hMod), dwSize2) then
begin
GetModuleFileNameExA(PH, hMod, PIDName, SizeOf(PIDName));
tmp:=ReplaceAll('\??\' , '', PIDName, false);
if fileexists(tmp)= true then
begin //to fill listview1
mylist:=listview1.Items.Add;
mylist.Caption :=tmp;
mylist.SubItems.Add(inttostr(ProcessEntry32.th32Pr ocessID));
end;
end;
end;
FoundOne := Process32Next(aHandle, ProcessEntry32);
end;
finally
CloseHandle(ahandle);
end;
end;

مشکل من در این است که می خواهم فقط اطلاعات پروسس هایی را مشخص کند که در حین اجرای برنامه من، اجرا شده اند.

Felony
یک شنبه 26 دی 1389, 18:36 عصر
خوب موقع اجرای برنامه لیست پروسه های در حال اجرا رو بگیرید ، این میشه لیست پروسه هایی که قبل از برنامه شما اجرا شده ؛ حالا تو یه تایمر هر چند ثانیه لیست پروسه ها رو دوباره بگیرید و با لیستی که در هنگام اجرای برنامه گرفتید مقایسه کنید ، هر آیتمی که در لیست اولیه که هنگام اجرای برنامه گرفتید وجود نداشت یعنی بعد از اجرای برنامه شما اجرا شده .

aisuda
یک شنبه 26 دی 1389, 18:59 عصر
ممنون، این راه میشه ولی یکم سرعت رو پایین می آره، راه دیگه ای وجود نداره که فقط برنامه های اجرا شده را مشخص کنیم بدون اینکه کاری با قبلی ها داشته باشیم و هی بخواهیم Refresh کنیم؟

Felony
یک شنبه 26 دی 1389, 19:43 عصر
سرعت پائین نمیاد ، مگه چه عملیاتی قراره انجام بدید ؟!
تو تایمر لیست پروسه ها رو میگیرید و در آخر موقع بسته شدن بررسی میکنید و آیتم های مورد نظر رو به دست میارید .

نمونه ضمیمه رو ببینید ؛ اگر هم خیلی نگران سرعت هستید عملیات مقایسه و گرفتن لیست پروسه های رو تو یک Thread دیگه پیاده سازی کنید .

vcldeveloper
یک شنبه 26 دی 1389, 21:50 عصر
مشکل من در این است که می خواهم فقط اطلاعات پروسس هایی را مشخص کند که در حین اجرای برنامه من، اجرا شده اند. یک راهش همون استفاده از ProcessInfo هست که خودش به طور متناوب لیست پروسه ها را آپدیت میکنه. می تونید در هر بار آپدیت، تعداد پروسه ها را ذخیره کنید، و اگر با مقدار قبلی تفاوت داشت، در لیست پروسه ها به دنبال پروسه ایی بگردید که CreationTime اش مربوط به بعد از CreationTime پروسه شما باشه.

راه حل دیگه استفاده از API Hooking هست، به این صورت که بخواید تابع CreateProcess را Hook کنید. برای Hook کردن راه های مختلف با دردسرهای مختلفی وجود داره.

راه دیگه استفاده از تابع PsSetCreateProcessNotifyRoutine هست. این تابع یک callback function میگیره، و در زمان اجرای هر پروسه جدید، اون تابع رو اجرا میکنه. مشکلش اینه که PsSetCreateProcessNotifyRoutine مربوط به DDK هست، و اون تابعی هم که میگیره باید تابع سطح کرنل باشه، یعنی باید یک Device Driver بنویسید، و اون تابع رو درش پیاده سازی کنید، و آدرسش را به PsSetCreateProcessNotifyRoutine بدید. اگر به برنامه نویسی با C و نوشتن Device Driver برای ویندوز تسلط دارید، می تونید همچین کدی رو بنویسید. نیازی نیست کل برنامه تان با C نوشته بشه، فقط اون درایور رو بنویسید، می تونید مابقی کدتان را با دلفی توسعه بدید.

راه حل دیگه استفاده از WMI Events هست، به این صورت که با استفاده از رویدادهای WMI، هر زمان که پروسه جدیدی ایجاد شد، برنامه شما مطلع بشه. این هم البته مثل ProcessInfo به صورت poll عمل میکنه، یعنی شما باید در فواصل زمانی معین، وضعیت Event های دریافتی را بررسی کنید. برای همین، بهتر هست که در یک Thread مستقل اجرا بشه. من سربارش را با ProcessInfo مقایسه نکردم، ولی به طور کلی، سربار چندان زیادی نداره، و به خوبی عمل میکنه. من یک نمونه کد به صورت برنامه Console از این روش براتون میذارم. دقت کنید که:

اولا، بخش عمده ایی از این کد توسط نرم افزار WMI Delphi Code Creator به طور خودکار تولید شده، و من فقط کلاس های WMI مورد نظر رو تعیین کردم، و مقداری کد برای دریافت مشخصات پروسه ایجاد شده اضافه کردم.

دوما، در این کد از یک Thread مستقل استفاده نشده، در نتیجه در یک حلقه به طور پیوسته وضعیت کلیدهای صفحه کلید بررسی میشه، و اگر کلیدی فشرده بشه، اجرای برنامه خاتمه پیدا میکنه. در نرم افزار خودتان، اگر از یک Thread مستقل استفاده کنید، می تونید به جای بررسی وضعیت کلیدهای صفحه کلید، وضعیت خصوصیت Terminated اون Thread را بررسی کنید، و کلا اون تابع KeyPressed را حذف کنید.

ثالثا، اجرای این کد در دیباگر ممکنه براتون مشکل ایجاد کنه، چون اگر در فاصله زمانی تعیین شده (یک ثانیه) پروسه ایی اجرا نشه، برنامه یک استثناء EOleException برگشت میده. این مشکلی در روند اجرای برنامه به طور عادی ایجاد نمیکنه، اما دیباگر همه استثناء ها را به طور پیش فرض دریافت میکنه، و برنامه شما را مرتبا متوقف میکنه. برای جلوگیری از این مشکل، می تونید در تنظیمات دیباگر دلفی، استثناء EOleException را غیرفعال کنید، یا برنامه را خارج از دیباگر اجرا کنید (منوی Run | Run without debugger ).

رابعا، زمان اجرای پروسه به صورت یک مقدار Int64 برگشت داده میشه. من حوصله نکردم زمان را به TDateTime تبدیل کنم. خودتون می تونید این کار را انجام بدید. فکر کنم توی سورس ProcessInfo کد مربوط به تبدیلش وجود داشته باشه.

خامسا، بخش عمده این کد مروبط به توابع کمکی تبدیل داده های Variant و غیره هست. کد اصلی در GetWMIObject و Event___InstanceOperationEvent_Target_ نوشته شده. البته به اون توابع CoInitialize و CoUninitialize هم در بلوک اصلی begin end. دقت کنید. در صورت استفاده از این کد در داخل Thread، باید حتما اون دو تابع را در شروع و پایان اجرای متد Execute اون Thread فراخوانی کنید.

کد برنامه:


// ------------------------------------------------------------------------------
// This code was generated by the Wmi Delphi Code Creator http://theroadtodelphi.wordpress.com
// Version: 1.0.0.11
//
//
//
// LIABILITY DISCLAIMER
// THIS GENERATED CODE IS DISTRIBUTED "AS IS". NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED.
// YOU USE IT AT YOUR OWN RISK. THE AUTHOR NOT WILL BE LIABLE FOR DATA LOSS,
// DAMAGES AND LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING OR MISUSING THIS CODE.
//
//
// ------------------------------------------------------------------------------
program GetWMI_Info;
{$APPTYPE CONSOLE}

uses
Windows,
SysUtils,
ActiveX,
ComObj,
Variants;

function VarArrayToStr(const vArray: variant): string;

function _VarToStr(const V: variant): string;
var
Vt: integer;
begin
Vt := VarType(V);
case Vt of
varSmallint, varInteger:
Result := IntToStr(integer(V));
varSingle, varDouble, varCurrency:
Result := FloatToStr(Double(V));
varDate:
Result := VarToStr(V);
varOleStr:
Result := WideString(V);
varBoolean:
Result := VarToStr(V);
varVariant:
Result := VarToStr(variant(V));
varByte:
Result := char(byte(V));
varString:
Result := String(V);
varArray:
Result := VarArrayToStr(variant(V));
end;
end;

var
i: integer;
begin
Result := '[';
if (VarType(vArray) and varArray) = 0 then
Result := _VarToStr(vArray)
else
for i := VarArrayLowBound(vArray, 1) to VarArrayHighBound(vArray, 1) do
if i = VarArrayLowBound(vArray, 1) then
Result := Result + _VarToStr(vArray[i])
else
Result := Result + '|' + _VarToStr(vArray[i]);

Result := Result + ']';
end;

function VarStrNull(const V: OleVariant): string;
// avoid problems with null strings
begin
Result := '';
if not VarIsNull(V) then
begin
if VarIsArray(V) then
Result := VarArrayToStr(V)
else
Result := VarToStr(V);
end;
end;

function GetWMIObject(const objectName: String): IDispatch;
// create the Wmi instance
var
chEaten: integer;
BindCtx: IBindCtx;
Moniker: IMoniker;
begin
OleCheck(CreateBindCtx(0, BindCtx));
OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten,
Moniker));
OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
end;

function KeyPressed: boolean; // Detect if an key is pressed
var
NumEvents: DWORD;
ir: _INPUT_RECORD;
bufcount: DWORD;
StdIn: THandle;
begin
Result := false;
StdIn := GetStdHandle(STD_INPUT_HANDLE);
NumEvents := 0;
GetNumberOfConsoleInputEvents(StdIn, NumEvents);
if NumEvents <> 0 then
begin
PeekConsoleInput(StdIn, ir, 1, bufcount);
if bufcount <> 0 then
begin
if ir.EventType = KEY_EVENT then
begin
if ir.Event.KeyEvent.bKeyDown then
Result := true
else
FlushConsoleInputBuffer(StdIn);
end
else
FlushConsoleInputBuffer(StdIn);
end;
end;
end;

Procedure Event___InstanceOperationEvent_Target_;
var
objWMIService: OleVariant;
objEvent: OleVariant;
objResult: OleVariant;
objTargetInstance: OleVariant;
begin
objWMIService := GetWMIObject('winmgmts:\\.\root\CIMV2');
objEvent := objWMIService.ExecNotificationQuery(
'Select * from __InstanceCreationEvent Within 1 WHERE TargetInstance ISA ''Win32_Process''');
while not KeyPressed do
begin
try
objResult := objEvent.NextEvent(100);
except
on E: EOleException do
if EOleException(E).ErrorCode = HRESULT($80043001) then
// Check for the timeout error wbemErrTimedOut 0x80043001
objResult := Null
else
raise ;
end;

if not VarIsNull(objResult) then
begin
objTargetInstance := objResult.TargetInstance;
Writeln(VarStrNull(objResult.TIME_CREATED), ' - ',
VarStrNull(objTargetInstance.Caption));
end;
end;
end;

begin
try
CoInitialize(nil);
try
Event___InstanceOperationEvent_Target_;
Readln;
finally
CoUninitialize;
end;
except
on E: Exception do
begin
Writeln(E.Classname, ':', E.Message);
Readln;
end;
end;

end.

aisuda
جمعه 01 بهمن 1389, 00:50 صبح
این روش بسیار جالبه، امتحانش کردم، سریع هم اجرا می شه، منتهی چگونه می توانم مسیر فایل اجرا شده را هم پیدا کنم؟ نمی دونم چرا برای oleها خاصیت Autocomplete فعال نیست.

vcldeveloper
جمعه 01 بهمن 1389, 01:48 صبح
منتهی چگونه می توانم مسیر فایل اجرا شده را هم پیدا کنم؟
کلاس Win32_Process از WMI اطلاعات مختلفی درباره هر Process برگشت میده، من فقط دو نمونه از اون فیلدها را ذکر کردم. برای به دست آوردن مسیر فایل می تونید به جای Caption از خصوصیت CommandLine یا ExecutablePath استفاده کنید. لیست کامل این خصوصیات در لینک زیر آمده:
http://msdn.microsoft.com/en-us/library/aa394372%28v=vs.85%29.aspx


نمی دونم چرا برای oleها خاصیت Autocomplete فعال نیست.
به خاطر اینکه داره از Late Binding استفاد میکنه. اینکه اون شی ایی که در اون OleVariant ریخته شده چی هست و چه خصوصیات و متدهایی داره، در زمان کامپایل مشخص نیست. در زمان اجرا هست که مشخص میشه. الان شما اگر در زمان کامپایل، هر متدی را به یکی از اون متغیرهای OleVariant نسبت بدید، کامپایلر ایرادی نمیگیره، ولی وقتی برنامه را اجرا کنید، خطایی دریافت می کنید که همچین متدی وجود نداره. Late Binding رو می تونید با Early Binding مقایسه کنید.