Thursday, 21 October 2010

Delphi Unicode Migration

One of my colleagues found this interesting document about migrating Delphi versions. I'm sure is very useful and this will help us to release a stable version before we move on to Delphi XE.

Check out this SlideShare Presentation:
Other interesting document: Reasons to migrate from Delphi 7 to Delphi 2009:

Wednesday, 20 October 2010

Tuesday, 19 October 2010

Clean Code: A Handbook of Agile Software Craftsmanship

It recently arrived at my hands the book from Robert C. Martin: Clean Code. Martin presents a revolutionary paradigm in his book: A Handbook of Agile Software Craftsmanship. Martin has teamed up with his colleagues from Object Mentor to distil their best agile practice of cleaning code “on the fly” into a book that will instil within you the values of a software craftsman and make you a better programmer-but only if you work at it.
Clean Code is divided into three parts. The first describes the principles, patterns, and practices of writing clean code. The second part consists of several case studies of increasing complexity. Each case study is an exercise in cleaning up code-of transforming a code base that has some problems into one that is sound and efficient. The third part is the pay-off: a single chapter containing a list of heuristics and “smells” gathered while creating the case studies. The result is a knowledge base that describes the way we think when we write, read, and clean code. Source: Clean Code Handbook

Monday, 18 October 2010

Keeping track of a list by comparing it.

Due to my last application developed, I had to compare a list by scanning it and then present the results by including if any item was added or removed. For accomplish this task I've developed a little unit that is able to compare the list and show the results in a TMemo.

//@Author Jordi Coll
//@2010

unit uListCompare;

interface

uses
    Classes, SysUtils;

type
    TListCompare = class(TObject)
        private
            FList2: TStringList;
            FList1: TStringList;
            procedure SetList1(const Value: TStringList);
            procedure SetList2(const Value: TStringList);
        public
            property List1: TStringList read FList1 write SetList1;
            property List2: TStringList read FList2 write SetList2;
            constructor Create();
            destructor Destroy(); override;
            procedure DumpList();
            function ExistInList1(s: string): boolean;
            function ExistInList2(s: string): boolean;
    end;

implementation

{ TListCompare }

constructor TListCompare.Create;
begin
    FList1 := TStringList.Create;
    FList2 := TStringList.Create;
end;

destructor TListCompare.Destroy;
begin
    FreeAndNil(FList1);
    FreeAndNil(FList2);
    inherited;
end;

procedure TListCompare.DumpList;
var
    i: Integer;
begin
    if List2.Count = 0 then
    begin
        for i := 0 to List1.Count - 1 do
        begin
            List2.Add(List1[i]);
           log('New Item ->' + List1[i]);
        end;
    end
    else
    begin
        for i := 0 to List1.Count - 1 do
        begin
            if not ExistInList2(List1[i]) then
                log('New Item ->' + List1[i]);
        end;
        for i := 0 to List2.Count - 1 do
        begin
            if not ExistInList1(List2[i]) then
                log('Item doesn''t exist ->' + List2[i]);
        end;
        List2.Clear;
        for i := 0 to List1.Count - 1 do
            List2.Add(List1[i]);
    end;
end;

function TListCompare.ExistInList1(s: string): boolean;
var
    found: boolean;
    i: Integer;
begin
    i := 0;
    found := false;
    while (i < FList1.Count) and (not found) do
    begin
        found := FList1[i] = s;
        inc(i);
    end;
    result := found;
end;

function TListCompare.ExistInList2(s: string): boolean;
var
    found: boolean;
    i: Integer;
begin
    i := 0;
    foundt := false;
    while (i < FList2.Count) and (not found) do
    begin
        foundt := FList2[i] = s;
        inc(i);
    end;
    result := found;
end;

procedure TListCompare.SetList1(const Value: TStringList);
begin
    FList1 := Value;
end;

procedure TListCompare.SetList2(const Value: TStringList);
begin
    FList2 := Value;
end;

end.

The only thing you have to do is fill the List1 with your items and then execute the procedure DumpList that will dump the information from List1 to List2 and will compare it to show the differences. This object can be called in a thread or a controlled loop.

Killing suspicious processes

As a result of an experiment, we found out that one process was being run in my local machine by an unknown user and this trigger my curiosity to know more about this and which actions I could do to fix it. Firstly, I decided to monitor the process and gather data from it by using some Delphi API libraries and secondly, build a little application to kill the process and keep a little log just in case. The application is called Thundax Suspicious process watcher v1.1 and it's able to filter processes by unknown/known users and apply actions to them.
Once the application is found (by the process to inspect text box) the utility kills the process and shows the resulted information. We can also force the killing system to the known processes and use it to force the end of and application by filtering it by name and user name.
In the next picture you can notice that I've terminated the execution of the notepad.exe by simply adding it name and process user name (me).
The application core is based on two parts, the first one that is able to search for the applications that are being run in your machine and the second one that is able to kill a process by its PID. Here you can get the implemented code:

