0
votes

Nowhere in the Windows documentation do I see a reference to a size limit to the resources one can add using UpdateResource, but it seems I have stumbled upon one - and it's tiny!

I was developing a Windows Ribbon app and wanted to programmatically build and attach the resource. Linking the resource using a $R directive worked just dandy, but I kept getting memory junk when attaching the very same thing from code.

I have managed to reduce it to a simple example using a string resource:

  Handle := BeginUpdateResource(PChar(DestFileName), True);
  try
    AddResource(Handle, 'STRING', 'ManyXs', StrUtils.DupeString('X', 1000));
  finally
    EndUpdateResource(Handle, False);
  end;

And AddResource is defined as:

procedure TForm2.AddResource(Handle: NativeUInt; ResType, ResName, Value: string);
begin
  if not UpdateResource(Handle, PChar(ResType), PChar(ResName), 1033,
    PChar(Value), Value.Length * SizeOf(Char)) then
    RaiseLastOSError;
end;

Please ignore my hard-coded language for the moment.

When I inspect the resource subsequent to calling this, I see a thousand Xs. Fabulous.

I can change the resource to 1990 Xs and it's fine. The moment it goes to 1991, I get nonsense written to the DLL. The size of the resource is correctly indicated as 3982 (1991 * 2 because it's Unicode), but the contents is just a dump of stuff from memory.

I can view larger resources with my resource editor, and the IDE routinely inserts larger resources (Delphi forms, for example), so I'm definitely missing something.

I've tried the following, despite not thinking any of them would make a difference (they didn't):

  1. Using just large memory buffers instead of strings
  2. Using the Ansi version of the UpdateResource function
  3. Many different resource types - what I really need to get working, is UIFILE
  4. Looking for other functions in the API (I found none)
  5. Combinations of 1, 2 and 3

Any ideas?

Update: Inspired by the comments and Jolyon's answer, tried a few more things.

First, I tried in Delphi XE7 and XE5 as well (original was in XE6). I don't have XE2 installed anymore, so i cannot confirm what Sertak has said. I'll find out if someone else in my office still has it installed.

Second, here is the memory buffer version:

procedure TForm2.AddResource(Handle: NativeUInt; const ResType, ResName, Value: string);
var
  Buffer: Pointer;
  BuffLen: Integer;
begin
  BuffLen := Value.Length * SizeOf(Char);
  GetMem(Buffer, BuffLen);
  try
    StrPCopy(PChar(Buffer), Value);

    if not UpdateResource(Handle, PChar(ResType), PChar(ResName), 1033,
      Buffer, BuffLen) then
      RaiseLastOSError;
  finally
    FreeMem(Buffer);
  end;
end;

I actually had a previous version of this code where I dumped the contents of that pointer into a file before the call to UpdateResource and the file saved correctly but the resource still saved junk. Then I did this version, which doesn't involve strings at all:

procedure TForm2.AddResource(Handle: NativeUInt; const ResType, ResName: string; 
  C: AnsiChar; Len: Integer );
var
  Buffer: Pointer;
  BuffLen: Integer;
begin
  BuffLen := Len;
  GetMem(Buffer, BuffLen);
  try
    FillMemory(Buffer, Len, Byte(C));

    if not UpdateResource(Handle, PChar(ResType), PChar(ResName), 1033,
      Buffer, BuffLen) then
      RaiseLastOSError;
  finally
    FreeMem(Buffer);
  end;
end;

With this version I still have the same problem when I use 3882 Xs. Of course, I'm now using single-byte characters, that's why it's double. But I have the exact same issue.

I did notice a difference between the versions in the output of TDUMP though. For versions 1 (strings) and 2 (string copied to buffer), my resource size is suddenly indicated as FFFFFF90 when I use 1991 characters. With version 3 (no strings), the size is the actual hex value of whatever size I used.

1
Bearing in mind I know very little of Delphi, is string a "narrow" string? The documentation for UpdateResource states that lpData must not point to ANSI data, it must be Unicode. So perhaps using WideString instead may be an idea? Looking here suggests that String defaults to be an AnsiString.icabod
@icabod In modern Delphi string is UTF-16 UnicodeString.David Heffernan
@DavidHeffernan: There's a modern Delphi!?!icabod
@icabod There is. Sounds like you are behind the times. Also a big mistake to use ancient delphi basics site. Delphi is well-documented, documentation is online. Keep up please! ;-)David Heffernan
Cannot duplicate, tested with XE2 with 50000 dupes.Sertac Akyuz

1 Answers

0
votes

The fact that you are getting "junk" data but data of the right size leads me to suspect the PChar() casting of the string value yielding an incorrect address. This normally should not be a problem, but I wonder if the issue is some strange behaviour as the result of passing the result of a function directly into a parameter of a method ? A behaviour which for some strange reason is only triggered when the string involved reaches a certain size, perhaps indicating some edge-case optimization behaviour.

This might also explain difficulties in reproducing the problem if it is some combination of optimization (and/or other compiler settings) in some specific version of Delphi.

I would suggest to try eliminating this possibility by creating your new resource string in an explicit variable and passing that to the AddResource() method. I would also suggest that you be explicit in your parameter semantics and since the string involved is not modified, nor intended to be modified, in the AddResource() method, declare it as a formally const parameter.

You do mention having tried an alternative approach using "memory buffers". If the above suggestions do not resolve the problem, perhaps it would be helpful to post a minimal example that reproduces the problem using those, to eliminate any possible influence on things by the rather more exotic "string" type.