I found this question in the borland mailinglist:

… I get my dialog and all runs well – *except* I’ve lost theming
on this particular dialog. This means the application’s main windows shows
up correctly themed, the progress dialog does not. I’ve already added the
XP manifest to the progress dialog, too, but to no avail so far.

A theme is only applied if an EXE or DLL file has a manifest that explicitly enables theme support. If Windows cannot find this manifest, the process is only shown with the regular window design.

BildBild

However the Vista elevation is done by creating a COM object and offers its methods through a seperate process. A seperate process is necessary because the same process cannot get assigned the ncessary administrator token. In our case a service in the service container svchost.exe has the task to host the COM object. The contained service is called dllhost.exe (service name: “COMSysApp”) and does not run until requested. It does not matter whether the service is deactivated or not because it is started directly by svchost.

Bild

The COM method and the dialog will run in the process space of the newly elevated dllhost.exe. And because there is no manifest information in this file you cannot get a themed dialog layout.

Bild

So how can you get a themed dialog anyhow?

The task is done by using the basic principles of COM. COM knowns Callback interfaces and other stuff. By using a Callback interface you can turn around the server – client principle.

  1. Define a well known interface that is implemented by the client only. But also known to the server.
  2. Add a parameter to your elevated COM method that receives a pointer to this interface.
  3. Before calling an elevated method, create the interface and
  4. apply a pointer to the method’s parameter you added.
  5. Call the method and use the methods in the given callback interface. COM will automatically transfer the method calls to your non-elevated process. In this way you can create themed windows, do window management stuff (progress bar) and eventually close the window.

If you prefer to read code instead of text you should study this code excerpts.

Define a callback interface for your client and add it to the server’s TypeLib. Delphi will generate code for it automatically in a file called <Name>_TLB.pas (TLB Header). Do not create the interface by using the Delphi’s ActiveX expert. Just add it manually.

type
  IMyCallback = interface(IUnknown)
  [MYCALLBACK_GUID]
    procedure ACallBackMethod(DataFromServer : …); safecall;
  end;
 //We do not need a CoClass

IMyCallback interface is known to both the server and the client, because we use the TLB Header in the client and the server. The client will implement and the server will receive a pointer to it and then call the included method ACallBackMethod everytime it wishes to do so.

The server implements a method to allow a callback interface to be passed to it.

type
  IServerInt = interface(IDispatch)
  [ISERVERINT_GUID]
    procedure DoStuffThatNeedsCallBack(const Callback: IMyCallback;…) safecall;
  end;
  //TypeLib editor also declares a CoClass called CoServerInt

Of course you have to implement both interfaces: IServerInt and IMyCallback. However IServerInt is implemented on server side and IMyCallback is only implemented on the client side.

On client side (namely your application) write..

uses …, ComSrv,ComObj;
type
  TMyCallback = class(TComObject, IMyCallback)
    procedure ACallBackMethod(DataFromServer : …); safecall;
  end;

Install the COM object anywhere before you’re going to use it.

 TComObjectFactory.Create(ComServer,
     TMyCallback, Class_MyCallback,
     ‘MyCallback’, //Classname
     ‘Description here’,
     ciMultiInstance);

After all implement the callback method…

procedure TMyCallback.ACallBackMethod(DataFromServer : …);
begin
  //do stuff on the non-elevated client side
end;

…and anywhere you like, call the method to use our Callback interface.

var Server : IServerInt;
    Callback : IMyCallback;
begin
  Server := CoServerInt.Create;
  Callback := TMyCallback.Create as IMyCallback;

  Server.DoStuffThatNeedsCallBack(Callback, …);
end;

I’m using TComObject and TComObjectFactory on the client side , because this way works for console applications, too. If you need the possibilities of TAutoIntfObject, you will have to write a standalone COM server with TApplication. However this is not necessary here. The method ACallBackMethod will be run in the client’s process space rather than the elevated helper process. You can check whether the callback method runs in the correct process (elevated or not?) with the following code.

procedure TMyCallback.ACallBackMethod(DataFromServer : …);
var Token : TJwSecurityToken;
begin
   Token := TJwSecurityToken.CreateTokenEffective(TOKEN_QUERY or TOKEN_READ);
   try
     if Token.RunElevation = 0 then
       ShowMessage(‘Process is NOT Elevated: ‘+IntToStr(GetCurrentProcessId))
     else
       ShowMessage(‘Process is Elevated: ‘+IntToStr(GetCurrentProcessId));
   finally
     Token.Free;
   end;
end;

Second way, still on the client side:
If you already have a form class, you can add the interface to it.

type
  TForm1 = class(TForm, IMyCallback)
  protected
    procedure ACallBackMethod(DataFromServer : …); safecall;
  end;

Instead of creating the COM object, you pass a self-pointer to the method parameter.

procedure TForm1.OnButtonClick();
var Server : IServerInt;
    Callback : IMyCallback;
begin
  Server := CoServerInt.Create;

  Server.DoStuffThatNeedsCallBack(Self, …);
end;

Be aware that in a multi thread environment, you have to make sure that resources in that specific form are accessed only by one thread at a time.

Eventually the server side calls the methods within the callback interface:

uses …, ComSrv,ComObj;

type
  TServerIntImpl = class(TAutoObject, IServerInt)
    procedure DoStuffThatNeedsCallBack(const Callback: IMyCallback;…) safecall;
  end;


procedure TServerIntImpl.DoStuffThatNeedsCallBack(const Callback: IMyCallback;…)
begin
  …
  Callback.ACallBackMethod();
end;


initialization
  TAutoObjectFactory.Create(ComServer, TMyCallback,
     Class_MyCallback, ciMultiInstance);

The callback method will be called in the context of the client. So it will not be elevated. This is because there is no ordinary method call here. COM will pack the function stack and all its parameter into a stream, send it to the client, unpack it and restore a proper function stack to be executed. This happens in the client process of course.
Get more information about COM callbacks here.


Aside from the themed dialog problem you never should show window elements in such an elevated method. There is no need to it. All window elements can be displayed by using standard user rights. They do not need Administrator rights; otherwise we had to logon as Administrator.
However you only should do stuff that needs Administrator privileges in these COM methods. The more code you add to the method the more likely a bug may lead to a security risk. So simply call your function that needs Administrator rights, get the results and go back to Standard user rights. There is really no need to display a window with Administrator privilege.

Tell me how you liked this blog entry by adding a comment.