![]() |
Datasnap File Upload
Hallo,
ich sitze in der Arbeit an einer Client/Server Anwendung die wir mit Datasnap realisieren. Sonst lief auch alles, nur seit heute bekomme ich beim Upload einer Datei eine Exception. Über einen TOpenDialog wählt man eine Datei. Die wird mit Pfad in eine Listbox eingetragen (lbFiles). Hier der Client:
Delphi-Quellcode:
aFileStream: TFileStream;
Delphi-Quellcode:
Die Exception:
for i := 0 to lbFiles.Items.Count - 1 do
begin aFileStream := TFileStream.Create(lbFiles.Items[i], fmOpenRead); if aClient.UploadFile(aFileStream, Store, ExtractFileName(lbFiles.Items[i])) then // Hier exception! begin Konsole.TextHinweis(ExtractFileName(lbFiles.Items[i]) + ' gesendet!'); end; end; Exception-Klasse TDBXError mit Meldung 'Remote-Fehler: Ungültiger Dateiname - %s'. Der Server:
Delphi-Quellcode:
Ich verstehe nicht wo der Fehler liegt, weil gestern noch alles lief...
const
BufferSize = 1024; var aFileStream: TFileStream; Buffer: TBytes; BytesReadCount: Integer; Success: boolean; begin Success := false; if not DirectoryExists(ExtractFilePath(ParamStr(0)) + 'Files\') then begin MkDir(ExtractFilePath(ParamStr(0)) + 'Files\' + Store); end; try if FileExists(Name) then begin DeleteFile(Name); end; aFileStream := TFileStream.Create(ExtractFilePath(ParamStr(0)) + 'Files\' + Name, fmCreate); SetLength(Buffer, BufferSize); repeat BytesReadCount := str.Read(Buffer[0], BufferSize); if (BytesReadCount > 0) then aFileStream.Write(Buffer[0], BytesReadCount); until (BytesReadCount < BufferSize); str.Position := 0; Success := True; finally aFileStream.Free; SetLength(Buffer, 0); end; Result := Success; end; Hat wer eine Idee? |
AW: Datasnap File Upload
In der Listbox steht übrigens der richtige Pfad mit der richtigen Datei. Store und Name sind nur für den Server und soweit ich das sehe auch nicht das Problem.
|
AW: Datasnap File Upload
Einzig ungewöhlich finde ich den Parameter "Name"
|
AW: Datasnap File Upload
Jupp, wo "Name" doch ein beliebtes property in allen VCL-Komponenten ist.
Zitat:
Mit Pfad, ohne Pfad, Pfad mit Store oder Pfad ohne Store?
Delphi-Quellcode:
PS: Daß die DP Name fett darstellt, sollte dir schon zu Denken geben. :angle2:
ExtractFilePath(ParamStr(0)) + 'Files\' + Store
Name ExtractFilePath(ParamStr(0)) + 'Files\' + Name |
AW: Datasnap File Upload
Ja stimmt mit Name und habe ich (VNC sei dank) auch gleich geändert aber der Fehler kommt nach wie vor am Client.
Ist Store '00000' dann wird die Datei im Ordner Files gespeichert, ist sie anders (z.B. 52125) dann wird der Ordner 52125 in Files angelegt und die Datei dort gespeichert. Der Server liefert auch kein False zurück, bis dahin kommt er garnicht. ...hab es jetzt gefunden... der Fehler war das ich auf Files geprüft habe aber dann immer Files\00000 anlegen wollte. nenene Ne das war es doch nicht... |
AW: Datasnap File Upload
Hier mal die Upload Methode
Delphi-Quellcode:
Vielleicht hilft es ja :cry:
function TServerMethodsClient.UploadFile(str: TStream; Store: string; fName: string): Boolean;
begin if FUploadFileCommand = nil then begin FUploadFileCommand := FDBXConnection.CreateCommand; FUploadFileCommand.CommandType := TDBXCommandTypes.DSServerMethod; FUploadFileCommand.Text := 'TServerMethods.UploadFile'; FUploadFileCommand.Prepare; end; FUploadFileCommand.Parameters[0].Value.SetStream(str, FInstanceOwner); FUploadFileCommand.Parameters[1].Value.SetWideString(Store); FUploadFileCommand.Parameters[2].Value.SetWideString(fName); FUploadFileCommand.ExecuteUpdate; // Genau hier gibt es dann die Exception Result := FUploadFileCommand.Parameters[3].Value.GetBoolean; end; |
AW: Datasnap File Upload
Zitat:
|
AW: Datasnap File Upload
Zitat:
|
AW: Datasnap File Upload
Jo, dann hab ich das wohl falsch interpretiert, ich hab auch ehrlich gesagt keinen Plan von DataSnap.
|
AW: Datasnap File Upload
Delphi-Quellcode:
Beschreibung:
var
aFileStream: TFileStream; Buffer: array[1..16*1024] of Byte; // 16 KB BytesReadCount: Integer; begin if not DirectoryExists(ExtractFilePath(ParamStr(0)) + 'Files') then MkDir(ExtractFilePath(ParamStr(0)) + 'Files\' + Store); aFileStream := TFileStream.Create(ExtractFilePath(ParamStr(0)) + 'Files\' + FileName, fmCreate); try repeat BytesReadCount := str.Read(Buffer, SizeOf(Buffer)); aFileStream.Write(Buffer, BytesReadCount); until BytesReadCount < SizeOf(Buffer); finally aFileStream.Free; end; end;
PS: Sollte die Datei im Store-Pfad liegen, oder hab ich das falsch verstanden? Wenn ja, dann muß "Store" natürlich noch mit in den Pfad des TFileStream.Create aufgenommen werden. Zitat:
|
AW: Datasnap File Upload
Danke für deine Hilfe zu so später Stunde. :)
Ich habe deine Änderungen jetzt umgesetzt und mit Server+Client auf einem Rechner funktioniert es auch, aber sobald ich den Server extern öffne und dann übertragen will, gibt es wieder die gleiche Exception. :( |
AW: Datasnap File Upload
Soweit ich bisher feststellen kann, wird der richtige Dateiname übertragen, aber die Dateien sind immer nur 1024 byte groß.
Die Exception kommt natürlich auch :) |
AW: Datasnap File Upload
Das ist bisher der Server:
Delphi-Quellcode:
Und der Client:
procedure TServerMethods.UploadFile(str: TStream; Store, FileName: string);
var aFileStream: TFileStream; BytesReadCount: Integer; Buffer: array [1 .. 16 * 1024] of Byte; begin if not DirectoryExists(ExtractFilePath(ParamStr(0)) + 'Files\' + Store) then begin MkDir(ExtractFilePath(ParamStr(0)) + 'Files\' + Store); end; aFileStream := TFileStream.Create(ExtractFilePath(ParamStr(0)) + 'Files\' + Store + '\' + FileName, fmCreate); try repeat BytesReadCount := str.Read(Buffer, BufferSize); aFileStream.Write(Buffer, BytesReadCount); until (BytesReadCount < SizeOf(Buffer)); str.Position := 0; finally aFileStream.Free; end; end;
Delphi-Quellcode:
procedure TfrmMain.btnSendClick(Sender: TObject);
var aFileStream: TFileStream; i: Integer; begin if lbFiles.Count > 0 then begin try for i := 0 to lbFiles.Items.Count - 1 do begin aFileStream := TFileStream.Create(lbFiles.Items[i], fmOpenRead); try begin aClient.UploadFile(aFileStream, Store, ExtractFileName(lbFiles.Items[i])); lbFiles.Clear; end except begin ShowMessage('Übertragung fehlerhaft!'); end; end; end; finally aFileStream.Free; btnSend.Enabled := False; end; end else begin Konsole.TextHinweis('Keine Dateien zum Senden vorhanden.'); end; end; |
AW: Datasnap File Upload
Grad gesehen das ich ein
Delphi-Quellcode:
vergessen hatte. Nun wird die komplette Datei übertragen und eine Exception gibt es auch nicht mehr.
SizeOf(Buffer)
Vielen Dank für die späte Hilfe! :thumb: |
AW: Datasnap File Upload
Hallo,
ich habe die Upload-Routine aus dem obigen Beispiel auf Delphi 2009 bzw. Delphi XE2 ausprobiert, allerdings bekomme ich beim zweiten Aufruf der UploadFile-Methode im Client eine Exception (bzw. auch bei der Freigabe des DataSnap-Datenmoduls auf dem Client). Ich habe dann mal die Freigabe des aFileStream im Client auskommentiert, damit funktioniert es dann. Komischerweise scheint es aber auch keine Speicherlecks (auf dem Client) zu geben, jedenfalls liefert FastMM keine. Irgendwo scheint der Stream also dann doch freigegeben zu werden. Hier meine Upload-Routine, die mehrfach aufgerufen wird:
Delphi-Quellcode:
Hat einer von euch eine Idee, warum das so ist?procedure UploadFile(PfadName, FileName: String); var lFileStream: TFileStream; begin if (FileName <> '') and FileExists(PfadName + FileName) then begin lFileStream := TFileStream.Create(PfadName + FileName, fmOpenRead); try try DataModule.ServerMethods1Client.UploadFile(lFileStream, 'KundeXY', FileName); except ShowMessage('Übertragung fehlerhaft!'); end; finally // lFileStream.Free; end; end; end; Es funktioniert zwar grundsätzlich so, aber es bleibt ein ungutes Gefühl, wenn ein lokales Objekt erzeugt wird aber dann nicht mehr (explizit) freigegeben wird. |
AW: Datasnap File Upload
Zitat:
irgendwo = Wenn man sagt der Owner soll es machen, dann macht Er (DataSnap) das auch. Tipp:
Delphi-Quellcode:
PS: Bei Streams mit (standardmäßig) über 29 KB muß man etwas aufpassen.
procedure UploadFile(PfadName, FileName: String);
var lFileStream: TFileStream; begin if (FileName <> '') and FileExists(PfadName + FileName) then begin lFileStream := TFileStream.Create(PfadName + FileName, fmOpenRead); try try DataModule.ServerMethods1Client.UploadFile(lFileStream, 'KundeXY', FileName); except ShowMessage('Übertragung fehlerhaft!'); end; finally if not DataModule.InstanceOwner then // DataSnap ist hier einfach nur blöd implementiert, also besser so lFileStream.Free; end; end; end; PPS: Eine bessere treffendere Namensgebung wäre auch nicht unbedingt zu verachten. (z.B. alternativ zu den Vorgabewerten DataModule und ServerMethods1Client)
Delphi-Quellcode:
... Und was lief schief? ... das weiß nun keiner mehr.
ShowMessage('Übertragung fehlerhaft!');
stattdessen eventuell sowas:
Delphi-Quellcode:
except
on E: Exception do ShowMessage('Übertragung fehlerhaft!' + sLineBreak + E.Message); end; |
AW: Datasnap File Upload
Die Exception-Meldung war "Im Projekt Testprog.exe ist ein Exception der Klasse EExternalException mit der Meldung 'Externe Exception C0000008' aufgetreten".
Die Servermethode ist im Wesentlichen so implementiert wie in dem Beispiel von BeBored oben, aber der Vollständigkeit halber hier nochmal:
Delphi-Quellcode:
Der Tipp mit der Abfrage nach dem InstanceOwner war sehr hilfreich, sie liefert hier True. Allerdings ist mir nicht klar, warum der InstanceOwner des Streams hier das DataModule ist. Kann man das irgendwo einstellen?
procedure TServerMethods1.UploadFile(str: TStream; Store, FileName: String);
var aFileStream: TFileStream; BytesReadCount: Integer; Buffer: array [1 .. 16 * 1024] of Byte; strPfad: String; begin strPfad := CPfad + '\' + Store; if not DirectoryExists(strPfad) then begin MkDir(strPfad); end; aFileStream := TFileStream.Create(strPfad + '\' + FileName, fmCreate); try repeat BytesReadCount := str.Read(Buffer, SizeOf(Buffer)); aFileStream.Write(Buffer, BytesReadCount); until (BytesReadCount < SizeOf(Buffer)); str.Position := 0; finally aFileStream.Free; end; end; Mit der besseren Namensgebung hast du natürlich recht, aber es ging mir hier erst mal um einen einfachen Test und die korrekte Funktion. |
AW: Datasnap File Upload
Zitat:
Zitat:
(das ist die globale Einstellung für sowas) Dieser Wert wird vor den automatisch generierten Clientklassen abgefragt und jeweils für deren Objekt-Parameter verwendet. |
AW: Datasnap File Upload
Danke für deine Hilfe, ich habe die entsprechenden Stellen jetzt gefunden.
|
AW: Datasnap File Upload
Hallo,
ich habe mir eure Funktion oben mal zu nutze gemacht um ein Bild zu übertragen. Klappt auch im Großen und Ganzen wunderbar. Nur habe ich an einer Stelle einen Fehler, den ich mir nicht erklären kann. Hier erst mal mein Code im Client:
Delphi-Quellcode:
(Jetzt bitte mal nicht auf die Try...expept-Blöcke achten ;-) )
procedure TForm2.Button2Click(Sender: TObject);
var aClient: TServerMethods1Client; aFileStream: TFileStream; i: Integer; begin aClient := TServerMethods1Client.Create(SQLCOnnection1.DBXConnection); Try aFileStream := TFileStream.Create(JvFilenameEdit1.Text, fmOpenRead); try aClient.UploadFile(aFileStream, 'Upload', ExtractFileName(JvFilenameEdit1.Text)); except ShowMessage('Übertragung fehlerhaft!'); end; Finally aFileStream.Free; aClient.Free; End; end; Wenn ich jetzt was übertragen will, dann tritt beim aClient.Free eine Exception auf: "Im Projekt DSClient.exe ist eine Exception der Klasse $C0000008 mit der Meldung 'system exception (code 0xc0000008) at 0x77b712f7' aufgetreten." Ich war eigentlich immer der Meinung, dass man selbst erzeugte Objekte auch selber wieder freigeben sollte. Aber möglicherweise besteht ja noch ein Zugriff auf dieses Objekt. Ich weiß aber nich wo. Wenn ich das aClient.Free weglasse, dann habe ich bei XP beim verlassen des Programms eine Exception: "Exception EAccessViolation in Modul DSClient.exe bei 00005D08. Zugriffsverletzung bei Adresse 00405D08 in Modul 'DSClient.exe'. Lesen von Adresse 0000000C." Ich habe noch eine zweite Funktion, die ich aus einen Tutorial übernommen habe:
Delphi-Quellcode:
Die läuft aber ohne Exception, und hier gehe ich aClient ja auch frei.
procedure TForm2.Button1Click(Sender: TObject);
var aClient: TServerMethods1Client; begin aClient := TServerMethods1Client.Create(SQLCOnnection1.DBXConnection); Try Edit1.Text := aClient.ReverseString(Edit1.Text); Finally aClient.Free; End; end; Hat jemand eine Idee, was ich falsch gemacht habe? |
AW: Datasnap File Upload
Sorry, hat mehrfach gepostet.
|
AW: Datasnap File Upload
Zitat:
|
AW: Datasnap File Upload
Um das mal etwas näher zu erläutern: Beim Create eines Proxy kann man als zweiten Parameter angeben, ob der Proxy der InstanceOwner ist. Lässt man diesen Parameter weg, wird true angenommen. Als InstanceOwner kümmert sich der Proxy selbst um die übergebenen und erhaltenen Instanzen. Ich vermute, das ist in diesem Fall auch so. Daher meine Frage nach der Implementation von UploadFile.
|
AW: Datasnap File Upload
Hier ist mein Proxy:
Delphi-Quellcode:
und hier mein Server:
//
// Erzeugt vom DataSnap-Proxy-Generator. // 11.09.2012 15:05:33 // unit proxy; interface uses Data.DBXCommon, Data.DBXClient, Data.DBXDataSnap, Data.DBXJSON, Datasnap.DSProxy, System.Classes, System.SysUtils, Data.DB, Data.SqlExpr, Data.DBXDBReaders, Data.DBXJSONReflect; type TServerMethods1Client = class(TDSAdminClient) private FEchoStringCommand: TDBXCommand; FReverseStringCommand: TDBXCommand; FUploadFileCommand: TDBXCommand; public constructor Create(ADBXConnection: TDBXConnection); overload; constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload; destructor Destroy; override; function EchoString(Value: string): string; function ReverseString(Value: string): string; procedure UploadFile(str: TStream; Store: string; FileName: string); end; implementation function TServerMethods1Client.EchoString(Value: string): string; begin if FEchoStringCommand = nil then begin FEchoStringCommand := FDBXConnection.CreateCommand; FEchoStringCommand.CommandType := TDBXCommandTypes.DSServerMethod; FEchoStringCommand.Text := 'TServerMethods1.EchoString'; FEchoStringCommand.Prepare; end; FEchoStringCommand.Parameters[0].Value.SetWideString(Value); FEchoStringCommand.ExecuteUpdate; Result := FEchoStringCommand.Parameters[1].Value.GetWideString; end; function TServerMethods1Client.ReverseString(Value: string): string; begin if FReverseStringCommand = nil then begin FReverseStringCommand := FDBXConnection.CreateCommand; FReverseStringCommand.CommandType := TDBXCommandTypes.DSServerMethod; FReverseStringCommand.Text := 'TServerMethods1.ReverseString'; FReverseStringCommand.Prepare; end; FReverseStringCommand.Parameters[0].Value.SetWideString(Value); FReverseStringCommand.ExecuteUpdate; Result := FReverseStringCommand.Parameters[1].Value.GetWideString; end; procedure TServerMethods1Client.UploadFile(str: TStream; Store: string; FileName: string); begin if FUploadFileCommand = nil then begin FUploadFileCommand := FDBXConnection.CreateCommand; FUploadFileCommand.CommandType := TDBXCommandTypes.DSServerMethod; FUploadFileCommand.Text := 'TServerMethods1.UploadFile'; FUploadFileCommand.Prepare; end; FUploadFileCommand.Parameters[0].Value.SetStream(str, FInstanceOwner); FUploadFileCommand.Parameters[1].Value.SetWideString(Store); FUploadFileCommand.Parameters[2].Value.SetWideString(FileName); FUploadFileCommand.ExecuteUpdate; end; constructor TServerMethods1Client.Create(ADBXConnection: TDBXConnection); begin inherited Create(ADBXConnection); end; constructor TServerMethods1Client.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); begin inherited Create(ADBXConnection, AInstanceOwner); end; destructor TServerMethods1Client.Destroy; begin FreeAndNil(FEchoStringCommand); FreeAndNil(FReverseStringCommand); FreeAndNil(FUploadFileCommand); inherited; end; end.
Delphi-Quellcode:
Was du sagst, mit dem InstanceOwner, verstehe ich. Aber was ist dann der Unterschied zwischen den beiden Funktionen?
unit ServerMethodsUnit1;
interface uses System.SysUtils, System.Classes, Datasnap.DSServer, Datasnap.DSAuth; type {$METHODINFO ON} TServerMethods1 = class(TComponent) private { Private declarations } public { Public declarations } function EchoString(Value: string): string; function ReverseString(Value: string): string; procedure UploadFile(str: TStream; Store, FileName: String); end; {$METHODINFO OFF} implementation uses System.StrUtils; procedure TServerMethods1.UploadFile(str: TStream; Store, FileName: String); var aFileStream: TFileStream; BytesReadCount: Integer; Buffer: array [1 .. 16 * 1024] of Byte; strPfad: String; begin strPfad := 'C:\' + Store; if not DirectoryExists(strPfad) then begin MkDir(strPfad); end; aFileStream := TFileStream.Create(strPfad + '\' + FileName, fmCreate); try repeat BytesReadCount := str.Read(Buffer, SizeOf(Buffer)); aFileStream.Write(Buffer, BytesReadCount); until (BytesReadCount < SizeOf(Buffer)); str.Position := 0; finally aFileStream.Free; end; end; function TServerMethods1.EchoString(Value: string): string; begin Result := Value; end; function TServerMethods1.ReverseString(Value: string): string; begin Result := System.StrUtils.ReverseString(Value); end; end.
Delphi-Quellcode:
in beiden erzeuge ich das Objekt aClient auf die gleiche Weise. Demnach müßte ja in beiden Funktion der gleiche Fehler auftreten. Tut er aber nicht.
procedure TForm2.Button1Click(Sender: TObject);
var aClient: TServerMethods1Client; begin aClient := TServerMethods1Client.Create(SQLCOnnection1.DBXConnection); Try Edit1.Text := aClient.ReverseString(Edit1.Text); Finally aClient.Free; End; end; procedure TForm2.Button2Click(Sender: TObject); var aClient: TServerMethods1Client; aFileStream: TFileStream; begin aClient := TServerMethods1Client.Create(SQLCOnnection1.DBXConnection); Try aFileStream := TFileStream.Create(JvFilenameEdit1.Text, fmOpenRead); try aClient.UploadFile(aFileStream, 'Upload', ExtractFileName(JvFilenameEdit1.Text)); except ShowMessage('Übertragung fehlerhaft!'); end; Finally aFileStream.Free; aClient.Free; End; end; |
AW: Datasnap File Upload
Zitat:
Aus diesem Grund hast du auch bei ReverseString keine Probleme, da dort keine Instanzen freigegeben werden müssen. |
AW: Datasnap File Upload
Das habe ich verstanden. Vielen Dank. Mal sehen, wie weit ich jetzt komme :-D
|
AW: Datasnap File Upload
Datasnap gibt (standardmäßig) Streams frei, welche übertragen wurden, also wenn man die automatisch generierten Clientklassen verwendet.
Wenn du es selbst nochmal freigibst, dann knallt es natürlich. Vertausche mal
Delphi-Quellcode:
und
aFileStream.Free;
Delphi-Quellcode:
, oder kommentiere
aClient.Free;
Delphi-Quellcode:
aus.
aFileStream.Free;
[edit] Komisch, alles ab 13:30 war eben noch nicht sichtbar. :shock: In einer der automatisch generierten Clientklassen baut Delphi ein Property ein, wo du dieses Freigeben deaktivieren kannst. |
AW: Datasnap File Upload
Zitat:
|
AW: Datasnap File Upload
Neee nee, das war nicht zur Fehlerbehandlung ... Ich war nur mal neugierig, wie/ob sich dann die Fehlerposition verschiebt.
Ich bin eher der Meinung, daß der Stream schon direkt nach dem Übertragen freigegeben wird, womit ich schon beim
Delphi-Quellcode:
eine Exception erwartet hätte.
aFileStream.Free;
Hmmmm, aber vielleicht hatte es ja dort geknallt und der Debugger zeigte nur die falsche Codezeile an. (macht er gerne mal, da der Stacktrace die Rücksprungadressen behinhaltet, also den nächsten Befehl) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:44 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz