Mike Taulty's Blog
Bits and Bytes from Microsoft UK
SQL 2008, FILESTREAM, C++, Managed Code

Blogs

Mike Taulty's Blog

Elsewhere

I spent a bunch of time today just trying to play with the FILESTREAM option on varbinary(max) in SQL Server 2008.

There's a good intro to doing this up here but it stops short of writing code against it which is where I wanted to go.

So, I've created my database with a FILESTREAM filegroup, I've got a table with a FILESTREAM entry in it a la;

create table Photos
(
  rowid uniqueidentifier rowguidcol not null primary key default(NEWID()),
  photo varbinary(max) filestream
)

 

and I've read about how I'm supposed to use PathName() as a function and then use GET_FILESTREAM_TRANSACTION_CONTEXT() as a function and then feed all that into a new API (in the SQL native client library) called OpenSqlFilestream but I look in the docs and I see that the declaration in the docs;

 

HANDLE OpenSqlFilestream (
  LPCWSTR FilestreamPath,
  SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess,
  ULONG OpenOptions,
  LPBYTE FilestreamTransactionContext,
  ULONG FilestreamTransactionContextLength
);

 

doesn't match the definition in the header file which is;

HANDLE __stdcall OpenSqlFilestream (
           LPCWSTR                        FilestreamPath,
           SQL_FILESTREAM_DESIRED_ACCESS  DesiredAccess,
           ULONG                          OpenOptions,
           LPBYTE                         FilestreamTransactionContext,
           SSIZE_T                        FilestreamTransactionContextLength,
           PLARGE_INTEGER                 AllocationSize);
 

which leads into that whole discussion of why (for me) .NET is a more productive model than C++ because in .NET I just have the assembly whereas in C++ I have the header, the .lib file and the DLL itself and if they are out of sync with each other then you can nicely waste a few hours.

I kept trying to get an interop signature to work in .NET for this function but I was getting nowhere and so I ended up writing a call to the function from C++/CLI and when that worked I tried to translate this back to the .NET world and ended up with the old fashioned (i.e. IntPtr based) version below;

        [DllImport("sqlncli10.dll", SetLastError=true, CharSet=CharSet.Unicode)]
        private extern static IntPtr OpenSqlFilestream(
            string path,
            int access,
            int openOptions,
            byte[] txnContext,
            int contextLength,
            ref int allocationSize);

which seems to work for me. Currently calling it from a fairly dodgy looking piece of code which is this (purely trying to get it to not give me back INVALID_HANDLE which I believe I've now got to);

   class Program
    {
        static void Main(string[] args)
        {
            using (SqlConnection con = new SqlConnection(
                "server=.;database=photodb;integrated security=sspi"))
            {
                con.Open();

                SqlTransaction tran = con.BeginTransaction();

                using (SqlCommand com = new SqlCommand("select photo.PathName(), " +
                    "GET_FILESTREAM_TRANSACTION_CONTEXT() from photos", con))
                {
                    com.Transaction = tran;

                    SqlDataReader reader = com.ExecuteReader();

                    while (reader.Read())
                    {
                        byte[] bits = ((byte[])(reader[1]));
                        int  len = bits.Length;
                        int allocSize = 0;

                        IntPtr h = OpenSqlFilestream(
                            (string)reader[0],
                            0,
                            0,
                            bits,
                            len,
                            ref allocSize);

                        int errno = Marshal.GetLastWin32Error();

                        Console.WriteLine(errno);

                        CloseHandle(h);
                    }
                }
                tran.Rollback();

                con.Close();

            }
        }
        [DllImport("sqlncli10.dll", CharSet=CharSet.Unicode)]
        private extern static IntPtr OpenSqlFilestream(
            string path,
            int access,
            int openOptions,
            byte[] txnContext,
            int contextLength,
            ref int allocationSize);

        [DllImport("kernel32.dll")]
        private extern static void CloseHandle(IntPtr handle);
    }

What a nice way to spend a couple of hours - at least there's a happy ending.


Posted Mon, Oct 22 2007 8:18 AM by mtaulty

Comments

Jason Haley wrote Interesting Finds: October 23, 2007
on Tue, Oct 23 2007 6:46 AM