在即时字符串资源转换后找不到表单资源

时间:2014-11-21 08:31:23

标签: forms delphi winapi resources translation

我遇到的问题只发生在非常小的客户范围内,我想问一下你是否可以给我一个提示问题的提示。该计划适用于98%的客户。唉,我不可能与客户一起调试问题,因为他们对Windows和计算机的了解非常基础。我也不可能向他们发送多个版本的产品,因为他们甚至不知道如何安装软件(管理员会做所有的事情)。

首先,我即时翻译所有RT_STRING资源,以便程序中的语言切换也会影响硬编码的内容,例如" Yes"," No& #34;,"取消"等,这只能通过编译2个EXE文件来实现。

代码(我试图尽可能多地留下不必要的东西,但由于我不知道问题出在哪里,我尽可能多地提供了bug的详细信息):

ony-the-fly资源翻译

procedure TranslateResources;
var
  i: integer;
  s: string;
{$IF NOT Declared(FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)}
const
  FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = $2000;
{$IFEND}
begin
  // I copy all resources in a dummy DLL (without code), because
  // 1) The resources are the only thing we need when changing the resource module
  // 2) If the EXE code/debug sections are too long, BeginUpdateResource() will ruin the performance heavily
  FTempFile := IncludeTrailingPathDelimiter(GetTempDirectory) + GetRandomString(8)+'.dll';

  // Transfers all resources from ParamStr(0) into the dummy DLL at FTempFile
  ReGenerateResourceFile(FTempFile);

  // if necessary, remove readonly flag
  SetFileAttributes(PChar(FTempFile), FILE_ATTRIBUTE_OFFLINE or
                                      FILE_ATTRIBUTE_NOT_CONTENT_INDEXED or
                                      FILE_ATTRIBUTE_TEMPORARY );

  for i := 0 to Length(RTLResStringTranslationArray)-1 do
  begin
    s := Translate(RTLResStringTranslationArray[i].TranslationID);

    if s <> '' then
    begin
      // Translate the string
      UpdateResString(RTLResStringTranslationArray[i].ResStrDescriptor.Identifier, s);
    end;
  end;

  LoadNewResourceModule(FTempFile):
end;

procedure ReGenerateResourceFile(OutputFile: string);
var
  hUpd: Cardinal;
  rs: TResourceStream;
  fs: TFileStream;
begin
  // As template we use a dummy DLL which contains no code.
  // We will implement all resources from ParamStr(0) into it, before we translate the strings.
  rs := TResourceStream.Create(HInstance, 'DUMMYDLL', 'DLL');
  fs := TFileStream.Create(OutputFile, fmCreate or fmOpenWrite);
  try
    fs.CopyFrom(rs, rs.Size)
  finally
    rs.Free;
    fs.Free;
  end;

  // Transfer resources from our EXE into the dummy DLL file
  hUpd := BeginUpdateResource(PChar(OutputFile), true);
  try
    EnumResourceTypes(hInstance, @_enumResTypesProc, hUpd);
  finally
    EndUpdateResource(hUpd, false)
  end;
end;

// This is based on reinit.pas from Borland's RichEdit example; slightly modified
function LoadNewResourceModule(PatchedFile: string): LongInt;
var
  NewInst: Longint;
  CurModule: PLibModule;
begin
  Result := 0;                                 

  // Win95: "Initialization routine failed"
  // NewInst := LoadLibrary(PChar(PatchedFile));

  NewInst := LoadLibraryEx(PChar(PatchedFile), 0, LOAD_LIBRARY_AS_DATAFILE);

  CurModule := LibModuleList;
  Result := 0;
  while CurModule <> nil do
  begin
    if CurModule.Instance = HInstance then
    begin
      if CurModule.ResInstance <> CurModule.Instance then
        FreeLibrary(CurModule.ResInstance);

      // Win95: ERangeError
      CurModule^.ResInstance := NewInst;

      Result := NewInst;
      Exit;
    end;
    CurModule := CurModule.Next;
  end;
end;

// Based on http://stackoverflow.com/questions/1498658/modifying-a-string-in-resource-of-an-exe
// Modified
procedure UpdateResString(const AStringIdent: Integer; const ANewString: WideString);
var
  ResData, TempData: TWordArray;
  iSection, iIndexInSection: Integer;
  i, iLen, iSkip, iPos: Integer;