Get process list:
procedure ProcessList();
var
    hProcSnap: THandle;
    pe32: TProcessEntry32;
    Domain, User: string;
    listItem: TListItem;
begin
    hProcSnap := CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0);
    if hProcSnap = INVALID_HANDLE_VALUE then
        Exit;
    pe32.dwSize := Sizeof(ProcessEntry32);
    ListView1.Clear;
    listCompare.List1.Clear;
    if Process32First(hProcSnap, pe32) = True then
        while Process32Next(hProcSnap, pe32) = True do
        begin
            if GetUserAndDomainFromPID(pe32.th32ProcessID, User, Domain) then
            begin
                if RadioButton2.Checked then
                begin
                    listItem := ListView1.Items.Add;
                    listItem.Caption := pe32.szExeFile;
                    listItem.SubItems.Add(IntToStr(pe32.th32ProcessID));
                    listItem.SubItems.Add(User);
                    listItem.SubItems.Add(Domain);
                    listCompare.List1.Add(pe32.szExeFile);
                    if AnsiUpperCase(pe32.szExeFile) = AnsiUpperCase(Edit2.text) then
                    begin
                        if Edit3.text <> '' then
                        begin
                            if AnsiUpperCase(User) = AnsiUpperCase(Edit3.text) then
                            begin
                                Log('Application founded ->' + AnsiUpperCase(Edit2.text));
                                if CheckBox3.Checked then
                                begin
                                    KillProcess(pe32.th32ProcessID);
                                    Sleep(300);
                                    Log('Application killed ->' + AnsiUpperCase(Edit2.text));
                                end
                            end;
                        end
                        else
                        begin
                            Log('Application founded ->' + AnsiUpperCase(Edit2.text));
                            if CheckBox3.Checked then
                            begin
                                KillProcess(pe32.th32ProcessID);
                                Sleep(300);
                                Log('Application killed ->' + AnsiUpperCase(Edit2.text));
                            end
                        end;

                    end;
                end
            end
            else
            begin
                if RadioButton1.Checked then
                begin
                    // Suspicious Items
                    listItem := ListView1.Items.Add;
                    listItem.Caption := pe32.szExeFile;
                    listItem.SubItems.Add(IntToStr(pe32.th32ProcessID));
                    listItem.SubItems.Add('UNKNOW');
                    listItem.SubItems.Add('UNKNOW');
                    listCompare.List1.Add(pe32.szExeFile);
                    if AnsiUpperCase(pe32.szExeFile) = AnsiUpperCase(Edit1.text) then
                    begin
                        Log('Application founded ->' + AnsiUpperCase(Edit1.text));
                        if CheckBox2.Checked then
                        begin
                            KillProcess(pe32.th32ProcessID);
                            Sleep(300);
                            Log('Application killed ->' + AnsiUpperCase(Edit1.text));
                        end
                    end;
                end
            end;
            Application.ProcessMessages;
        end;
    CloseHandle(hProcSnap);
end;

function GetUserAndDomainFromPID(ProcessId: DWORD; var User, Domain: string): Boolean;
var
    hToken: THandle;
    cbBuf: Cardinal;
    ptiUser: PTOKEN_USER;
    snu: SID_NAME_USE;
    ProcessHandle: THandle;
    UserSize, DomainSize: DWORD;
    bSuccess: Boolean;
begin
    Result := false;
    ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION, false, ProcessId);
    if ProcessHandle <> 0 then
    begin
        if OpenProcessToken(ProcessHandle, TOKEN_QUERY, hToken) then
        begin
            bSuccess := GetTokenInformation(hToken, TokenUser, nil, 0, cbBuf);
            ptiUser := nil;
            while (not bSuccess) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) do
            begin
                ReallocMem(ptiUser, cbBuf);
                bSuccess := GetTokenInformation(hToken, TokenUser, ptiUser, cbBuf, cbBuf);
            end;
            CloseHandle(hToken);

            if not bSuccess then
            begin
                Exit;
            end;

            UserSize := 0;
            DomainSize := 0;
            LookupAccountSid(nil, ptiUser.User.Sid, nil, UserSize, nil, DomainSize, snu);
            if (UserSize <> 0) and (DomainSize <> 0) then
            begin
                SetLength(User, UserSize);
                SetLength(Domain, DomainSize);
                if LookupAccountSid(nil, ptiUser.User.Sid, PChar(User), UserSize, PChar(Domain), DomainSize, snu) then
                begin
                    Result := True;
                    User := StrPas(PChar(User));
                    Domain := StrPas(PChar(Domain));
                end;
            end;

            if bSuccess then
            begin
                FreeMem(ptiUser);
            end;
        end;
        CloseHandle(ProcessHandle);
    end;
end;

Kill process by PID:
procedure KillProcess(pid: integer);
var
    h: THandle;
begin
    try
        h := OpenProcess(PROCESS_TERMINATE, false, pid);
        try
            if h <> 0 then
                TerminateProcess(h, 0);
        finally
            CloseHandle(h);
        end;
    except
        ShowMessage('Error');
    end;
