1

I want to query Active Directory in an app developed with Delphi (7 and up), but do not want to include "ActiveDs_TLB" in the "uses" clause to keep the EXE size down. When querying WMI it is possible to use the IBindCtx and IMoniker interfaces to avoid linking in the type library (see How do I use WMI with Delphi without drastically increasing the application's file size? for a solution).

Is it possible to do the same when performing AD queries? I my case I want to retrieve "IADsUser" and "IADsComputer". I am aware that I can decrease the EXE size by manually copying only the required definitions from "ActiveDs_TLB" into my program or to use an LDAP query, but I would prefer a solution similar to the one described for WMI.

Community
  • 1
  • 1
Olaf Hess
  • 1,453
  • 11
  • 18
  • So what's stopping you using late bound COM? What type of application do you currently have? VCL or console? How large is your executable currently? How large does it become when you add the type library unit? – David Heffernan Jul 15 '14 at 08:31
  • The size of a console program increases by nearly 400 KB (D7) and by roughly 800 KB (D2010). And I am asking for an example on how to do it using late bound COM as I couldn't find any. – Olaf Hess Jul 15 '14 at 13:57
  • Late bound COM is late bound COM. Create the object, and call its methods. – David Heffernan Jul 15 '14 at 14:32

2 Answers2

3

I'm no Active Directory expert, but I just created two D7 console applications, one accessing the WnNTSystemInfo object using the ActiveDS_TLB.Pas type library and the other using late binding do do the same thing, namely get the ComputerName from AD.

First, the late binding one:

program ActiveDSLBConsole;
{$APPTYPE CONSOLE}
uses
  SysUtils, ActiveX, ComObj;
var
  SI : OleVariant;
  S : String;
begin
  CoInitialize(Nil);
  SI := CreateOleObject('WinNTSystemInfo');
  S := SI.ComputerName;
  writeln(S);
  readln;
end.

(what took me longest writing the above was checking the registry for the name of the object to create)

Anyway, I hope that shows that, yes, you can query AD via late binding and that this minimal example will get you started querying AD that way.

The equivalent AD console application using ActiveDS_Tlb is

program ActiveDSConsole;
{$APPTYPE CONSOLE}
uses
  SysUtils, ActiveX, ActiveDS_Tlb;
var
  SI : IADsWinNTSystemInfo;
  S : String;
begin
  CoInitialize(Nil);
  SI := CoWinNTSystemInfo.Create;
  S := SI.ComputerName;
  writeln(S);
  readln;
end.

These have .Exe sizes of

ActiveDSConsole   :  390144 bytes
ActiveDSLBConsole :   87552 bytes (late bound)

So there's evidently quite a bit of code pulled in to support the use of the tlb objects, but neither is huge.

FWIW, the above re-written as Button1Click handlers of a minimalist VCL app gives Exe sizes of

using ActiveDS_TLB : 396288 bytes
late bound         : 392704 bytes

the difference between these two seems fairly marginal to me, but there's a clear size advantage to late binding in a minimal D7 console application. Your mileage may vary, so probably best to "suck it and see", if you'll pardon the mixed metaphors.

Btw, late binding has the advantage that you don't always have to supply arguments for each of the parameters in an interface method. And you can call a method with this special syntax that the compiler was enhanced to allow (when automation support was added, in D2) for variants that it knows contain late-bound automation objects:

(from an MS Word late binding example)

Table := MSWord.ActiveDocument.Tables.Add(Range:= MSWord.Selection.Range, NumRows:= Rows, NumColumns:= Columns, DefaultTableBehavior:= wdWord9TableBehavior, AutoFitBehavior:= wdAutoFitFixed);
MartynA
  • 30,454
  • 4
  • 32
  • 73
  • +1 You are comparing sizes of VCL apps. They already have all the units that asker doesn't want dragged in. Try comparing sizes in a console app that uses nothing. – David Heffernan Jul 16 '14 at 18:12
  • @ David Heffernan: Ah, ok. I thought his concern was " to avoid linking in the type library", which puzzled me a bit but never mind. Anyway, I've tried what you said and the result surprised me a bit - I'll update the answer shortly. – MartynA Jul 16 '14 at 18:38
  • Yeah, it is all the units that you need for com support that count – David Heffernan Jul 16 '14 at 18:46
2

Martyn's answer filled in the missing pieces. Here's an example on how to query IADsUser using late binding:

program GetUserObjectPath;

{$APPTYPE CONSOLE}

uses SysUtils, ActiveX, ComObj;

function GetObject (const Name: WideString) : IDispatch;

var
    Moniker : IMoniker;
    Eaten : Integer;
    BindContext : IBindCtx;

begin
    OleCheck (CreateBindCtx (0, BindContext));
    OleCheck (MkParseDisplayName (BindContext, PWideChar (Name), Eaten,
                                  Moniker));
    OleCheck (Moniker.BindToObject (BindContext, NIL, IDispatch, Result));
end; { GetObject }

procedure Query_AD (const sQuery: String);

var
    vUser : OleVariant;

begin
    vUser := GetObject (sQuery);  // = IADsUser
    WriteLn ('Name = ' + vUser.FullName);
end; { Query_AD }

var
    sQuery, sDomain, sUserName : String;

begin
    sDomain := GetEnvironmentVariable ('USERDNSDOMAIN');
    sUserName := GetEnvironmentVariable ('USERNAME');

    sQuery := Format ('WinNT://%s/%s,user', [sDomain, sUserName]);

    CoInitialize (NIL);

    try
        Query_AD (sQuery);

       finally
        // Causes Access Violation if AD query does not happen in subroutine
        CoUninitialize;  
    end; { try / finally }

    WriteLn;
    Write ('Press [Enter] to continue ...');
    ReadLn;
end.

The actual AD query should happen in a subroutine (here "Query_AD"), otherwise calling "CoUninitialize" is going to lead to an access violation (see Why does CoUninitialize cause an error on exit? for an explanation).

Community
  • 1
  • 1
Olaf Hess
  • 1,453
  • 11
  • 18