begin
  // Calculate the resource string area and the string index in that area
  iSection := AStringIdent div 16 + 1;
  iIndexInSection := AStringIdent mod 16;

  ResData := ReadSectionCached(iSection);

  // Calculate the position of the string
  iLen := Length(ANewString);
  iPos := 0;
  for i := 0 to iIndexInSection do
  begin
    if iPos > High(ResData) then
    begin
      SetLength(ResData, iPos + 1);
      ResData[iPos] := 0;
    end;
    if i <> iIndexInSection then
    begin
      iSkip := ResData[iPos] + 1;
      Inc(iPos, iSkip);
    end;
  end;

  // Put data behind strings into TempData
  iSkip := 1{size} + ResData[iPos];
  SetLength(TempData, Length(ResData) - (iPos + iSkip));
  if Length(TempData) > 0 then
  begin
    CopyMemory(@TempData[0], @ResData[iPos + iSkip], Length(TempData)*SizeOf(TempData[0]));
  end;
  SetLength(ResData, iPos + (iLen + 1{size}) + Length(TempData));

  // Overwrite string
  ResData[iPos] := iLen;
  Inc(iPos);
  if iLen > 0 then
  begin
    CopyMemory(@ResData[iPos], @ANewString[1], iLen*SizeOf(ANewString[1]));
    Inc(iPos, iLen);
  end;

  // Append TempData after our new string
  if Length(TempData) > 0 then
  begin
    CopyMemory(@ResData[iPos], @TempData[0], Length(TempData)*SizeOf(TempData[0]));
  end;

  CacheSet(iSection, ResData);
end;

type
  TGlobalData = record
    GlobalPtr: Pointer;
    Length: integer;
  end;

function LoadResourcePtr(hModule: HMODULE; restype, resname: PChar; wIDLanguage: WORD): TGlobalData;
var
  hFind, hRes: THandle;
begin
  result.GlobalPtr := nil;
  result.Length    := -1;
  hFind := Windows.FindResourceEx(hModule, restype, resname, wIDLanguage);
  if hFind = 0 then RaiseLastOSError;
  hres := Windows.LoadResource(hModule, hFind);
  if hres = 0 then RaiseLastOSError;
  result.GlobalPtr := Windows.LockResource(hres);
  result.Length    := Windows.SizeofResource(hModule, hFind);
end;

function _enumResLangsProc(hmodule: HMODULE; restype, resname: PChar; wIDLanguage: WORD;
  lParam: LongInt): BOOL; stdcall;
var
  rs: TGlobalData;
begin
  rs := LoadResourcePtr(hmodule, restype, resname, wIDLanguage);
  UpdateResource(lParam, restype, resname, wIDLanguage, rs.GlobalPtr, rs.Length);
  result := true;
end;

function _enumResNamesProc(hmodule: HMODULE; restype, resname: PChar;
  lParam: LongInt): BOOL; stdcall;
begin
  EnumResourceLanguages(hmodule, restype, resname, @_enumResLangsProc, lParam);
  result := true;
end;

function _enumResTypesProc(hmodule: HMODULE; restype: PChar;
  lParam: LongInt): BOOL; stdcall;
begin
  EnumResourceNames(hmodule, restype, @_enumResNamesProc, lParam);
  result := true;
end;

{$R '..\dummydll\dummydll.RES'}

然后我使用等待表单:

unit Wait;

interface

uses
  ...

type
  TWaitForm = class(TForm)
    ...
  end;

var
  WaitForm: TWaitForm;

implementation

{$R *.dfm}

...

end;

将通过动态显示表单来调用等待表单:

procedure ShowWaitForm;
begin
   ...

  { I use my own _CreateForm function because it solves many workarounds for
  juicy stuff like half-modal windows (which can be hidden without user action),
  miscellaneous deadlocks etc. and to allow the form to be shown in a shared PAS file
  without the requirement to add it to every DPR file where the WaitForm API is used. }
  WaitForm := _CreateForm(TWaitForm, {Application.MainForm}AParent) as TWaitForm;

  WaitForm.Show;

  ...
end;

function _CreateForm(InstanceClass: TCustomFormClass; AParent: TCustomForm): TCustomForm;
var
  LOwner: TComponent;
begin
  if Assigned(AParent) then
  begin
    LOwner := AParent;
  end
  else if Assigned(Application) then
  begin
    LOwner := Application;
  end
  else
  begin
    LOwner := nil;
  end;

  result := InstanceClass.Create(LOwner);
end;

2%的客户的错误消息:

未找到资源TWaitForm

但是,其他形式正在发挥作用。

我能想到两种理论:

1)资源转换是否损坏了RCData部分的DLL文件/部分? (可能是WinAPI的UpdateResource中的一个错误?)

2)等待表单的动态显示是否存在问题(因为显示了其他&#34;静态&#34;表单?)

0 个答案:

没有答案