recv function not receive all bytes - sockets

I'm using delphi (RAD Studio 10) to make a little app client and server. The server only send text.
The next functions returns correctly the number of bytes sent. For example I send 'hello' (client side):
procedure TTCPConnection.Write(S: String);
var buff : string;
begin
buff = S+EOL;
WriteBuffer(buff, Length(buff));
end;
Where: S = 'hello' and EOL = #13#10, so buff = 'hello'+EOL.
procedure TTCPConnection.WriteBuffer(var Buffer; const Len: Cardinal);
var
a : Int16;
begin
a := send(FSocket, Buffer, Len, 0);
if (a = SOCKET_ERROR) and FConnected then
begin
HandleError;
Disconnect;
end;
end;
in the previous function: send(FSocket, Buffer, Len, 0) return 7, number of bytes sent ('hello+EOL').
Server side:
function TTCPConnection.ReadLn(Delim: String = EOL): String;
const BUFFER_SIZE = 255;
var
Buff, Buf: string;
I, L: Cardinal;
begin
Result := '';
I := 1;
L := 1;
SetLength(Buff, BUFFER_SIZE);
Buf := AnsiString(Buff);
while Connected and (L <= Cardinal(Length(Delim))) do
begin
if recv(FSocket, Buffer, Len, 0) < 1 then exit;
Buf := Buff[I];
if Buff[I] = Delim[L] then
...
...
end;
(In the previous code I include Buf var for debugger purposes only). When I debug obtain this result:
Buff = {'h', #0, 'e', #0, 'l', #0, 'l'} (7 bytes) and the next time this line is executed recv(FSocket, Buffer, Len, 0) the program does nothing, I guess this is because recv function has nothing to receive (7 bytes were sent).
I need help to make all bytes are received correctly. I do not know why they are in that order (h-#0-e-#0-l-#0-l).

Starting with RAD Studio 2009, string consists of WideChar characters. Char is an alias for WideChar and is 16 bit. The Length() function returns number of characters, not bytes.
You may want to consider using TEncoding to convert your string to a TBytes (array of byte) for sending and vice versa for receiving. I don't know your TTCPConnection class, so it may be you need to tweek the code, but here for the sending part using TEncoding.UTF8:
procedure TTCPConnection.Write(S: String);
var buff : TBytes;
begin
buff := TEncoding.UTF8.GetBytes(S+EOL); // Convert to bytes using UTF8 encoding
WriteBuffer(buff, Length(buff));
end;
And here is my receiver code from a testproject in XE5 where I used the TTcpServer component
procedure TForm3.TcpServer1Accept(Sender: TObject;
ClientSocket: TCustomIpClient);
var
a: array[0..255] of byte;
b: TBytes;
i, n: integer;
s: string;
begin
n := ClientSocket.ReceiveBuf(a,255,0);
SetLength(b, n);
for i := Low(b) to High(b) do b[i] := a[i]; // copy from the receive buffer
s := TEncoding.UTF8.GetString(b); // convert UTF8 bytes to string
Memo1.Lines.Add(format('%4d %s',[n,s]));
end;
Finally a few useful links to the documentation:
TEncoding
UTF-8 Conversion Routines

Related

Removing char from string in pascal cause question marks in console pascal

I am trying write simple program that will remove all 'o' letters from the string.
Example :
I love cats
Output:
I lve cats
I wrote following code :
var
x:integer;
text:string;
text_no_o:string;
begin
text:='I love cats';
for x := 0 to Length(text) do
//writeln(Ord(text[6]));
if(Ord(text[x])=111) then
else
text_no_o[x]:=text[x];
write(text_no_o);
end.
begin
end;
end.
When text is in English program works fine .
But if i change it to Russian . It returns we question marks in console.
Code with small modifications for Russian language.
var
x:integer;
text:string;
text_no_o:string;
begin
text:='Русский язык мой родной';
for x := 0 to Length(text) do
//writeln(Ord(text[6]));
if(Ord(text[x])=190) then
else
text_no_o[x]:=text[x];
write(text_no_o);
end.
begin
end;
end.
And result in console that i receive is :
Русский язык м�й р�дн�й
I expect receive
Русский язык мй рднй
As I got the problem can be caused incorrect encoding settings in console, so i should force pascal to use CP1252 instead ANSI .
I am using Free Pascal Compiler version 3.2.0+dfsg-12 for Linux .
P.S I am not allowed to use StringReplace or Pos
Simple solution:
function Simple_StripO (Text : String) : String;
var
i : integer;
Text2 : string;
begin
Text2 := '';
for i := 1 to Length(Text) do
if Text[i] <> 'o' then
Text2 := Text2 + Text[i];
Result := Text2; // Or Simple_StripO := Text2;
end;
The string is likely to be UTF8 encoded. So the cyrillic o is encoded as two chars $d0 $be. Here you replace one $be (=190). You need to replace both chars, though you cannot just test for the value of the char, because their meaning depends of surrounding chars.
Here is a way, remembering the current state (outside of letter or after first byte)
var
c: char;
text: string;
state: (sOutside, sAfterD0);
begin
text:= 'Русский язык мой родной';
state:= sOutside;
for c in text do
begin
if state = sOutside then
begin
if c = #$D0 then // may be the start of the letter
state := sAfterD0
else
write(c); // output this char because not part of letter
end
else if state = sAfterD0 then
begin
if c = #$BE then state := sOutside // finished skipping
else
begin
// chars do not form letter so output skipped char
write(#$D0, c);
state := sOutside;
end;
end
end;
writeln;
end.

WinSock: Server not receive data on same sequence that Client sent

I need send data from Client to Server in a determinated sequence, where the Server can receive these data also on same sequence sent by Client. On code below exists a problem that a data (that is a byte of control, 1) is received like a data of next data.
Ex:
On Client i have the following piece that send 1 (Connection._Input)
if SendInt(Sock, Ord(Connection._Input)) <= 0 then
Exit;
This byte sent above, on Server the correct is be received on Check variable, but instead is received on dwC.
See:
How can solve it?
Here is the complete code:
Client:
program _Client;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
WinSock,
SysUtils;
type
Connection = (Desktop, _Input);
const
SendBuf: array [0 .. 9] of AnsiChar = ('A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', #0);
function SendInt(S: TSocket; I: Integer): Integer;
begin
Result := send(S, I, SizeOf(I), 0);
end;
function ConnectServer: TSocket;
var
Wsa: WSAData;
Client: sockaddr_in;
S: TSocket;
Rslt: Integer;
begin
S := INVALID_SOCKET;
try
Rslt := WSAStartup(MakeWord(2, 2), Wsa);
if Rslt = NO_ERROR then
begin
S := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if S <> INVALID_SOCKET then
begin
Client.sin_family := AF_INET;
Client.sin_addr.s_addr := inet_addr('192.168.15.6');
Client.sin_port := htons(5099);
if connect(S, Client, SizeOf(Client)) <> SOCKET_ERROR then
Writeln('Connected successfully!');
end;
end;
except
Writeln(SysErrorMessage(WSAGetLastError));
end;
Result := S;
end;
function DesktopThread(P: Pointer): DWORD; stdcall;
var
Sock: TSocket;
dwC, dwD, dwE, dwF, dwG: DWORD;
A, B: Integer;
begin
Result := 0;
Sock := ConnectServer;
if send(Sock, SendBuf, SizeOf(SendBuf), 0) <= 0 then
Exit;
if SendInt(Sock, Ord(Connection.Desktop)) <= 0 then
Exit;
dwC := 111;
dwD := 222;
dwE := 333;
dwF := 444;
dwG := 555;
repeat
if recv(Sock, A, SizeOf(A), 0) <= 0 then
Exit;
if recv(Sock, B, SizeOf(B), 0) <= 0 then
Exit;
if SendInt(Sock, Ord(Connection._Input)) <= 0 then
Exit;
if SendInt(Sock, dwC) <= 0 then
Exit;
if SendInt(Sock, dwD) <= 0 then
Exit;
if SendInt(Sock, dwE) <= 0 then
Exit;
if SendInt(Sock, dwF) <= 0 then
Exit;
if SendInt(Sock, dwG) <= 0 then
Exit;
// Writeln(Format('%s', [SysErrorMessage(WSAGetLastError)]));
Writeln(Format('dwC: %d, dwD: %d, dwE: %d, dwF: %d, dwG: %d',
[dwC, dwD, dwE, dwF, dwG]));
until True;
end;
var
ThrId: Cardinal;
begin
try
CreateThread(nil, 0, #DesktopThread, nil, 0, ThrId);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Server:
program _Server;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
WinSock,
SysUtils;
type
Connection = (Desktop, Input, _End);
const
Buffer: array [0 .. 9] of AnsiChar = ('A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', #0);
function SendInt(S: TSocket; I: Integer): Integer;
begin
Result := send(S, I, SizeOf(I), 0);
end;
function ClientThread(P: Pointer): DWORD; stdcall;
var
Buf: array [0 .. SizeOf(Buffer) - 1] of AnsiChar;
Sock: TSocket;
Check: BOOL;
A, B: Integer;
_connection: Connection;
dwC, dwD, dwE, dwF, dwG: DWORD;
begin
Result := 0;
Sock := TSocket(P);
if recv(Sock, Buf, SizeOf(Buffer), 0) <= 0 then
begin
closesocket(Sock);
Result := 0;
Exit;
end;
if not CompareMem(#Buf, #Buffer, SizeOf(Buffer)) then
begin
closesocket(Sock);
Result := 0;
Exit;
end;
if recv(Sock, _connection, SizeOf(_connection), 0) <= 0 then
begin
closesocket(Sock);
Result := 0;
Exit;
end;
if _connection = Connection.Desktop then
begin
A := 666;
B := 777;
repeat
if SendInt(Sock, A) <= 0 then
Exit;
if SendInt(Sock, B) <= 0 then
Exit;
if recv(Sock, Check, SizeOf(Check), 0) <= 0 then
Exit;
Writeln(BoolToStr(Check));
{ if not Check then
continue; }
if recv(Sock, dwC, SizeOf(dwC), 0) <= 0 then
Exit;
if recv(Sock, dwD, SizeOf(dwD), 0) <= 0 then
Exit;
if recv(Sock, dwE, SizeOf(dwE), 0) <= 0 then
Exit;
if recv(Sock, dwF, SizeOf(dwF), 0) <= 0 then
Exit;
if recv(Sock, dwG, SizeOf(dwG), 0) <= 0 then
Exit;
// Writeln(Format('%s', [SysErrorMessage(WSAGetLastError)]));
Writeln(Format('dwC: %d, dwD: %d, dwE: %d, dwF: %d, dwG: %d',
[dwC, dwD, dwE, dwF, dwG]));
until True;
end;
end;
function StartServer(Port: Integer): Boolean;
var
_wsdata: WSAData;
serverSocket, S: TSocket;
_addrIn, _addr: sockaddr_in;
addrSize: Integer;
tid: Cardinal;
begin
Result := False;
if WSAStartup(MakeWord(2, 2), _wsdata) <> 0 then
Exit;
serverSocket := socket(AF_INET, SOCK_STREAM, 0);
if serverSocket = INVALID_SOCKET then
Exit;
_addrIn.sin_family := AF_INET;
_addrIn.sin_addr.S_addr := INADDR_ANY;
_addrIn.sin_port := htons(Port);
if bind(serverSocket, _addrIn, SizeOf(_addrIn)) = SOCKET_ERROR then
Exit;
if listen(serverSocket, SOMAXCONN) = SOCKET_ERROR then
Exit;
addrSize := SizeOf(_addrIn);
getsockname(serverSocket, _addrIn, addrSize);
Writeln(Format('Listening on port %d.' + #13, [ntohs(_addrIn.sin_port)]));
while True do
begin
S := accept(serverSocket, #_addr, #addrSize);
CreateThread(nil, 0, #ClientThread, Pointer(S), 0, tid);
end;
Result := True;
end;
begin
try
if not StartServer(5099) then
Writeln(SysErrorMessage(WSAGetLastError));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
You have a misalignment of data size in client vs server.
type
Connection = (Desktop, _Input);
Default size of an enumeration in Delphi is byte. This is in itself OK, but you handle these differently in the client and the server.
You send from the client using your SendInt() function which converts to integer.
On the server side you receive it as a SizeOf(_connection) which is only a byte. Because of the byte order, the 1 remains in the buffer and is later read into dwC.
You can correct the error either by setting Minimum enum size in project options to doubleword or by sending as byte.
Edit after comment
Indeed you also have another error, or maybe misunderstanding.
From the client you send
SendInt(Sock, Ord(Connection._Input))
which is received by the server as
var
Check: BOOL;
....
recv(Sock, Check, SizeOf(Check), 0) ,
Then you write it out as
Writeln(BoolToStr(Check));
and the console shows '-1'. But that is not an error, it is documented:
System.SysUtils.BoolToStr
Value of B Value of UseBoolStrs Value of returned string
true false '-1'
Perhaps you want to show it as the enum value instead.

Understanding legacy pascal

So I need to understand what this code is doing. I don't know pascal or cryptography, and am struggling to understand what is going on in here. I need to reverse engineer SHA1DigestToHex into scala and am totally lost beyond learning pascal. Can you tell me what this function is doing? Or how I can go about figuring it out?
Function SHA1DigestToHex (const Digest : T160BitDigest) : String;
Begin
Result := DigestToHex (Digest, Sizeof (Digest));
End;
Function DigestToHex (const Digest; const Size : Integer) : String;
Begin
SetLength (Result, Size * 2);
DigestToHexBuf (Digest, Size, Pointer (Result)^);
End;
Procedure DigestToHexBuf (const Digest; const Size : Integer; const Buf);
const s_HexDigitsLower : String [16] = '0123456789abcdef';
var I : Integer;
P : PChar;
Q : PByte;
Begin
P := #Buf;;
Assert (Assigned (P), 'Assigned (Buf)');
Q := #Digest;
Assert (Assigned (Q), 'Assigned (Digest)');
For I := 0 to Size - 1 do
begin
P^ := s_HexDigitsLower [Q^ shr 4 + 1];
Inc (P);
P^ := s_HexDigitsLower [Q^ and 15 + 1];
Inc (P);
Inc (Q);
end;
End;
UPDATE
type
PByte = ^Byte;
PWord = ^Word;
PLongWord = ^LongWord;
T128BitDigest = record
case integer of
0 : (Int64s : Array [0..1] of Int64);
1 : (Longs : Array [0..3] of LongWord);
2 : (Words : Array [0..7] of Word);
3 : (Bytes : Array [0..15] of Byte);
end;
P128BitDigest = ^T128BitDigest;
T160BitDigest = record
case integer of
0 : (Longs : Array [0..4] of LongWord);
1 : (Words : Array [0..9] of Word);
2 : (Bytes : Array [0..19] of Byte);
end;
P160BitDigest = ^T160BitDigest;
const
MaxHashDigestSize = Sizeof (T160BitDigest);
Procedure DigestToHexBuf (const Digest; const Size : Integer; const Buf);
Function DigestToHex (const Digest; const Size : Integer) : String;
Function Digest128Equal (const Digest1, Digest2 : T128BitDigest) : Boolean;
Function Digest160Equal (const Digest1, Digest2 : T160BitDigest) : Boolean;
It merely converts the bytes of the binary buffer passed in as Buf into a string of hexadecimal digits representing the same bytes in Digest.
So e.g. if Buf is the byte array (0x12, 0x34, 0x56), then afterwards Digest will be '123456'.
Here's a simpler Pascal (Delphi) version that does the same thing:
function SHA1DigestToHex2(const Digest : T160BitDigest) : string;
const s_HexDigitsLower : array[0..15] of char = '0123456789abcdef';
var
i, j: Integer;
Begin
SetLength(Result, sizeof(Digest) * 2);
i := 1;
j := 0;
while j < sizeof(Digest) do begin
Result[i] := s_HexDigitsLower[Digest.Bytes[j] shr 4];
Result[i+1] := s_HexDigitsLower[Digest.Bytes[j] and $F];
inc(i, 2);
inc(j);
end;
End;

Why does TTcpClient drop data on SendStream()?

When I call TTcpClient.SendStream(MyStream), it advances MyStream.Position to 8704, and I have to call SendStream() repeatedly to get it to completely send my stream. However, the data received is missing chunks of 512 bytes about every 8K.
Note: This question is rhetorical, because I suffered through trying and failing to find a solution on the web. I found the bug in Delphi 7 Sockets.pas, and want to publish the solution for the good of the community.
The problem is a coding bug in Delphi 7 Sockets.pas. The bug causes any stream larger than about 8K (exact size is OS-dependent) to lose 512-byte chunks of data. The SendStream implementation uses a repeat..until loop to pull 512-byte buffers from the caller's stream for sending with SendBuf(), and it continues as long as the stream has data and SendBuf() does not return equal to SOCKET_ERROR. The loss occurs when the Windows socket buffer fills, causing SendBuf() to return equal to SOCKET_ERROR, but at that point up to 512 bytes have already been read from the caller's stream and the stream Position has been advanced - but that Position is not restored on exit. Original Sockets.pas code:
function TBaseSocket.SendStream(AStream: TStream): Integer;
var
BufLen : Integer;
Buffer: array[0..511] of Byte;
begin
Result := 0;
if Assigned(AStream) then begin
repeat
BufLen := AStream.Read(Buffer, SizeOf(Buffer));
until (BufLen = 0) or (SendBuf(Buffer, BufLen) = SOCKET_ERROR);
end;
end;
And here's a fix:
function TBaseSocket.SendStream(AStream: TStream): Integer;
var
Quit : boolean;
BufLen,OldPosition : Integer;
Buffer: array[0..511] of Byte;
begin
Result := 0;
if Assigned(AStream) then begin
repeat
OldPosition := AStream.Position;
BufLen := AStream.Read(Buffer, SizeOf(Buffer));
if (BufLen > 0) then begin
Quit := (SendBuf(Buffer, BufLen) = SOCKET_ERROR);
if Quit then AStream.Position := OldPosition; //restore!
end else begin //BufLen = 0
Quit := true;
end;
until Quit;
end;
end;

Form resource not found after on-the-fly String-Resource translation

I have a problem which occurs only in a very small customer range and I would like to ask if you might give me a hint where the problem might be. The program works for 98% of the customers. Alas, it is not possible that I work with the customers to debug the issue, because their knowledge of Windows and computers is very basic. It is also not possible that I send multiple versions of the product to them, since they don't even know how to install software (the admins do all the stuff).
First of all, I translate all RT_STRING resources on-the-fly, so that the language-switching in the program also affects hardcoded stuff like "Yes", "No", "Cancel" etc., which would only be possible by compiling 2 EXE files.
The code (I have tried to left away as much unnecessary stuff as possible, but since I don't know where the problem is, I provided as much details for the bug as possible):
The ony-the-fly resource translation
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'}
Then I use a wait form:
unit Wait;
interface
uses
...
type
TWaitForm = class(TForm)
...
end;
var
WaitForm: TWaitForm;
implementation
{$R *.dfm}
...
end;
The wait form will be called by dynamically showing the form:
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;
The error message at 2% of the customers:
Resource TWaitForm was not found
However, other forms are working.
There are 2 theories I can think of:
1) Did the resource translation corrupt the DLL file / part of the RCData section? (Maybe a bug in the WinAPI's UpdateResource ?)
2) Is there a problem with the dynamic showing of the wait form (since other "static" forms are shown?)