Results 1 to 8 of 8

Thread: Probleem met invullen parameters van SQL Server API functie OpenSqlFilestream()

  1. #1
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,133

    Probleem met invullen parameters van SQL Server API functie OpenSqlFilestream()

    Gisteravond ben ik heel dapper begonnen aan een Delphi-implementatie van het opslaan in en uitlezen van het nieuwe FILESTREAM veld-type van SQL Server (Express) 2008, zie SQL Server 2008 FILESTREAM Part 3 of 3: Using the OpenSqlFilestream API. Zie evt ook de OpenSqlFilestream msdn-pagina.

    Dit heeft inmiddels geresulteerd in de volgende declaraties:
    Delphi Code:
    1. unit AwSqlFilestream;
    2.  
    3. interface
    4.  
    5. uses
    6.   Windows;
    7.  
    8. const
    9.   sqlncli10 = 'sqlncli10.dll';
    10.   SQL_FILESTREAM_READ = 0;
    11.   {$EXTERNALSYM SQL_FILESTREAM_READ}
    12.   SQL_FILESTREAM_WRITE = 1;
    13.   {$EXTERNALSYM SQL_FILESTREAM_WRITE}
    14.   SQL_FILESTREAM_READWRITE = 2;
    15.   {$EXTERNALSYM SQL_FILESTREAM_READWRITE}
    16.  
    17. type
    18.   SQL_FILESTREAM_DESIRED_ACCESS = SQL_FILESTREAM_READ..SQL_FILESTREAM_READWRITE;
    19.   {$EXTERNALSYM SQL_FILESTREAM_DESIRED_ACCESS}
    20.   LPBYTE = PByte;
    21.   {$EXTERNALSYM LPBYTE}
    22.   SIZE_T = Cardinal;
    23.   {$EXTERNALSYM SIZE_T}
    24.   PLARGE_INTEGER = PInt64;
    25.   {$EXTERNALSYM PLARGE_INTEGER}
    26.  
    27. function OpenSqlFilestream(
    28.   FilestreamPath: LPCWSTR;
    29.   DesiredAccess: SQL_FILESTREAM_DESIRED_ACCESS;
    30.   OpenOptions: ULONG;
    31.   FilestreamTransactionContext: LPBYTE;
    32.   FilestreamTransactionContextLength: SIZE_T;
    33.   AllocationSize: PLARGE_INTEGER): HFILE; stdcall;
    34.   {$EXTERNALSYM OpenSqlFilestream}
    35.  
    36. implementation
    37.  
    38. function OpenSqlFilestream; external sqlncli10 name 'OpenSqlFilestream';
    39.  
    40. end.
    De eerste vraag: is bovenstaande juist? En moet/mag de functie inderdaad als stdcall gedeclareerd worden?

    Vervolgens heb ik het stappenplan uit het artikel (zie eerste link) doorlopen en beland ik bij het ophalen van een zgn. Filestream-Transaction-Context, als volgt:
    Delphi Code:
    1. var
    2.   ContextSize: Integer;
    3.   Context: array[0..255] of Byte;
    4.   FilePath: WideString;
    5.   Handle: HFILE;
    6. begin
    7.   ...
    8.   SelectSQL = 'SELECT GET_FILESTREAM_TRANSACTION_CONTEXT(), Doc.PathName() ' +
    9.               'FROM Documents WHERE Id = :Id';
    10.   DataSet.CommandText := SelectSQL;
    11.   DataSet.Parameters.ParamValues['Id'] := 2;
    12.   DataSet.Open;
    13.   ContextSize := (DataSet.Fields[0] as TBytesField).Size; //128
    14.   (DataSet.Fields[0] as TBytesField).GetData(@Context);
    15.   FilePath := DataSet.Fields[1].Value;
    16.   Handle := OpenSqlFilestream(PWideChar(FilePath), SQL_FILESTREAM_READWRITE,
    17.     0, @Context, ContextSize, nil);
    Het probleem: ik krijg hier met geen mogelijkheid een geldige filehandle uit.

    FilePath lijkt een geldige waarde te hebben: \\ALBERT-NB\SQLEXPRESS\v1\Awerdo\dbo\Documents\Doc\B959E0E4-9905-497D-8C38-4E3A284D406C. Dus ik vermoed sterk dat het hem zit in Context en ContextSize. Jammer is dat ik dat eigenlijk niet kan valideren. In een DB-control ziet het eruit als (VARBYTES).

    Het volgende heb ik ook geprobeerd:

    Delphi Code:
    1. var
    2.   Context: Variant;
    3. begin
    4.   ...
    5.   Context := DataSet.Fields[0].Value;
    6.   ContextSize := Length(Context); //8
    7.   Handle := OpenSqlFilestream(PWideChar(FilePath), SQL_FILESTREAM_READWRITE,
    8.     0, @Context, ContextSize, nil);
    en
    Delphi Code:
    1. var
    2.   Context: Variant;
    3.   ContextArr: TVarArray;
    4. begin
    5.   ...
    6.   Context := DataSet.Fields[0].Value;
    7.   ContextArr := VarArrayAsPSafeArray(Context);
    8.   ContextSize := VarArrayHighBound(Context, 1); //15
    9.   ContextSize := ContextArr.Bounds[0].ElementCount; //16
    10.   Handle := OpenSqlFilestream(PWideChar(FilePath), SQL_FILESTREAM_READWRITE,
    11.     0, ContextArr.Data, ContextSize, nil);
    Alle probeersels resulteren in INVALID_HANDLE_VALUE.

    Wat doe ik verkeerd? Heeft iemand een idee?
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  2. #2
    Counting your refs Paul-Jan's Avatar
    Join Date
    Feb 2002
    Location
    Lage Zwaluwe
    Posts
    2,160
    Interessant experiment!

    Je definitie (incl. stdcall declaratie) ziet er goed uit. Wat zegt getLastError? Die is volgens de MSDN netjes gezet in geval van een INVALID_HANDLE_VALUE.

    Voor wat betreft je context token, daar zou ik mee beginnen met nalopen. Voer je query uit in een willekeurige query tool en kijk welke bytes er in zitten. Je kunt dan in de delphi debugger hetzelfde doen (breakpoint zetten, ctrl-f7, data opvragen, igv pointer opvragen met pointer^,100m en co). De bytes zullen wellicht niet overeenkomen, maar je krijgt dan in ieder geval een beetje een gevoel voor het _soort_ data dat in zo'n token hoort te staan.

    Rechtstreeks de variant gebruiken gaat in ieder geval niet werken, je moet aan de functie echt een pointer naar de bytes meegeven.
    Last edited by Paul-Jan; 29-Dec-09 at 08:40.

  3. #3
    Ik heb ook nogal eens ruzie met die arrays. Soms werkt @context, soms werkt @context[0]... Hoewel dat onderscheid meestal gaat over arrays of char, heeft het daar misschien nog mee te maken?
    1+1=b

  4. #4
    Counting your refs Paul-Jan's Avatar
    Join Date
    Feb 2002
    Location
    Lage Zwaluwe
    Posts
    2,160
    Als context een eenvoudige statische array is, zoals in voorbeeld 1, dan is @context precies hetzelfde als @context[0]. Voor dynamische arrays (en strings) geldt dat niet, dan moet je meestal @array[0] of PChar(string) meegeven.

    Oh, over poging 2, die Length() is echt fout, tenzij context stiekem uit een ansistring bestaat, maar dat lijkt me stug.

  5. #5
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,133
    Ja, dat was een wanhoopspoging...

    Enfin, GetLastError geeft ERROR_INVALID_PARAMETER = 87. Dat was te verwachten...

    Inspectie van dat ByteArray met de debugger geeft: 16, 0, 35, 48, 123, 12, 62, 187, 195, 73, 167, 115, 122, 64, 198, 116, 40, 210, 0, 0, 0, 0...
    In SSMS ziet dat er zo uit: 0x 40 12 8C E0 B2 3B DC 4A 96 2C 95 43 C9 E1 FF 71 (andere transactie).

    Ik ben er dus redelijk van overtuigd dat de eerste byte de grootte voorstelt, en het array zelf bij de derde byte begint:
    Delphi Code:
    1. begin
    2.   ...
    3.   (DataSet.Fields[0] as TBytesField).GetData(@Context, True);
    4.   ContextSize := Context[0];
    5.   Handle := OpenSqlFilestream(PWideChar(FilePath), SQL_FILESTREAM_READWRITE,
    6.     0, @Context[2], ContextSize, nil);
    GetLastError geeft nu ERROR_ACCESS_DENIED = 5. Dus de parameters zijn nu wel goed, maar toegang is verboden?

    Het lijkt me niet te kunnen liggen aan de beveiligingsinstellingen van de betreffende map, want die worden door SQL Server beheerd. En de bestanden voor SQL Server Native Client staan keurig in C:\Windows\System32.

    In welke situaties kun je geen file-handle verkrijgen?
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  6. #6
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,133
    Whoeha, gelukt! Ik moest met een Windows(domein)account connecten.

    Volgende stap: een Word document inlezen , via Obtaining a File Name From a File Handle.

    Happy Christmas.
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  7. #7
    Counting your refs Paul-Jan's Avatar
    Join Date
    Feb 2002
    Location
    Lage Zwaluwe
    Posts
    2,160
    Hee, netjes... gefeliciteerd!

  8. #8
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,133
    Thanks.

    Helaas werkt het omzetten van een dergelijke filehandle naar een filename niet. Dus helaas nog geen oplossing voor het rechtstreeks schrijven van Word-documenten naar de database.

    Enfin, voor de geïnteresseerden, hierbij de GetFileNameFromHandle functie:
    Delphi Code:
    1. uses
    2.   Windows, PsAPI, SysUtils;
    3.  
    4. function GetFileNameFromHandle(Handle: HFILE): String;
    5. var
    6.   FileName: array[0..MAX_PATH] of Char;
    7.   Mapping: HLOCAL;
    8.   FileSizeHi: Integer;
    9.   FileSizeLo: Integer;
    10.   View: Pointer;
    11.   Drives: array[0..128] of Char;
    12.   PDrive: PChar;
    13.   Device: array[0..MAX_PATH] of Char;
    14. begin
    15.   Result := '';
    16.   FileSizeHi := 0;
    17.   FileSizeLo := GetFileSize(Handle, @FileSizeHi);
    18.   if (FileSizeHi <> 0) or (FileSizeLo <> 0) then
    19.   begin
    20.     Mapping := CreateFileMapping(Handle, nil, PAGE_READONLY, 0, 1, nil);
    21.     if Mapping <> 0 then
    22.     try
    23.       View := MapViewOfFile(Mapping, FILE_MAP_READ, 0, 0, 1);
    24.       if View <> nil then
    25.       try
    26.         if GetMappedFileName(GetCurrentProcess, View, FileName,
    27.           MAX_PATH) <> 0 then
    28.         begin
    29.           Result := FileName;
    30.           if GetLogicalDriveStrings(SizeOf(Drives), Drives) <> 0 then
    31.           begin
    32.             PDrive := Drives;
    33.             while PDrive^ <> #0 do
    34.             begin
    35.               PDrive[2] := #0;
    36.               if QueryDosDevice(PDrive, Device, MAX_PATH) <> 0 then
    37.                 if AnsiStrLIComp(FileName, Device, StrLen(Device)) = 0 then
    38.                 begin
    39.                   Result := StringReplace(FileName, Device, PDrive,
    40.                     [rfIgnoreCase]);
    41.                   Break;
    42.                 end;
    43.               Inc(PDrive, 4);
    44.             end;
    45.           end;
    46.         end;
    47.       finally
    48.         UnmapViewOfFile(View);
    49.       end;
    50.     finally
    51.       CloseHandle(Mapping);
    52.     end;
    53.   end;
    54. end;
    En de ongetwijfeld iets nuttigere functie om een bestand in een FILESTREAM-kolom te pompen:
    Delphi Code:
    1. function InsertSqlDocument(var Id: Integer; const FileName: String;
    2.   Connection: TADOConnection): Boolean;
    3. const
    4.   InsertSQL = 'INSERT INTO Documents (Id, FileName) VALUES (:Id, :FileName)';
    5.   SelectSQL = 'SELECT GET_FILESTREAM_TRANSACTION_CONTEXT(), Doc.PathName() ' +
    6.               'FROM Documents WHERE Id = :Id';
    7.   BlockSize = 1024 * 512;
    8. var
    9.   CloseConn: Boolean;
    10.   DataSet: TADOQuery;
    11.   Context: array[0..127] of Byte;
    12.   FilePath: WideString;
    13.   FileHandle: HFILE;
    14.   Source: TStream;
    15.   Dest: TStream;
    16.   Buffer: array[0..BlockSize - 1] of Char;
    17.   ReadCount: Integer;
    18. begin
    19.   Result := False;
    20.   CloseConn := not Connection.Connected;
    21.   DataSet := TADOQuery.Create(nil);
    22.   try
    23.     if FileExists(FileName) then
    24.     try
    25.       Connection.Open;
    26.       Connection.BeginTrans;
    27.       if Id = -1 then
    28.         Id := GetNewRecordId(Connection);
    29.       DataSet.Connection := Connection;
    30.       DataSet.SQL.Text := InsertSQL;
    31.       DataSet.Parameters.ParamValues['Id'] := Id;
    32.       DataSet.Parameters.ParamValues['FileName'] := ExtractFileName(FileName);
    33.       DataSet.ExecSQL;
    34.       DataSet.SQL.Text := SelectSQL;
    35.       DataSet.Parameters.ParamValues['Id'] := Id;
    36.       DataSet.Open;
    37.       if not DataSet.Eof then
    38.       begin
    39.         DataSet.Fields[0].GetData(@Context);
    40.         FilePath := DataSet.Fields[1].Value;
    41.         DataSet.Close;
    42.         FileHandle := OpenSqlFilestream(PWideChar(FilePath),
    43.           SQL_FILESTREAM_READWRITE, 0, @Context[2], Context[0], nil);
    44.         if FileHandle <> INVALID_HANDLE_VALUE then
    45.         begin
    46.           Source := TFileStream.Create(FileName, fmOpenRead);
    47.           Dest := TFileStream.Create(FileHandle);
    48.           try
    49.             repeat
    50.               ReadCount := Source.Read(Buffer, SizeOf(Buffer));
    51.               Dest.Write(Buffer, ReadCount);
    52.             until ReadCount <= 0;
    53.           finally
    54.             Dest.Free; //Implicitly closes FileHandle
    55.             Source.Free;
    56.           end;
    57.           Connection.CommitTrans;
    58.           Result := True;
    59.         end
    60.         else
    61.           RaiseLastOSError;
    62.       end;
    63.     except
    64.       Connection.RollbackTrans;
    65.       raise;
    66.     end;
    67.   finally
    68.     DataSet.Free;
    69.     if CloseConn then
    70.       Connection.Close;
    71.   end;
    72. end;
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •