5

Running the following code in Delphi XE2 Win32 platform works. However, the same code compile in win64 platform will cause access violation in "EnumRCDataProc" if run in debug mode:

procedure TForm2.Button1Click(Sender: TObject);
  function EnumRCDataProc(hModule: THandle; lpszType, lpszName: PChar; lParam:
      NativeInt): Boolean; stdcall;
  begin
    TStrings(lParam).Add(lpszName);
    Result := True;
  end;

var k: NativeInt;
    L: TStringList;
    H: THandle;
begin
  H := LoadPackage('resource.bpl');
  L := TStringList.Create;
  try
    EnumResourceNames(H, RT_RCDATA, @EnumRCDataProc, NativeInt(L));
    ShowMessage(L.Text);
  finally
    L.Free;
    UnloadPackage(H);
  end;
end;

When debug the code in Delphi XE2 IDE on Win64 platform, I found the value of hModule in EnumRCDataProc doesn't match with variable H. I suspect that might be something wrong about the parameters I constructed for the EnumRCDataProc. However, I can't figure out how. Any ideas?

Chau Chee Yang
  • 18,422
  • 16
  • 68
  • 132

1 Answers1

5

The problem is that you have made EnumRCDataProc a local procedure. You need to move it outside the method.

function EnumRCDataProc(hModule: HMODULE; lpszType, lpszName: PChar; lParam:
    NativeInt): BOOL; stdcall;
begin
  TStrings(lParam).Add(lpszName);
  Result := True;
end;

procedure TForm2.Button1Click(Sender: TObject);
var k: NativeInt;
    L: TStringList;
    H: HMODULE;
begin
  H := LoadPackage('resource.bpl');
  L := TStringList.Create;
  try
    EnumResourceNames(H, RT_RCDATA, @EnumRCDataProc, NativeInt(L));
    ShowMessage(L.Text);
  finally
    L.Free;
    UnloadPackage(H);
  end;
end;

On first inspection I expected that the compiler would emit an error with your code:

E2094 Local procedure/function 'Callback' assigned to procedure variable

But it does not do so. I dug a little deeper and discovered that the callback parameter for EnumResourceNames is declared as type Pointer. If the header translation had declared this as a typed callback parameter then the above error message would indeed have been emitted. To my mind the header translation is poor in this regard. There seems very little to be gained from abandoning the safety of the type system.

The fact that your code works in 32 bit code is just a happy coincidence that relies on implementation details. Your luck runs out on 64 bit. Again, if the type checking system had been enabled, the compiler could have told you what was wrong immediately.

Some other comments:

  1. The EnumRCDataProc has a couple of incorrect types in its declaration: hModule should be of type HMODULE and the function result should be BOOL.
  2. LoadPackage is a rather heavyweight approach to getting a module handle. I would prefer to see LoadLibraryEx with the LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE and LOAD_LIBRARY_AS_IMAGE_RESOURCE options.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Compiler should not complain about that. Local enum function is not an issue at all. – OnTheFly Jan 12 '12 at 15:46
  • 1
    As long as the callback does not access any form thing that shouldn't be a problem. Then again, for that purpose only, taking the callback out is the correct thing.   What does the over-strike mean? Does that void the part of the answer above? – Sertac Akyuz Jan 12 '12 at 17:07
  • @Sertac You might get away with it in 32 bit code, but this is 64 bit code and the rules are different. The strike through is because I could not understand why the compiler was not spitting out E2094. But then I discovered the sloppy declaration of `EnumResourceNames`. – David Heffernan Jan 12 '12 at 17:09
  • @SertacAkyuz, thats correct, local sub is completely safe to invoke in the described manner. Error means no [possible] breakaway from the current scope is allowed. – OnTheFly Jan 12 '12 at 17:28
  • @user539484 If it was safe, explain the compiler error? You are relying on implementation details. And they change (enormously) with 64 bit. That's what happens when you break the rules. Your luck eventually runs out. – David Heffernan Jan 12 '12 at 17:35
  • 1
    In my opinion E2094 should be a warning, not an error. It should be in my discretion to access anything outer and crash the program. After all, I might not do that and then nothing will crash. On another note, even if it is declared a procedural type, I can bypass it by type casting, f.i. passing it like `TEnumRCDataProc(Pointer(@EnumRCDataProc))`. I fail to see the point of '2094' being a compile error.. – Sertac Akyuz Jan 12 '12 at 17:57
  • 1
    @Sertac Error is looking pretty good decision on 64 bit. Local functions are quite special and challenging to implement. Especially when there is recursion. Error is fine in my view since you can always cast to avoid it. – David Heffernan Jan 12 '12 at 18:01
  • 4
    @DavidHeffernan In 32bit Delphi the compiler makes the local function a "real" function if it doesn't use anything from the surrounding function. In 64bit the compiler always inserts the hidden base-pointer parameter. It certainly is an implementation detail but they could have easily decided not break existing code. But I guess they didn't thought/know about this issue when they implemented the 64bit compiler backend. – Andreas Hausladen Jan 12 '12 at 18:50
  • @Andreas I wonder about the implementation history of local functions. I have some vague recollection that they didn't always support recursive calling when they used local variables from the parent function. Wasn't that added in one of the very early Delphi releases, or perhaps even TP? When they added that then maybe left as *real functions* those that didn't need special treatment. And maybe with x64 they decided just to implement one form of local function that could support the full generality of calling. All pure speculation of course. – David Heffernan Jan 12 '12 at 20:06