end;

Let me know if you have any doubt about the program or any improvement!.

Related links:

Friday, 1 October 2010

Working with Microsoft Excel (.xlsx) files in Delphi 2010

Due to the improvement of the new format of Microsoft office 2007 files (.docx, .xlsx based on xml) the way we use to access this documents has changed. Instead of using OLE we can use the power of Delphi and the type library Borland (TLB Files).
In this article we'll talk about how to create the TLB files and the code we need to access the .Excel (.xlsx file) of the example I've created.
First of all, we need to import the components via -> Component -> Import a Type Library. Once we are there, we need to select the "Microsoft Excel 12.0 Object Library" and go on with the import. Mark the "Generate component wrappers" check-box and finally add the component to the working unit. This will generate the Excel_TLB.pas.
Once we have integrated this file into our project, we need to go on with importing the second component: Component -> Import a Type Library and then install the "Microsoft Office 12.0 Object Library". This will generate the Office_TLB.pas (this unit is necessary because it's used by Excel_TLB.pas).

1 - Knowing the number and name of the sheets of the Workbook.
The following code will open a excel file and will load all the sheets into a combo box. Even though it represents a small portion of code, the library is quite complicated to use.

procedure TForm1.LoadCombo();
var
    XLSXReader: Excel_TLB.TExcelApplication;
    newWorkbook: Excel_TLB._Workbook;
    objXLS: Excel_TLB.TExcelWorkbook;
    objSheet: Excel_TLB.TExcelWorksheet;
    i: Integer;
begin
    newWorkbook := XLSXReader.Workbooks.Add('C:\book1.xlsx', GetUserDefaultLCID);
    objXLS := Excel_TLB.TExcelWorkbook.Create(nil);
    objXLS.ConnectTo(newWorkbook);
    objSheet := Excel_TLB.TExcelWorksheet.Create(nil);
    for i := 1 to objXLS.Worksheets.count do
    begin
        objSheet.ConnectTo(objXLS.Worksheets[i] as _Worksheet);
        ComboBox1.Items.Add(objSheet.Name);
    end;
    newWorkbook.Close(false, EmptyParam, EmptyParam, GetUserDefaultLCID);
    FreeAndNil(objXLS);
    FreeAndNil(objSheet);
end;

2 - Accessing to Excel fields.
The following example will get the objSheet and will get all the data from the sheet and will dump into a dataset to display then the result into a grid.
procedure TForm1.Open(Sender: TObject);
var
    XLSXReader: Excel_TLB.TExcelApplication;
    newWorkbook: Excel_TLB._Workbook;
    objXLS: Excel_TLB.TExcelWorkbook;
    objSheet: Excel_TLB.TExcelWorksheet;
    objWrapper: TExcelWrapper;
begin
    newWorkbook := XLSXReader.Workbooks.Add('C:\book1.xlsx', GetUserDefaultLCID);
    objXLS := Excel_TLB.TExcelWorkbook.Create(nil);
    objXLS.ConnectTo(newWorkbook);
    objSheet := Excel_TLB.TExcelWorksheet.Create(nil);
    objSheet.ConnectTo(objXLS.Worksheets['sheet1'] as _Worksheet);
    objWrapper := TExcelWrapper.Create(objSheet);
    if cdsExcel.Active then
        cdsExcel.Close;
    objWrapper.FillDataSet(cdsExcel);
    cdsExcel.Open;
    cdsExcel.First;
    newWorkbook.Close(false, EmptyParam, EmptyParam, GetUserDefaultLCID);
end;

Then the code for getting the data from the sheet:

procedure TExcelWrapper.FillDataSet(var cdsExcel: TClientDataSet);
var
    count: Integer;
    i: Integer;
    bFinished, correct: boolean;
    Value: String;
begin
    if Assigned(FSheet) then
    begin
        try
            cdsExcel.DisableControls;
            count := 0;
            i := 2;
            LastModule := '';
            bFinished := false;
            while not bFinished do
            begin
                Value := FSheet.Cells.Item[i, 1];
                if Value = '' then
                    count := count + 1
                else
                begin
                    count := 0;
                    cdsExcel.Append;
                    cdsExcel.FieldByName('Column1').AsString := FSheet.Cells.Item[i, 1];
                    cdsExcel.FieldByName('Column2').AsString := FSheet.Cells.Item[i, 2];
                    cdsExcel.FieldByName('Column3').AsString := FSheet.Cells.Item[i, 3];
                end;
                if count > 2 then
                    bFinished := true;
                i := i + 1;
            end;
            cdsExcel.Post;
        finally
            cdsExcel.EnableControls;
        end;
    end;
end;

3 - Things to take into account.
You need to remember that the library creates an instance of Excel application, and you must close all the opened connections using the Close procedure.

newWorkbook.Close(false, EmptyParam, EmptyParam, GetUserDefaultLCID);