print

Writing a Copernic Desktop Search Plug-in Using Delphi 2006

Overview

Copernic Desktop Search is arguably one of the best desktop search tools available. One of the features that makes it great is the ability to extend it using the simple file extractor plug-in API. The API is fairly simple to use and this article is intended to get a Delphi developer up to speed quickly.

Objective

This article will demonstrate how to create a plug-in to find and search within binary Delphi form resource files (binary DFMs). This may not seem that useful to those who are only familiar with more recent generations of Delphi that default to text DFMs. However, if you maintain any older projects you will likely have the odd binary DFM floating around. Nevertheless, it demonstrates the basic concepts for writing a fully functional CDS plug-in in the Delphi language.

One of my main motivations in writing this particular plug-in was to finally eradicate all binary DFMs from my personal source code library. Borland's "convert" utility seemed useful to do this, but I wanted a bit more control over the conversion. I had some binary DFMs lurking in freeware components or libraries that I'd acquired in the past as well as a few older projects of mine that had been upgraded through older versions of Delphi. Little did my binary DFMs know but they would face extinction in about one hour! Well, all except some old Delphi 3 stuff that was in permanent cryogenic storage... never to be returned to this world (hopefully)!

Usually a file extractor plug-in simply extracts all text and inserts appropriate whitespace and presents that to Copernic so that it can index all the keywords. In this particular case there is one additional goal: to make it easy to differentiate between binary and text DFMs. The simple solution is to add a distinctive keyword to the DFM results: "BinDFM" for binary and "TextDFM" for text. This makes it easy to find text in any text/binary DFM and still differentiate the binary ones by placing "BinDFM" or "TextDFM" at the top of the preview pane. In addition, finding binary DFMs simply involves a search for "Bindfm" (and possibly restricting the search to only *.dfm files).

Step-by-Step

