How can I send data between 2 applications using SendMessage? - delphi

I have 2 applications- Manager with this code:
procedure TForm1.CopyData(var Msg: TWMCopyData);
var sMsg: String;
begin
if IsIconic(Application.Handle) then Application.Restore;
sMsg := PWideChar(Msg.CopyDataStruct.lpData);
Caption := Caption+'#'+sMsg;
Msg.Result := 123;
end;
procedure TForm1.Button1Click(Sender: TObject);
const
WM_MY_MESSAGE = WM_USER + 1;
var
h: HWND;
begin
Caption := 'X';
h := FindWindow('TForm1', 'Client');
if not IsWindow(h) then Exit;
Caption := Caption+'#';
SendMessage(h, WM_MY_MESSAGE, 123, 321);
end;
And Client with:
procedure TForm1.WndProc(var Message: TMessage);
const
WM_MY_MESSAGE = WM_USER + 1;
var DataStruct: CopyDataStruct;
S: String;
h: HWND;
begin
inherited;
if Message.Msg <> WM_MY_MESSAGE then Exit;
h := FindWindow('TForm1', 'Manager');
if not IsWindow(h) then Exit;
Message.Result := 123;
S := Edit2.Text + '#' + Edit1.Text;
DataStruct.dwData := 0;
DataStruct.cbData := 2*Length(S)+1;
DataStruct.lpData := PWideChar(S);
Caption := Caption + '#';
PostMessage(h, WM_CopyData, Form1.handle, integer(#DataStruct));
end;
The code works- but only once.
Manager sends 2 integers: 123 and 321 as a "wake up" message to the Client.
Client responds by sending contents of Edit1 + Edit2.
Then Manager gets this data and shows on its caption.
Why does it work only once? After I click Button1 again it does nothing.

As noted in comments, you must use SendMessage with WM_COPYDATA. The primary reason for this is that the message sender is responsible for cleaning up the resources used for the transfer. As noted in the documentation :
The receiving application should consider the data read-only. The lParam parameter is valid only during the processing of the message. The receiving application should not free the memory referenced by lParam. If the receiving application must access the data after SendMessage returns, it must copy the data into a local buffer.
The only way this can work is if the message sender waits for the receiver to process the message and return a result. Otherwise the sender cannot know when it is safe to release those resources.
PostMessage is asynchronous and returns immediately so this is simply not viable. SendMessage will block until the receiver processes the message and assigns a return value.
Here you are passing a pointer to a stack allocated (local variable) record #DataStruct. Further, you are also passing a pointer to a string which is a local variable. If you use PostMessage, this method will return immediately - the stack locations (for value types like the record) will become invalid and susceptible to being overwritten. The string lives on the heap but is reference counted and, in this case, will be freed when the method returns.
The solution is to always be sure to use SendMessage with WM_COPYDATA.

Related

Why do I get a stack overflow when I call the Windows API from my Delphi program?

My form supports drag'n'drop of files from the Windows Explorer:
uses
ShellApi, System.IOUtils;
procedure TFormMain.FormCreate(Sender: TObject);
begin
DragAcceptFiles(Self.Handle, True);
end;
procedure TFormMain.WMDropFiles(var Msg: TMessage);
var
hDrop: THandle;
FileCount, NameLen, i: Integer;
CurrFile: String;
FileSysEntries: TArray<String>;
begin
inherited;
hDrop := Msg.wParam;
try
FileCount := DragQueryFile(hDrop, $FFFFFFFF, nil, 0);
for i := 0 to FileCount - 1 do
begin
NameLen := DragQueryFile(hDrop, i, nil, 0) + 1; //+1 for NULL
SetLength(CurrFile, NameLen);
DragQueryFile(hDrop, i, PWideChar(CurrFile), NameLen);
//If I don't do this...
SetLength(CurrFile, StrLen(PWideChar(CurrFile)));
if DirectoryExists(CurrFile) then
begin
//...I get a stack overflow here!
FileSysEntries := TDirectory.GetFiles(CurrFile, '*.*', TSearchOption.soAllDirectories);
//Rest removed for clarity...
end;
end;
finally
DragFinish(hDrop);
end;
end;
Now if I don't strip the NULL (#0) character off the CurrFile string (see 2nd SetLength) I get a stack overflow when I call TDirectory.GetFiles and I'm now sure why.
Is the second SetLength (that strips #0) really necessary or should I do NameLen - 1 for the first SetLength? Or maybe something else?
I see a few issues:
you are calling DragAcceptFiles() only in the Form's OnCreate event. If the Form's HWND is ever re-created during the Form's lifetime (it can happen!), you will lose the ability to receive WM_DROPFILES messages.
You would need to call DragAcceptFiles() again with the updated HWND. You can override the Form's virtual CreateWnd() method to handle that.
Alternatively, you can override the Form's virtual CreateParams() method to enable the WS_EX_ACCEPTFILES extended window style for each HWND that is created.
your message handler is calling inherited. You don't need to do that. The default handler will not do anything with the message.
you are over-allocating memory for CurrFile. You technically DO NOT need to include the null terminator when calling SetLength(), as it will automatically allocate extra space for one (a Delphi string is implicitly null-terminated, so that PChar casts can be used with C-style APIs that expect null-terminated character pointers).
If you DO include the null terminator in the string's length, you have to explicitly shrink the strings length afterwards, which you are doing (but not as efficiently as you could be, as DragQueryFile(i) will tell you the length to use without a null terminator, so you don't have to calculate it manually with StrLen()). But, it is better to simply not over-allocate to begin with.
Apparently having that extra #0 in the string's length is causing problems for TDirectory.GetFiles() (or more likely, TPath, which TDirectory uses internally). You should file a bug report about that. But, you do need to make sure you don't leave the terminating #0 in the string's length to begin with, since filesystem path APIs don't accept it anyway.
Try this instead:
uses
ShellApi, System.IOUtils;
procedure TFormMain.CreateWnd;
begin
inherited;
DragAcceptFiles(Self.Handle, True);
end;
procedure TFormMain.WMDropFiles(var Msg: TMessage);
var
hDrop: THandle;
FileCount, NameLen, i: Integer;
CurrFile: String;
FileSysEntries: TArray<String>;
begin
hDrop := Msg.wParam;
try
FileCount := DragQueryFile(hDrop, $FFFFFFFF, nil, 0);
for i := 0 to FileCount - 1 do
begin
NameLen := DragQueryFile(hDrop, i, nil, 0);
SetLength(CurrFile, NameLen);
DragQueryFile(hDrop, i, PChar(CurrFile), NameLen + 1);
if TDirectory.Exists(CurrFile) then
begin
FileSysEntries := TDirectory.GetFiles(CurrFile, '*.*', TSearchOption.soAllDirectories);
//...
end;
end;
finally
DragFinish(hDrop);
end;
end;

How to have TIdTCPServer reply every time it receives a message?

I am learning how to work with HL7 and IdTCPClient and IdTCPServer. The HL7 message is received and I get an acknowledgement reply from the server only for the first message. But after that, messages are received but no acknowledgement reply is sent. It hangs at AContext.Connection.IOHandler.WriteLn. How can you make the IdTCPServer send acknowledgement replies for every message it receives? Your input is highly appreciated. Here is the server side code onExcute:
procedure THL7DM.IdTCPServer1Execute(AContext: TIdContext);
Function AcknowledgementMessage(HL7_msg: string): string;
var
s: TStrings;
MSA: TMSASegment;
MSH: TMSHSegment;
begin
result := '';
MSH := TMSHSegment.Create(HL7_msg); {HL7 MSH Segment}
MSA := TMSASegment.Create(''); {HL7 MSA Segment}
s := TStringList.Create;
try
MSH.Accept_Acknowledgment_Type_15 := 'AA';
MSA.Acknowledgment_Code_18 := 'AA';
MSH.Sending_Facility_4 := 'AEdge Lab';
MSH.Message_Type_9 := 'ACK';
MSA.Message_Waiting_Number_1827 := DateTimeToStr(now);
s.Text := MSH.ToString + #13 + #10 + 'MSA' + '|' + MSA.ToString;
s.Text := #11 + s.Text + #28 + #13;
result := s.Text;
finally
MSA.Free;
MSH.Free;
s.Free;
end;
end;
var
MsgStrings: TStrings;
s: string;
msg: string;
begin
MsgStrings := TStringList.Create;
s := AContext.Connection.IOHandler.ReadLn(IndyTextEncoding_OSDefault());
try
MsgStrings.Text := StrEscapedToString(s);
Form2.Memo3.Text := TRegEx.Replace(MsgStrings.Text, #11 + '|' + #28, '');
msg := AcknowledgementMessage(Form2.Memo3.Text);
if TRegEx.IsMatch(msg, #28#13) = True then
==> AContext.Connection.IOHandler.WriteLn(StrStringToEscaped(msg),
IndyTextEncoding_OSDefault());
if TRegEx.IsMatch(MsgStrings.Text, #11) = True then
SaveMessageToDatabase(MsgStrings);
finally
MsgStrings.Free;
end;
end;
Here is the Client side sending the message:
procedure TForm2.BitBtn1Click(Sender: TObject);
var
LLine: String;
I: Integer;
s: string;
begin
// wrapping for HL7
LLine := #11 + Memo1.Text + #28 + #13;
if Receiving_System_Accepts_Escaped_Strings then
HL7DM.IdTCPClient1.IOHandler.WriteLn(StrStringToEscaped(LLine),
IndyTextEncoding_OSDefault())
else
HL7DM.IdTCPClient1.IOHandler.WriteLn(LLine, IndyTextEncoding_OSDefault());
if Assigned(ACKReplyHandler) = False then
begin
ACKReplyHandler := TACK_MsgHandlingThread.Create;
//This will handle incoming HL7 ACK replies
end;
end;
The TACK_MsgHandlingThread looks like this:
procedure TACK_MsgHandlingThread.Execute;
begin
HandleACK_Replies;
end;
procedure TACK_MsgHandlingThread.HandleACK_Replies;
var
s: string;
begin
s := (HL7DM.IdTCPClient1.IOHandler.ReadLn(IndyTextEncoding_UTF8));
// ShowMessage(s);
s := StrEscapedToString(s);
s := TRegEx.Replace(s, #11, '');
s := TRegEx.Replace(s, #28#13#10, '');
Form2.Memo4.Clear;
Form2.Memo4.Text := (s);
end;
The only way TIdIOHandler.WriteLn() can block is if the receiver is not reading data that has been sent, causing its inbound buffer to fill up and stop the sender from sending more data. This is because your TACK_MsgHandlingThread.Execute() method is reading only 1 incoming reply and then terminating the thread when Execute() exits, so it stops reading subsequent replies. You need to run the logic of HandleACK_Replies() in a loop for the lifetime of the thread, calling TIdIOHandler.ReadLn() for each reply that is sent until the socket is closed and/or the thread is terminated.
Also, IndyTextEncoding_OSDefault is not portable across machine boundaries. But more importantly, you are using IndyTextEncoding_UTF8 on the client side instead. You need to use the same encoding on both sides or else you risk data loss.
Also, your server is accessing Memo3, and your client is accessing Memo4, without syncing with their respective UI threads at all. That is very dangerous. The VCL and FMX frameworks are not thread-safe (most UI frameworks are not), so you MUST synchronize when accessing UI controls from outside of the UI thread.

MAPI in Delphi - working but crashing after mail is send

I have been playing around with some code to send mail using MAPI in my Delphi XE7 program.
I have managed to get the To, Cc and Bcc working together with fileattachment, but when the mail is send by the client (in this case Outlook 2010) the program crashes and I simply can't figure out where I am am doing something wrong - I might be staring blind on the code.
I have made a small example with the code in a file in my dropbox
dl.dropboxusercontent.com/u/65392149/Mapi_MCVE.ZIP
The MAPI function looks like this
function SendMailMAPI(const aFrom, aTo, aCc, aBcc, aSubject, aBody: string; aMailFiles: TStringList; aReceipt: boolean): boolean;
var
MapiStatus: DWord;
MapiMessage: TMapiMessage;
MapiOrigin: TMapiRecipDesc;
MapiRecipient: array of TMapiRecipDesc;
MapiFiles: PMapiFileDesc;
RecipientsTo: TStringlist;
RecipientsCc: TStringlist;
RecipientsBcc: TStringlist;
RecipientsCount: integer;
FilesCount: Integer;
i: integer;
Filename: string;
begin
MapiStatus := SUCCESS_SUCCESS;
Result := True;
MapiFiles := nil;
FillChar(MapiMessage, Sizeof(TMapiMessage), 0);
if aReceipt then
MapiMessage.flFlags := MAPI_RECEIPT_REQUESTED;
MapiMessage.lpszSubject := PAnsiChar(AnsiString(aSubject));
MapiMessage.lpszNoteText := PAnsiChar(AnsiString(aBody));
FillChar(MapiOrigin, Sizeof(TMapiRecipDesc), 0);
MapiOrigin.lpszName := PAnsiChar(AnsiString(aFrom));
MapiOrigin.lpszAddress := PAnsiChar(AnsiString(aFrom));
MapiMessage.lpOriginator := nil;
FilesCount := aMailFiles.Count;
if FilesCount > 0 then
begin
GetMem(MapiFiles, SizeOf(TMapiFileDesc) * FilesCount);
for i := 0 to FilesCount - 1 do
begin
FileName := aMailfiles[i];
MapiFiles[i].ulReserved := 0;
MapiFiles[i].flFlags := 0;
MapiFiles[i].nPosition := ULONG($FFFFFFFF);
MapiFiles[i].lpszPathName := PAnsiChar(AnsiString(FileName));
MapiFiles[i].lpszFileName := PAnsiChar(AnsiString(ExtractFileName(FileName)));
MapiFiles[i].lpFileType := nil;
end;
MapiMessage.nFileCount := FilesCount;
MapiMessage.lpFiles := #MapiFiles^;
end;
RecipientsCount := 0;
RecipientsTo := TStringlist.Create;
RecipientsCc := TStringlist.Create;
RecipientsBcc := TStringlist.Create;
RecipientsTo.Delimiter := ';';
RecipientsCc.Delimiter := ';';
RecipientsBcc.Delimiter := ';';
try
if aTo <> '' then
begin
RecipientsTo.DelimitedText := aTo;
RecipientsCount := RecipientsCount + RecipientsTo.Count;
end;
if aCc <> '' then
begin
RecipientsCc.DelimitedText := aCc;
RecipientsCount := RecipientsCount + RecipientsCc.Count;
end;
if aBcc <> '' then
begin
RecipientsBcc.DelimitedText := aBcc;
RecipientsCount := RecipientsCount + RecipientsBcc.Count;
end;
FillChar(MapiRecipient, Sizeof(TMapiRecipDesc) * RecipientsCount, 0);
SetLength(MapiRecipient, RecipientsCount);
RecipientsCount := 0;
if RecipientsTo.Count > 0 then
begin
MapiRecipient[RecipientsCount].ulRecipClass := MAPI_TO;
for i := 0 to RecipientsTo.Count - 1 do
begin
MapiRecipient[RecipientsCount].lpszName := PAnsiChar(AnsiString(RecipientsTo[i]));
MapiRecipient[RecipientsCount].lpszAddress := PAnsiChar(AnsiString(RecipientsTo[i]));
Inc(RecipientsCount);
end;
end;
if RecipientsCc.Count > 0 then
begin
MapiRecipient[RecipientsCount].ulRecipClass := MAPI_CC;
for i := 0 to RecipientsCc.Count - 1 do
begin
MapiRecipient[RecipientsCount].lpszName := PAnsiChar(AnsiString(RecipientsCc[i]));
MapiRecipient[RecipientsCount].lpszAddress := PAnsiChar(AnsiString(RecipientsCc[i]));
Inc(RecipientsCount);
end;
end;
if RecipientsBcc.Count > 0 then
begin
MapiRecipient[RecipientsCount].ulRecipClass := MAPI_BCC;
for i := 0 to RecipientsBcc.Count - 1 do
begin
MapiRecipient[RecipientsCount].lpszName := PAnsiChar(AnsiString(RecipientsBcc[i]));
MapiRecipient[RecipientsCount].lpszAddress := PAnsiChar(AnsiString(RecipientsBcc[i]));
Inc(RecipientsCount);
end;
end;
MapiMessage.nRecipCount := RecipientsCount;
MapiMessage.lpRecips:= Pointer(MapiRecipient);
finally
RecipientsTo.Free;
RecipientsCc.Free;
RecipientsBcc.Free;
end;
try
MapiStatus := MapiSendMail(0, Application.MainForm.Handle, MapiMessage, MAPI_LOGON_UI + MAPI_DIALOG, 0);
except
on E:Exception do
ShowMessage('U_Mailing.Mapi.SendMailMAPI: ' + E.Message);
end;
for i := 0 to FilesCount - 1 do
begin
System.AnsiStrings.StrDispose(MapiFiles[i].lpszPathName);
System.AnsiStrings.StrDispose(MapiFiles[i].lpszFileName);
end;
for i := 0 to RecipientsCount - 1 do
begin
System.AnsiStrings.StrDispose(MapiRecipient[i].lpszName);
System.AnsiStrings.StrDispose(MapiRecipient[i].lpszAddress);
end;
case MapiStatus of
MAPI_E_AMBIGUOUS_RECIPIENT:
Showmessage('A recipient matched more than one of the recipient descriptor structures and MAPI_DIALOG was not set. No message was sent.');
MAPI_E_ATTACHMENT_NOT_FOUND:
Showmessage('The specified attachment was not found; no message was sent.');
MAPI_E_ATTACHMENT_OPEN_FAILURE:
Showmessage('The specified attachment could not be opened; no message was sent.');
MAPI_E_BAD_RECIPTYPE:
Showmessage('The type of a recipient was not MAPI_TO, MAPI_CC, or MAPI_BCC. No message was sent.');
MAPI_E_FAILURE:
Showmessage('One or more unspecified errors occurred; no message was sent.');
MAPI_E_INSUFFICIENT_MEMORY:
Showmessage('There was insufficient memory to proceed. No message was sent.');
MAPI_E_LOGIN_FAILURE:
Showmessage('There was no default logon, and the user failed to log on successfully when the logon dialog box was displayed. No message was sent.');
MAPI_E_TEXT_TOO_LARGE:
Showmessage('The text in the message was too large to sent; the message was not sent.');
MAPI_E_TOO_MANY_FILES:
Showmessage('There were too many file attachments; no message was sent.');
MAPI_E_TOO_MANY_RECIPIENTS:
Showmessage('There were too many recipients; no message was sent.');
MAPI_E_UNKNOWN_RECIPIENT:
Showmessage('A recipient did not appear in the address list; no message was sent.');
MAPI_E_USER_ABORT:
Showmessage('The user canceled the process; no message was sent.');
else
Showmessage('MAPISendMail failed with an unknown error code.');
Result := False;
end;
end;
I can see two obvious mistakes. Firstly you use a lot of implicit temporary AnsiString variables whose lifetime is unclear. This happens whenever you do
PAnsiChar(AnsiString(...))
The compiler makes a temporary AnsiString local variable. You are relying on the compiler making enough of them, one for each distinct string.
When you do this inside a loop, the compiler will have one implicit local variable shared between all iterations. That's not enough. You might get away with this because the memory manager doesn't happen to re-use the memory. But your code is still wrong.
I would create a TList<AnsiString> to hold the numerous AnsiString variables. Every time you need to get a PAnsiChar do this:
astr := AnsiString(...);
TempAnsiStrings.Add(astr);
... := PAnsiChar(astr);
where TempAnsiStrings is your list that holds temporary AnsiString objects. Wrap this up in a nested function for ease of use.
function GetPAnsiChar(const str: string): PAnsiChar;
var
astr: AnsiString;
begin
astr := AnsiString(str);
TempAnsiStrings.Add(astr);
Result := PAnsiChar(astr);
end;
Obviously you need to instantiate the list, and destroy it, but I trust you already know how to do that.
Your other problem are the calls to StrDispose. You have to remove them since the compiler is managing lifetime. Call StrDispose to free memory allocated with StrNew or StrAlloc. You don't allocate this way so there is no place for StrDispose.
Rather than using explicit memory allocation for list of files, a dynamic array would be cleaner. This would have the other benefit of avoiding the possibility of leaking that your code currently has due to its missing finally block.

Get Fax Job Status

I have tried the code below for sending fax:
uses
ComObj, ActiveX, FAXCOMEXLib_TLB;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
JobIDs: OleVariant;
FaxServer: IFaxServer2;
FaxDocument: IFaxDocument2;
begin
try
FaxServer := CoFaxServer.Create;
FaxServer.Connect('');
FaxDocument := CoFaxDocument.Create;
FaxDocument.Body := 'd:\Document.pdf';
FaxDocument.DocumentName := 'Document name';
FaxDocument.Recipients.Add('+1 (425) 555-4567', 'Bill');
FaxDocument.Sender.Name := 'Bob';
FaxDocument.Sender.BillingCode := '23A54';
FaxDocument.Sender.Department := 'Accts Payable';
FaxDocument.Sender.FaxNumber := '+972 (4) 555-9070';
JobIDs := FaxDocument.ConnectedSubmit(FaxServer);
for I := VarArrayLowBound(JobIDs, 1) to VarArrayHighBound(JobIDs, 1) do
ShowMessage('Job ID: ' + VarArrayGet(JobIDs, [I]));
except
on E: EOleSysError do
ShowMessage(
Format('Sending of the fax failed! %s [%d]', [E.Message, E.ErrorCode])
);
end;
end;
What I was trying to do was get the job status for the fax sent. I have tried to add
var
FaxJobStatus: IFaxJobStatus;
.....
FaxJobStatus := CoFaxJobStatus.Create;
compiled the source code and found no error but after executing the code, it fails at
FaxJobStatus := CoFaxJobStatus.Create
saying "class not registered".
From the IFaxJobStatus documentation:
You do not create the FaxJobStatus object. It is received as part of a notification when you implement IFaxServerNotify::OnIncomingJobChanged or IFaxServerNotify::OnOutgoingJobChanged, which include a parameter of the type FaxJobStatus. When the event occurs and the implemented function is called, you receive this object containing the dynamic information.
So you have to register for the IFaxServerNotify.OnIncomingJobChanged or IFaxServerNotify.OnOutgoingJobChanged events. When the event is received, you get the FaxJobStatus object and can read its Status property.

Using SendData results in a mangled string when received

I'm trying to send a string between two Delphi forms using code adapted from here: http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm.
The string that is displayed by the receiver is partially garbage. I suspect this is because of Unicode I issues when Delphi 2010 is communicating with the Windows API.
I want to be able to handle Unicode if possible.
I have been unable to figure out where in the code below a cast is wrong. Any help?
Sending form:
procedure TForm1.gridDetailsDblClick(Sender: TObject);
var
StringToSend : String;
CopyDataStruct : TCopyDataStruct;
begin
StringToSend := StringGrid1.Cells[0, StringGrid1.Row];
CopyDataStruct.dwData := 0;
CopyDataStruct.cbData := 1 + Length(StringToSend) ;
CopyDataStruct.lpData := PChar(StringToSend) ;
SendDataToAppearanceForm(copyDataStruct) ;
end;
procedure TForm1.SendDataToAppearanceForm(const CopyDataStruct: TCopyDataStruct) ;
var
ReceiverHandle : THandle;
begin
ReceiverHandle := FindWindow(PChar('TForm2'), nil);
if (ReceiverHandle <> 0) then
SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle), Integer(#CopyDataStruct)) ;
end;
Receiving form: (Which results in the edit box containing a part of the string, but then garbage.)
procedure TForm2.WMCopyData(var Msg: TWMCopyData);
var
S: String;
begin
edText.Text := PChar(Msg.CopyDataStruct.lpData);
end; { WMCopyData }
Your problem is that you are setting cbData incorrectly. This is the number of bytes and not the number of characters.
The +1 is needed since your receiver is interpreting it as a null-terminated string. Therefore your code should read:
(1 + Length(StringToSend))*SizeOf(Char)
Alternatively you could, at the receiving end, make use of SetString() and cbdata to avoid the need for the +1.
I just tried
procedure TForm1.Button1Click(Sender: TObject); // Project1.exe
var
CDS: TCopyDataStruct;
begin
CDS.dwData := 0;
CDS.cbData := (length(Edit1.Text) + 1) * sizeof(char);
CDS.lpData := PChar(Edit1.Text);
SendMessage(FindWindow(nil, 'RecForm'),
WM_COPYDATA, Integer(Handle), Integer(#CDS));
end;
procedure TForm1.WndProc(var Message: TMessage); // Project2.exe
begin
inherited;
case Message.Msg of
WM_COPYDATA:
begin
Edit1.Text := PChar(TWMCopyData(Message).CopyDataStruct.lpData);
Message.Result := Integer(True);
end;
end;
end;
to copy and it works. The difference between this code and yours is that, since one Unicode character is two bytes long, the cbData member needs to be the number of characters in the string times two, that is, times sizeof(char). In addition, you need to add a whole character so that the null terminator is sent along with the string! Otherwise the receiver will not know when the string ends!

Resources