Discussion:
Access Violation on TMemo.Items.Add after DLL call
(too old to reply)
Paul E. Schoen
2009-11-29 09:27:57 UTC
Permalink
I have been working on an application which sends and receives information
directly via a USB connection, using a generic interface (rather than the
CDC, which emulates a COM port). The USB device enumerates properly and I
can communicate with it using a DLL which is provided by Microchip. I found
some demo code which requested information via a DLL call and displayed it
in a MessageBox. But when I instead tried to add the same string to a TMemo
I got an AV, and when I used the debugger the Memo1 component was not
accessible. When I clicked another button which performed other functions,
the Memo updated normally with no AV and the debugger showed the component
normally. Here are some code fragments:

procedure TForm1.btInfoClick(Sender: TObject);
var S: String;
begin
send_buf[0]:= READ_VERSION;
send_buf[1]:= 4; // Expected length of the result

Memo1.Lines.Add('Hello from Info'); // This is OK
RecvLength:=4;
if(SendReceivePacket(send_buf,1,receive_buf,RecvLength,100,100) = 1) then
begin
S := IntToStr(receive_buf[0])+IntToStr(receive_buf[1]);
ShowMessage(S); // This is OK
if(receive_buf[0] = READ_VERSION) then begin
ShowMessage('Firmware version : '+ IntToStr(receive_buf[3])+'.'+
IntToStr(receive_buf[2])); end;
Memo1.lines.add(S); end // This causes an AV
else begin
ShowMessage('USB Error'); end;
Memo1.Lines.Add('Goodbye from Info'); // This causes an AV
end;

procedure TForm1.btConnectClick(Sender: TObject);
var
i: Integer;
begin // Read Firmware version
selection:=_MPUSBGetDLLVersion;
Memo1.lines.add(Format('%0.8x',[selection])); // This is OK
selection := 0;

for i := 0 to length(ComboBox1.Text) do
vid_pid[i] := ComboBox1.Text[i+1];

if (_MPUSBGetDeviceCount(vid_pid)=0) then begin // This is OK
Memo1.lines.add(Format('- Device %s not connected', [vid_pid]) );
exit;
end
else
Memo1.lines.add(Format('Device %s connected!', [vid_pid]) );
end;

The prototype for the function in the DLL is:

function _MPUSBRead(handle:THANDLE;var pData:PBYTE;
dwLen:DWORD;var pLength:DWORD;
dwMilliseconds:DWORD):DWORD;stdcall;
external 'mpusbapi.dll';

I have the source code for the DLL in Borland C++ and I didn't see anything
suspicious. But sometimes the AV seems to occur in a thread and not in the
Delphi code. The DLL uses asynchronous IO with a ReadFile(),
WaitForSingleObject and GetOverlappedResult, with a 100 mSec timeout as set
in the SendReceivePacket call above.

I even added a second Memo and an Edit box and it has the same problem. It
seems that once the DLL executes, the components in the form go out of
scope and do not return until the btInfoClick procedure exits.

I was able to do this:

const fm_UpdateMemo = wm_User + 1; //message constant

message fm_UpdateMemo;

In the btInfoClick handler:

PostMessage(Form1.Handle, fm_UpdateMemo, 0, LParam(0));

which called fmUpdateMemo which has the line

procedure TForm1.fmUpdateMemo(var Message: TMessage);
begin
Memo1.Lines.Add('Goodbye from Info');
end;

If I used btInfo as the LParam I got an AV.

Any ideas? Thanks!

Paul
a***@aol.com
2009-11-29 14:53:02 UTC
Permalink
Paul

Are you using the correct calling conventions ? A quick google search
showed up . . .

http://stackoverflow.com/questions/954844/how-to-call-microchip-pic-usb-dll-with-delphi-2009

"How to call Microchip PIC USB DLL with Delphi 2009"

. . .which AFAICS has the function as . . .

_MPUSBRead : function ( handle : THANDLE;
pData : pointer;
dwLen : DWORD;
var pLength : DWORD;
dwMilliseconds : DWORD ) : DWORD; cdecl;

ie cdecl not stdcall.

Alan Lloyd
Paul E. Schoen
2009-11-29 18:21:05 UTC
Permalink
Post by a***@aol.com
Paul
Are you using the correct calling conventions ? A quick google search
showed up . . .
http://stackoverflow.com/questions/954844/how-to-call-microchip-pic-usb-dll-with-delphi-2009
"How to call Microchip PIC USB DLL with Delphi 2009"
. . .which AFAICS has the function as . . .
_MPUSBRead : function ( handle : THANDLE;
pData : pointer;
dwLen : DWORD;
var pLength : DWORD;
dwMilliseconds : DWORD ) : DWORD; cdecl;
ie cdecl not stdcall.
Yup! I changed the functions to cdecl and the AVs went away. I got the
original code from a website I found with a Dogpile search. It had only the
showmessage() calls and did not give AVs until I added the Memo.

I suspected something like that when I sat back and looked at the evidence.
I have not checked out the link yet but your advice was spot on. I'll add
that to my thread on the Microchip forum where I have an active discussion
that explains why I am trying to use generic USB methods for communication
rather than the CDC functions.

Thanks!

Paul
Jamie
2009-11-29 18:39:22 UTC
Permalink
Post by Paul E. Schoen
I have been working on an application which sends and receives information
directly via a USB connection, using a generic interface (rather than the
CDC, which emulates a COM port). The USB device enumerates properly and I
can communicate with it using a DLL which is provided by Microchip. I found
some demo code which requested information via a DLL call and displayed it
in a MessageBox. But when I instead tried to add the same string to a TMemo
I got an AV, and when I used the debugger the Memo1 component was not
accessible. When I clicked another button which performed other functions,
the Memo updated normally with no AV and the debugger showed the component
procedure TForm1.btInfoClick(Sender: TObject);
var S: String;
begin
send_buf[0]:= READ_VERSION;
send_buf[1]:= 4; // Expected length of the result
Memo1.Lines.Add('Hello from Info'); // This is OK
RecvLength:=4;
if(SendReceivePacket(send_buf,1,receive_buf,RecvLength,100,100) = 1) then
begin
S := IntToStr(receive_buf[0])+IntToStr(receive_buf[1]);
ShowMessage(S); // This is OK
if(receive_buf[0] = READ_VERSION) then begin
ShowMessage('Firmware version : '+ IntToStr(receive_buf[3])+'.'+
IntToStr(receive_buf[2])); end;
Memo1.lines.add(S); end // This causes an AV
else begin
ShowMessage('USB Error'); end;
Memo1.Lines.Add('Goodbye from Info'); // This causes an AV
end;
procedure TForm1.btConnectClick(Sender: TObject);
var
i: Integer;
begin // Read Firmware version
selection:=_MPUSBGetDLLVersion;
Memo1.lines.add(Format('%0.8x',[selection])); // This is OK
selection := 0;
for i := 0 to length(ComboBox1.Text) do
vid_pid[i] := ComboBox1.Text[i+1];
if (_MPUSBGetDeviceCount(vid_pid)=0) then begin // This is OK
Memo1.lines.add(Format('- Device %s not connected', [vid_pid]) );
exit;
end
else
Memo1.lines.add(Format('Device %s connected!', [vid_pid]) );
end;
function _MPUSBRead(handle:THANDLE;var pData:PBYTE;
dwLen:DWORD;var pLength:DWORD;
dwMilliseconds:DWORD):DWORD;stdcall;
external 'mpusbapi.dll';
I have the source code for the DLL in Borland C++ and I didn't see anything
suspicious. But sometimes the AV seems to occur in a thread and not in the
Delphi code. The DLL uses asynchronous IO with a ReadFile(),
WaitForSingleObject and GetOverlappedResult, with a 100 mSec timeout as set
in the SendReceivePacket call above.
I even added a second Memo and an Edit box and it has the same problem. It
seems that once the DLL executes, the components in the form go out of
scope and do not return until the btInfoClick procedure exits.
const fm_UpdateMemo = wm_User + 1; //message constant
message fm_UpdateMemo;
PostMessage(Form1.Handle, fm_UpdateMemo, 0, LParam(0));
which called fmUpdateMemo which has the line
procedure TForm1.fmUpdateMemo(var Message: TMessage);
begin
Memo1.Lines.Add('Goodbye from Info');
end;
If I used btInfo as the LParam I got an AV.
Any ideas? Thanks!
Paul
If you're multithreading and not using a blocking mechanism
to prevent other code from entering, until finished, you'll
get all sorts of issues.
In your example here, the problem appears to happen after you
have inserted a string into the Tmemo, which may be trigging
some other part of your app in a secondary thread!

If this be the case, you need to employ blocking practices
to avoid collision, because the VCL isn't multithread safe.

In the secondary threads, try using the "synchronize" process
to call VCL code.

Try not inserting anything into the Tmemo at the start of this
test. See if the error propagates down further. If so, then this
proves my theory..
Paul E. Schoen
2009-11-30 17:11:51 UTC
Permalink
Post by Jamie
If you're multithreading and not using a blocking mechanism
to prevent other code from entering, until finished, you'll
get all sorts of issues.
In your example here, the problem appears to happen after you
have inserted a string into the Tmemo, which may be trigging
some other part of your app in a secondary thread!
If this be the case, you need to employ blocking practices
to avoid collision, because the VCL isn't multithread safe.
In the secondary threads, try using the "synchronize" process
to call VCL code.
Try not inserting anything into the Tmemo at the start of this
test. See if the error propagates down further. If so, then this
proves my theory..
This problem was due to having the USB functions as stdcall rather than
cdecl as Alan Lloyd pointed out. Using the wrong calling convention most
likely messed up the stack and trashed the pointers to the components such
as the TMemo and TEdit. Fortunately things were restored when the procedure
exited. It is interesting that a call to ShowMessage worked OK, but that
created a new instance of a form so everything in its scope would be OK.
And the message, being sent to the main form, was not affected by the stray
pointers. I don't know exactly what was going on, but at least I know what
was wrong and now it is fixed and I can progress to other details of the
project. For more details see the thread:
http://www.microchip.com/forums/tm.aspx?m=461904

Thanks,

Paul

Loading...