Now that I've explained a bit about what we're going to do, here's step by step instructions to accomplish this:

  1. Create a new ActiveX Library project:
    1. Select "File" -> "New" -> "Other" from menu.
    2. Select "Delphi Projects" / "ActiveX" -> "ActiveX Library" and click "OK".
      (Using Delphi 7 the steps are only slightly different: Click on the "ActiveX" tab, select "ActiveX Library," and then click "OK")
    3. Save this project as "CDSFileExtractorForDFM.bdsproj"
      (Using Delphi 7: "CDSFileExtractorForDFM.dpr").
  2. Create a new COM object that implements ICopernicDesktopSearchFileExtractor:
    1. Select "File" -> "New" -> "Other" from menu.
    2. Select "Delphi Projects" / "ActiveX" -> "COM Object" and click "OK".
      (Using Delphi 7: Click on the "ActiveX" tab, select "COM Object," and then click "OK")
    3. Type in "CDSDFMFileExtractor" for the "Class Name" field.
    4. Change the implemented interface to "ICopernicDesktopSearchFileExtractor". Select it by clicking on the "List" button. Warning: wait for the full list to be loaded (watch the status bar beneath the dialog) and then sort by the "Interface" column.
    5. Type "Copernic Desktop Search DFM File Extractor" in the "Description" field.
    6. Click "OK".
    7. Using the type-library editor, check to see what the GUID is for the "CDSDFMFileExtractor" CoClass. Make a note of this because you'll need it for the installation of the plugin (explained later). Once you've done this you can close the Type Library editor. Warning: Do not use the GUID for the ActiveX library itself or your file extractor will not be correctly registered with Copernic Desktop Search. If you receive errors previewing DFM files within CDS and it doesn't appear to be indexing the content then this may well be the problem.
  3. Save the project we've created so far and provide the "CDSFileExtractorForDFMMain.pas" in place of "Unit1.pas" when prompted.
  4. Add a new unit by using the menu: "File" -> "New" -> "Unit - Delphi for Win32" ("Unit" in Delphi 7) that will be used to convert the DFM to a content stream and add the following code to it:
    1. In the "interface" section add:

      uses
        Classes;

      procedure ReadDFMToMemoryStream(
        const FileName: string;
        const MemoryStream: TMemoryStream);

    2. In the "implementation" section add:

      uses
        SysUtils;

      procedure ReadDFMToMemoryStream(
        const FileName: string;
        const MemoryStream: TMemoryStream);
      var
        FileStream: TFileStream;
        TempMemoryStream: TMemoryStream;
        PrependString: string;
        CharBuffer: array[0..65535] of char;
        ReadCount: integer;
        OriginalFormat: TStreamOriginalFormat;
      begin
        FileStream := TFileStream.Create(FileName, fmOpenRead);
        try
          TempMemoryStream := TMemoryStream.Create;
          try
            ReadCount := FileStream.Read(CharBuffer, 3);
            FileStream.Seek(0, soFromBeginning);
            if ReadCount = 3 then
            begin
              if (CharBuffer[0] = #255) and (CharBuffer[1] = #10)
                and (CharBuffer[2] = #0) then
              begin
                // Has the binary file signature
                OriginalFormat := sofBinary;
                ObjectResourceToText(FileStream, TempMemoryStream);
              end
              else
              begin
                // Assume text otherwise
                OriginalFormat := sofText;
              end;
            end
            else
            begin
              // Too short to be binary or text, but more likely
              // empty text file so go with that!
              OriginalFormat := sofText;
            end;
            TempMemoryStream.Seek(0, soFromBeginning);
            case OriginalFormat of
              sofUnknown: PrependString := 'UnknownDFM';
              sofBinary: PrependString := 'BinDFM';
              sofText: PrependString := 'TextDFM';
              else PrependString := 'OtherDFM';
            end;
            PrependString := PrependString + #13#10#13#10;
            if OriginalFormat = sofBinary then
            begin
              MemoryStream.Write(PrependString[1], Length(PrependString));
              ReadCount := TempMemoryStream.Read(CharBuffer[0],
                SizeOf(CharBuffer));
              while ReadCount > 0 do
              begin
                MemoryStream.Write(CharBuffer[0], ReadCount);
                ReadCount := TempMemoryStream.Read(CharBuffer[0],
                  SizeOf(CharBuffer));
              end;
            end
            else
            begin
              MemoryStream.Write(PrependString[1], Length(PrependString));
              FileStream.Free;
              FileStream := TFileStream.Create(FileName, fmOpenRead);
              ReadCount := FileStream.Read(CharBuffer, SizeOf(CharBuffer));
              while ReadCount > 0 do
              begin
                MemoryStream.Write(CharBuffer, ReadCount);
                ReadCount := FileStream.Read(CharBuffer, SizeOf(CharBuffer));
              end;
            end;

            MemoryStream.Seek(0, soFromBeginning);
          finally
            TempMemoryStream.Free;
          end;
        finally
          FileStream.Free;
        end;
      end;

    3. Save this unit as "DelphiContentExtractionUtils.pas".
  5. Now we can populate the "CDSFileExtractorForDFMMain" unit with all of the code that will be called by Copernic Desktop Search to retrieve the file contents:
    1. Insert these lines at the beginning of the TCDSDFMFileExtractor class in the "interface" section:

      private
        FFilePath: string;

    2. Add these units to the uses clause in the "implementation" section:

      Classes, DelphiContentExtractionUtils

    3. Add the following code to the "Get_IsContentUnicode" method:

      Value := False;
      Result := S_OK;

    4. Complete the "GetContentStream" method body as follows:

      var
        StreamAdapter: TStreamAdapter;
        MemoryStream: TMemoryStream;
      begin
        // Note: StreamAdapter will look after freeing MemoryStream
        MemoryStream := TMemoryStream.Create;
        ReadDFMToMemoryStream(FFilePath, MemoryStream);
        StreamAdapter := TStreamAdapter.Create(MemoryStream);
        ContentStream := StreamAdapter;
        Result := S_OK;
      end;

    5. Add the following code to the "LoadURI" method:

      FFilePath := URI;
      Result := S_OK;

  6. Now we can build the DLL.
  7. Register it at the command-line: regsvr32 CDSFileExtractorForDFM.dll
  8. And create a registry file ("install.reg") to make CDS aware of it (this example works on Windows XP):

    Windows Registry Editor Version 5.00

    [HKEY_LOCAL_MACHINE\SOFTWARE\Copernic\DesktopSearch\CustomExtractors]
    ".dfm"="{<substitute your GUID here>}"

  9. Open the "install.reg" file to add the necessary entries into the registry.
  10. Once you've registered the file and created the necessary registry entries CDS should be able to index DFM files. Use the test steps outlined in the PDF document below to do this. Essentially you need to shut down CDS, start it up again, and force it to update the index. If you run into problems read the PDF. To test the plugin type in "textdfm" or "bindfm" to find the appropriate DFM files that have been indexed.

Notes

In the step-by-step instructions above, the procedure "ObjectResourceToText" was used. The VCL has two overloadeded procedures available: one takes 2 parameters and another that takes 3. The "ObjectResourceToText" procedure that takes 3 parameters seemed like the most logical choice at first because it returned an OriginalFormat enumerated type. Unfortunately, this procedure didn't appear to work the way it should. In the end I resorted to writing my own code to detect a binary DFM file by checking the first 3 characters for the binary form signature. That worked reliably.

Additional References

Copernic provides a document called "Writing a File Extractor Plug-in for Copernic Desktop Search 1.5" that provides a good reference document for creating plugins and can be found at this URL: http://redirect.copernic.com/data/pdf/cds-file-extractor-plug-in-en.pdf

 
 
 
preload1preload_b1preload_b2preload_b3preload_b4preload_b5preload_b6preload_b7preload_b8preload_b9preload1_b11preload1_b12preload1_b13preload1_b14preload1_b15preload1_b16preload1_b17preload1_b18preload1_b19preload2_b21preload2_b22preload2_b23preload2_b24preload2_b25preload2_b26preload2_b27preload2_b28preload2_b29preload3_b31preload3_b32preload3_b33preload3_b34preload3_b35preload3_b36preload3_b37preload3_b38preload3_b39preload4_b41preload4_b42preload4_b43preload4_b44preload4_b45preload4_b46preload4_b47preload4_b48preload4_b49preload5_b51preload5_b52preload5_b53preload5_b54preload5_b55preload5_b56preload5_b57preload5_b58preload5_b59preload6_b61preload6_b62preload6_b63preload6_b64preload6_b65preload6_b66preload6_b67preload6_b68preload6_b69preload7_b71preload7_b72preload7_b73preload7_b74preload7_b75preload7_b76preload7_b77preload7_b78preload7_b79preload8_b81preload8_b82preload1preload2apreload2preload3preload4preload5preload6preload7preload8preload9preload10preload11preload12