Wednesday, October 17, 2007

Code signing large executables

I've been having some trouble signing some of my large (~1.8 GB!) executables. (In case you wonder, they contain a lot of multimedia content packed into a single file for downloading.)

The Microsoft Authenticode tools (signcode.exe, signtool.exe) failed with error messages like "Not enough storage is available to process this command". I've also tried signing them programmatically, using the CAPICOM API, but that also failed (the tools seem to use CAPICOM internally, too).

Looking around on the internet for a solution to this problem, I was very pleased to find out that Mono includes a code signing tool compatible with Authenticode. The tool failed as well... but there is source code! Here's the interesting part:

byte[] file;
using (FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
file = new byte[fs.Length];
fs.Read (file, 0, file.Length);
fs.Close ();
}

;-) After a quick fix and a rebuild, I'm able to successfully sign all my executables so far. Big kudos to the Mono team!

SetToString, StringToSet

The SetToString, StringToSet functions in TypInfo unit take PPropInfo as parameter which means you can only use them with set types for which RTTI (a published property) exists.

Here are my versions which take PTypeInfo instead so you can use them with set types in general, even without a published property declared for the type:

uses
SysUtils, TypInfo;

function GetOrdValue(Info: PTypeInfo; const SetParam): Integer;
begin
Result := 0;

case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Result := Byte(SetParam);
otSWord, otUWord:
Result := Word(SetParam);
otSLong, otULong:
Result := Integer(SetParam);
end;
end;

procedure SetOrdValue(Info: PTypeInfo; var SetParam; Value: Integer);
begin
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Byte(SetParam) := Value;
otSWord, otUWord:
Word(SetParam) := Value;
otSLong, otULong:
Integer(SetParam) := Value;
end;
end;

function SetToString(Info: PTypeInfo; const SetParam; Brackets: Boolean): AnsiString;
var
S: TIntegerSet;
TypeInfo: PTypeInfo;
I: Integer;
begin
Result := '';

Integer(S) := GetOrdValue(Info, SetParam);
TypeInfo := GetTypeData(Info)^.CompType^;
for I := 0 to SizeOf(Integer) * 8 - 1 do
if I in S then
begin
if Result <> '' then
Result := Result + ',';
Result := Result + GetEnumName(TypeInfo, I);
end;
if Brackets then
Result := '[' + Result + ']';
end;

procedure StringToSet(Info: PTypeInfo; var SetParam; const Value: AnsiString);
var
P: PAnsiChar;
EnumInfo: PTypeInfo;
EnumName: AnsiString;
EnumValue, SetValue: Longint;

function NextWord(var P: PAnsiChar): AnsiString;
var
I: Integer;
begin
I := 0;
// scan til whitespace
while not (P[I] in [',', ' ', #0,']']) do
Inc(I);
SetString(Result, P, I);
// skip whitespace
while P[I] in [',', ' ',']'] do
Inc(I);
Inc(P, I);
end;

begin
SetOrdValue(Info, SetParam, 0);
if Value = '' then
Exit;

SetValue := 0;
P := PAnsiChar(Value);
// skip leading bracket and whitespace
while P^ in ['[',' '] do
Inc(P);
EnumInfo := GetTypeData(Info)^.CompType^;
EnumName := NextWord(P);
while EnumName <> '' do
begin
EnumValue := GetEnumValue(EnumInfo, EnumName);
if EnumValue < 0 then
begin
SetOrdValue(Info, SetParam, 0);
Exit;
end;
Include(TIntegerSet(SetValue), EnumValue);
EnumName := NextWord(P);
end;
SetOrdValue(Info, SetParam, SetValue);
end;

Example usage:

var
A: TAlignSet;
S: AnsiString;
begin
// set to string
A := [alClient, alLeft, alTop];
S := SetToString(TypeInfo(TAlignSet), A, True);
ShowMessage(Format('%s ($%x)', [S, Byte(A)]));

// string to set
S := '[alNone, alRight, alCustom]';
StringToSet(TypeInfo(TAlignSet), A, S);
ShowMessage(Format('%s ($%x)', [SetToString(TypeInfo(TAlignSet), A, True), Byte(A)]));
end;


Update: I've changed the declarations of untyped parameters to const where they're not modified (originally, they were incorrectly declared as var). Thanks, Joe, for your comment!

Thursday, July 12, 2007

QC48959

I've submitted QC report 48959, titled "TCustomProvider destructor does not call UnregisterProvider".

In TCustomProvider.Destroy there's some code which assumes that Owner is still assigned during destruction. This, however, is not the case if the provider is being destroyed implicitly because of ownership.

Typically, you put provider components directly on your remote data module in which case this is not a problem: it's unnecessary to call UnregisterProvider since the remote data module (IAppserver implementor which exposes the providers) itself is being destroyed.

However, if your remote data module is not the same object as the owner of your provider components (the owner only delegates the providers), you might encounter this problem, especially when the owner is released while the remote data module stays alive. In this case, the providers are not unregistered before they are freed and the remote data module keeps references to invalid pointers.

Saturday, June 09, 2007

DataSnap to the rescue

I'm maintaining a custom setup program used to install applications. In some cases it needs to perform actions which require administrative privileges, e.g. writing into Program Files directory, or modifying the registry under HKEY_LOCAL_MACHINE.

On Windows Vista, the privileges can be acquired by elevation. However, elevation actually switches the user context; but the setup program also needs to perform some actions within the context of the current user: create registry entries under HKEY_CURRENT_USER, copy files into their "My Documents" folder, etc. This means that I cannot simply use manifest to request administrator privileges statically.

Therefore, elevation needs to take place dynamically at runtime. COM elevation moniker can be used for this purpose. This blog post is very useful in that regard. (Thanks, Aleksander!)

So I've written an ActiveX DLL, an automation object with methods to expose file streams, create and delete directories, access registry and so on - things which need to be done under an administrator's account.

But then I realized that this would require two UAC prompts: first to register the ActiveX DLL (and it does have to be in the registry, registration-free activation doesn't seem to suffice in this case), and second to actually use the elevation moniker.

To show only one UAC prompt, I decided to use two separate processes: server running elevated (via manifest) and client running under the current user's account. Since I already had written the automation object, I decided to reuse it. For the inter-process communication, I decided to use DataSnap with TSharedMemoryConnection (a memory-mapped file transport) which I've written for the purpose.

To reduce unnecessary registry clutter, instantiation of the COM class in the server process is simplified, no CoCreateObject is called and therefore no registration is required. Instead, the type information needed to dispatch method calls is used from the type library resource directly.

The client has the server linked in as a resource; if it detects that it's not running with sufficient privileges it extracts it to a temporary directory and launches it from there.

It all works very nicely and, IMHO, is another example of how powerful and flexible DataSnap is.

Saturday, May 05, 2007

QC45527


I've submitted QC report 45527 about a small, probably infrequently encountered issue with TCheckListBox: when themes are not used and you're using the Flat style, horizontal scrolling sometimes causes the checkboxes not to be redrawn properly.
Attached to the report are more details, screenshots and a sample project.

The problem somehow seems to be related to setting the clipping region in TCheckListBox.DrawCheck method. I was unable, so far, to find out the exact cause of the problem.

My proposed workaround which prepares an in-memory bitmap of the checkboxes and uses no clipping on the control's canvas directly is also attached to the report.