Jump to content

Remy Lebeau

Members
  • Content Count

    2684
  • Joined

  • Last visited

  • Days Won

    113

Everything posted by Remy Lebeau

  1. It does. When it is called the 1st time with a nil buffer and 0 size, it returns the actual buffer size that is needed, which would include room for the null terminator. On the 2nd call, when the buffer is actually filled in, it returns the number of bytes actually written into the buffer, not counting the null terminator, but the actual null terminator is written. A lot of Win32 APIs work this way. Then either the "OK" text is not actually on the response, or the API is broken. That would imply a bug in the API that would need to be reported to Microsoft. The API doesn't provide the complete raw response, only pieces of it. You would have to use a packet sniffer like Wireshark or Fiddler to capture the complete raw response.
  2. Remy Lebeau

    DLL Load Issue

    Throwing exceptions across the DLL boundary in a vanilla DLL is NEVER a good idea, regardless of compiler version. Just don't do it. That can only work in a Package instead of a DLL, but then you can't use a Package across different Delphi versions.
  3. Remy Lebeau

    DLL Load Issue

    Maybe related to the change in default Floating-Point exception flags in Delphi 12? https://docwiki.embarcadero.com/RADStudio/Athens/en/What's_New#Disabling_Floating-Point_Exceptions_on_All_Platforms
  4. More like (O#0K#0#0#0) if you are looking at raw bytes. But yes, it should be 6 bytes (2 wide chars + 1 wide null terminator) since the Unicode version of WinHTTPQueryHeaders() is being called. Correct. And since the Result is a String, the SetLength() will actually allocate memory for a null terminator even though it is not being requested in the size. So, the code should be calling SetLength(2) for 'OK' but it will allocate memory for 3 Chars, enough for WinHTTPQueryHeaders() to fill in.
  5. You should not need to add the .pas file, just enable Debug DCUs instead, then you can step into the RTL's source code. Are you compiling for 64bit? Do you have all patches installed? Inspecting Delphi variables in C++ is a known issue. Hard to say without knowing what the true value of LSize is. Sounds more like LSize may be 0-2 bytes to begin with. That is pretty large for a 200 reply. Most servers just send an "OK" text. What does the raw response line actually look like on the wire? That should not be possible. If the specified length is greater than 0 then memory is guaranteed to be allocated or an exception thrown on failure. Personally, I would not have coded it the way they did. If WinHTTP asks for a buffer allocation then I would just make the 2nd API call unconditionally after allocating the requested memory. It looks like they are trying to ignore the case where the text is empty (just a null terminator), in which case they should have tested the requested LSize before allocating the memory. But that is just me. That attachment can't be found.
  6. Remy Lebeau

    XML parsing problems

    Again, can you provide the actual XML? The error message is complaining about an invalid character. Non-ANSI characters on their own are not invalid characters in XML, but certain characters in certain contexts may be. For instance, text elements do not allow Unicode codepoints in the range of control characters and surrogates. We need more information to diagnose the problem. SOAP uses well-defined XML. So, either your server is not sending valid XML correctly, or more likely you are just not parsing it correctly. Also, to be clear - you are not getting an Access Violation. You are getting a Parsing error. Those are two different things. Then the encoding is probably not correct to begin with. Are you receiving and saving/parsing the XML as a string, or as raw bytes? You should be doing the latter, not the former.
  7. One thing to note - ReadHeaders() calls WinHttpQueryHeaders(), but WinHttpQueryHeadersEx() is documented as not accepting WINHTTP_QUERY_STATUS_TEXT, so maybe Microsoft made an update that affects WinHttpQueryHeaders() similarly and didn't document it? Just guessing. What does that have to do with retrieving the status text? It doesn't come from those headers.
  8. Remy Lebeau

    TRESTClient encode issue

    Or that! ☝️ Which is basically the same as ToJSON() with all options turned off.
  9. Remy Lebeau

    TRESTClient encode issue

    This has nothing to do with the TRESTClient itself. You are receiving the response, parsing it into a TJSONObject, and then converting its child data back into a JSON formatted string. What you are seeing is the JSON encoding. By default, ToJSON() encodes non-ASCII Unicode characters in '\uXXXX' format: function TJSONAncestor.ToJSON: string; begin Result := ToJSON([TJSONOutputOption.EncodeBelow32, TJSONOutputOption.EncodeAbove127]); end; To avoid that, you will have to directly call the ToJSON() overload that accepts a TJSONOutputOptions parameter so you can omit the EncodeAbove127 (and EncodeBelow32) flag, eg: (myResponse.JSONValue as TJSONObject).GetValue('objectProperty1').ToJSON([]) // <-- note the brackets!
  10. Remy Lebeau

    XML parsing problems

    Do you mean the XML prolog? can't you just add one if it is missing before you parse the XML? What's wrong with that? And? Xml supports Unicode characters. Any conformant XML parser will handle that. By default, it uses MSXML on Windoes. But, you can change the library used, via the DOMVendor property. Can you provide an example XML that you are having trouble with?
  11. Remy Lebeau

    BringToFront doesn't work

    TDrawGrid is derived from TWinControl, whereas TImage is derived from TGraphicControl. Both controls in your DFM have the same Parent (the Form). A graphical control is rendered directly onto the canvas of its Parent, and thus can never appear on top of a child windowed control of the same Parent. As suggested earlier, you will have to make the TImage be a child of the TDrawGrid, or put the TImage on a separate Form (or Frame) that is on top of the TDrawGrid.
  12. Remy Lebeau

    BringToFront doesn't work

    There is no such method in VCL.
  13. Remy Lebeau

    Show a Form from inside a Thread

    The only problem I see with this code is that you are not Synchronize'ing access to the Form's Memo. You must do so. Move that Assign() call into your ShowForm() method. //Tprintqueue(FForm).MEMO1.LINES.ASSIGN(L); Synchronize(ShowForm); ... procedure Tstuckthread.ShowForm; begin Tprintqueue(FForm).Memo1.Lines.Assign(L); FForm.Show; end;
  14. Remy Lebeau

    Problems with WaitForAll

    No, that is not what I meant. Calling TThread.Synchronize() in the main thread will just call the specific procedure directly, as if Synchronize() were not being used at all. What I actually meant is calling WaitForAll() inside of TThread.CreateAnonymous() or TTask.Run(), for instance.
  15. Remy Lebeau

    Strict type checking for tObject.

    Perhaps this will help: Magic behind FreeAndNil
  16. Remy Lebeau

    migrating projects to RAD Studio 12

    Like this: class TForm1 : public TForm { ... private: static const int c = 9; TBitmapLayer* pLayer[c]; public: __fastcall TForm1(TComponent *Owner); __fastcall ~TForm1(); ... }; __fastcall TForm1::TForm1(TComponent *Owner) : TForm(Owner) { ... for(int j = 0; j < c; ++j) { pLayer[j] = new TBitmapLayer(ImageBackground->Layers); pLayer[j]->Bitmap->DrawMode = dmBlend; pLayer[j]->Scaled = true; ... } ... } __fastcall TForm1::~TForm1() { ... for(int j = 0; j < c; ++j) { delete pLayer[j]; } ... }
  17. Remy Lebeau

    migrating projects to RAD Studio 12

    A word of advice - in C++, NEVER use the Form's OnCreate and OnDestroy events! (they are perfectly safe to use in Delphi only). The events are based on Delphi's object creation model (derived classes created before base classes), which is different than C++'s creation model (base classes created before derived classes). Also, their behavior has changed a few times over the years (due to internal changes [and bugs] related to handling of the TForm.OldCreateOrder property), so they are not always consistent in C++. As such, the events can be called before the Form's C++ constructor and after its destructor, respectively (I've seen it happen), which leads to undefined behavior in C++. In C++, ALWAYS use the Form's actual constructor/destructor instead.
  18. Remy Lebeau

    Problems with WaitForAll

    TTask.WaitForAll() is a blocking function. If you call it in the main thread, the main message loop will be blocked. That means no UI updates, no TThread.Synchronize() or TThread.Queue() executions, nothing until the wait is finished. There are some simple solutions: You could just call WaitForAll() in a separate thread, not in the main thread. If you must call WaitForAll() in the main thread, then call it in a loop with a short timeout. Each time it times out, call Application.ProcessMessages(). Each time a task is finished, stop waiting on that task. Stop the loop when there are no more tasks to wait on.
  19. Remy Lebeau

    Unable to save from TImage to file

    That's fine, but you need to synchronize it with the main thread snce you're accessing a UI control. //MemoryStream.LoadFromFile(FImageFilePath); TThread.Synchronize(nil, procedure begin Image1.Bitmap.SaveToStream(MemoryStream); end); The alternative is to create the MemoryStream in the main thread and save the bitmap into it, then pass the MemoryStream to your worker thread for sending.
  20. Remy Lebeau

    Unable to save from TImage to file

    How big is your JSON? The IOHandler.MaxLineLength property is 16K characters by default. You can set it to a larger value if needed, such as MaxInt. I have now updated my earlier example to show that. OK. Make sure you synchronize that code so it runs in the main UI thread and not in your worker thread.
  21. Remy Lebeau

    Unable to save from TImage to file

    There is no possible way it could have worked before the way you showed it earlier. That code had logic bugs in the communications. That is because you are using ReadString() incorrectly. You are writing the JSON as a string without sending either a leading size or a terminator, so the server will have no way to know when the string actually ends. But you are not telling ReadString() how many bytes to read, and when the ABytes parameter is <= 0 then ReadString() will just return a blank string since it has nothing to read. Hence the parse error. The examples I gave you in my previous reply 1) use WriteLn() and ReadLn() to send/read the JSON with a terminating CRLF, and 2) use Write(TStream) and ReadStream/ReadString() to send/read the JSON with a leading size. Makes sense, because SaveImageToDisk() uses a relative file path instead of an absolute file path.
  22. Remy Lebeau

    Unable to save from TImage to file

    I don't see how that is possible given the bugs I mentioned in the code you showed. Also, I just now noticed another bug I didn't see earlier - your server code is not synchronizing the call to SaveImageToDisk(). OK, but the rest of the server code you showed is still buggy. First off, you should not be calling TIdTCPClient.Connect() in the main UI thread. In fact, modern Android versions do not allow any network operations on the main thread at all. Second, that client send code does not match the server read code you showed earlier! Your client is sending the JSON using IOHandler.WriteLn(), but your server is reading the JSON using IOHandler.ReadStream(). That will not work! You need to use IOHandler.ReadLn() on the server side, eg: procedure TMainForm.SendPicture; var Host, Msg: string; Port: Integer; begin {$IFDEF MSWINDOWS} FImageFilePath := TPath.Combine(TPath.GetDocumentsPath, EditImage.Text); {$ENDIF} {$IFDEF ANDROID} FImageFilePath := TPath.Combine(TPath.GetSharedDocumentsPath, SetupForm.EditImage.Text); {$ENDIF} if not FileExists(FImageFilePath) then begin ShowMessage('No such file at ' + FImageFilePath + '!'); Exit; end; Host := SetupForm.EditHost.Text.Trim; if Host.IsEmpty then begin ShowMessage('Wrong host value!'); Exit; end; if not TryStrToInt(SetupForm.EditPort.Text, Port) then begin ShowMessage('Wrong port value!'); Exit; end; Msg := Edit1.Text; TTask.Run( procedure var MemoryStream: TMemoryStream; JObj: TJSONObject; JsonStr: string; begin try JObj := TJSONObject.Create; try MemoryStream := TMemoryStream.Create; try MemoryStream.LoadFromFile(FImageFilePath); JObj.AddPair('image_encoded', TIdEncoderMIME.EncodeStream(MemoryStream)); finally MemoryStream.Free; end; JObj.AddPair('message', Msg); JsonStr := JObj.ToJSON; finally JObj.Free; end; if IdTCPClient1.Connected then IdTCPClient1.Disconnect; IdTCPClient1.Host := Host; IdTCPClient1.Port := Port; try IdTCPClient1.Connect; except TThread.Queue(nil, procedure begin ShowMessage('Connection error!'); end); Exit; end; try try IdTCPClient1.IOHandler.WriteLn(JSonStr); finally IdTCPClient1.Disconnect; end; except TThread.Queue(nil, procedure begin ShowMessage('Send error!'); end); Exit; end; TThread.Queue(nil, procedure begin ShowMessage('BASE64 Image and message successfully sent to server!'); end); except on E: Exception do begin TThread.Queue(nil, procedure begin ShowMessage('Error! ' + E.Message); end); end; end; end); end; procedure TMainForm.IdTCPServer1Execute(AContext: TIdContext); var JSON: TJSONObject; MemoryStream: TMemoryStream; JsonStr, Msg: String; begin AContext.Connection.IOHandler.MaxLineLength := MaxInt; JsonStr := AContext.Connection.IOHandler.ReadLn; MemoryStream := nil; try JSON := TJSONObject.ParseJSONValue(JsonStr) as TJSONObject; try MemoryStream := TMemoryStream.Create; TIdDecoderMIME.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream); Msg := JSON.GetValue('message').Value; finally JSON.Free; end; MemoryStream.Position := 0; TThread.Synchronize(nil, procedure begin Edit2.Text := Msg; Image1.Bitmap.LoadFromStream(MemoryStream); end); finally MemoryStream.Free; end; TThread.Synchronize(nil, SaveImageToDisk); end; However, if there is any possibility that the JSON may have any embedded line breaks, then using IOHandler.WriteLn/ReadLn will not work, so you should use IOHandler.Write(TStream) with IOHandler.ReadStream/ReadString() instead, eg: procedure TMainForm.SendPicture; var Host, Msg: string; Port: Integer; begin {$IFDEF MSWINDOWS} FImageFilePath := TPath.Combine(TPath.GetDocumentsPath, EditImage.Text); {$ENDIF} {$IFDEF ANDROID} FImageFilePath := TPath.Combine(TPath.GetSharedDocumentsPath, SetupForm.EditImage.Text); {$ENDIF} if not FileExists(FImageFilePath) then begin ShowMessage('No such file at ' + FImageFilePath + '!'); Exit; end; Host := SetupForm.EditHost.Text.Trim; if Host.IsEmpty then begin ShowMessage('Wrong host value!'); Exit; end; if not TryStrToInt(SetupForm.EditPort.Text, Port) then begin ShowMessage('Wrong port value!'); Exit; end; Msg := Edit1.Text; TTask.Run( procedure var MemoryStream: TMemoryStream; StringStream: TStringStream; JObj: TJSONObject; begin try StringStream := nil; try JObj := TJSONObject.Create; try MemoryStream := TMemoryStream.Create; try MemoryStream.LoadFromFile(FImageFilePath); JObj.AddPair('image_encoded', TIdEncoderMIME.EncodeStream(MemoryStream)); finally MemoryStream.Free; end; JObj.AddPair('message', Msg); StringStream := TStringStream.Create(JObj.ToJSON, TEncoding.UTF8); finally JObj.Free; end; if IdTCPClient1.Connected then IdTCPClient1.Disconnect; IdTCPClient1.Host := Host; IdTCPClient1.Port := Port; try IdTCPClient1.Connect; except TThread.Queue(nil, procedure begin ShowMessage('Connection error!'); end); Exit; end; try try IdTCPClient1.IOHandler.LargeStream := False; IdTCPClient1.IOHandler.Write(StringStream, 0, True); finally IdTCPClient1.Disconnect; end; except TThread.Queue(nil, procedure begin ShowMessage('Send error!'); end); Exit; end; finally StringStream.Free; end; TThread.Queue(nil, procedure begin ShowMessage('BASE64 Image and message successfully sent to server!'); end); except on E: Exception do begin TThread.Queue(nil, procedure begin ShowMessage('Error! ' + E.Message); end); end; end; end); end; procedure TMainForm.IdTCPServer1Execute(AContext: TIdContext); var JSON: TJSONObject; MemoryStream: TMemoryStream; StringStream: TStringStream; // alternatively: // JSONStr: string; Msg: String; begin MemoryStream := nil; try StringStream := TStringStream.Create('', TEncoding.UTF8); try AContext.Connection.IOHandler.LargeStream := False; AContext.Connection.IOHandler.ReadStream(StringStream, -1, False); JSON := TJSONObject.ParseJSONValue(StringStream.DataString) as TJSONObject; finally StringStream.Free; end; // alternatively: // JSONStr := AContext.Connection.IOHandler.ReadString(AContext.Connection.IOHandler.ReadInt32); // JSON := TJSONObject.ParseJSONValue(JSONStr) as TJSONObject; try MemoryStream := TMemoryStream.Create; TIdDecoderMIME.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream); Msg := JSON.GetValue('message').Value; finally Json.Free; end; MemoryStream.Position := 0; TThread.Synchronize(nil, procedure begin Edit2.Text := Msg; Image1.Bitmap.LoadFromStream(MemoryStream); end); finally MemoryStream.Free; end; TThread.Synchronize(nil, SaveImageToDisk); end;
  23. Remy Lebeau

    Clipboard history

    Enumerating Windows clipboard history in C++/WinRT and C# Enumerating Windows clipboard history in PowerShell But, the way Microsoft describes this API, they make it sound like it is "the clipboard" history.
  24. Remy Lebeau

    Unable to save from TImage to file

    SizeOf(StringStream) This is wrong. This is the size of a pointer. How many bytes are you expecting the client to send? What does the client code look like? Ideally, the client should send the JSON's size before sending the JSON's data, and then the server should read the size before the data. IOHandler.Write(TStream) and IOHandler.ReadStream() can handle that for you, but not the way you are currently using IOHandler.ReadStream(). The client can call IOHandler.Write(TStream) with AWriteByteCount=True, and the server can call IOHandler.ReadStream() with AByteCount=-1 and AReadUntilDisconnect=False. That being said, since JSON is textual data, I would suggest using IOHandler.ReadString() instead of IOHandler.ReadStream(). Also, the preferred encoding for JSON is UTF-8 not ASCII. IdDecoderMIME1.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream); ... Image1.Bitmap.LoadFromStream(MemoryStream); You are not rewinding the TMemoryStream back to position 0 before loading it. So the TImage ends up blank before you save it to file.
  25. Remy Lebeau

    Form Activate

    You are not doing anything wrong. By default, Form1 is simply becoming the parent window (in API terms, not VCL terms) for Form2, which is why Form2 can't move behind Form1. You can change this behavior by either: using Form2's PopupMode and PopupParent properties overriding Form2's virtual CreateParams() method to set TCreateParams.WndParent to either 0 or TApplication.Handle turning off TApplication.MainFormOnTaskBar, which has the (side) effect of making the TApplication window be the parent window for all Forms.
×