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

Mike's Badges

Follow on Twitter
View mike's profile on slideshare
Add to Technorati Favorites
CW Blog Awards

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
(C) Mike Taulty, 2009. All rights reserved. The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Inappropriate comments will be deleted at the authors discretion. All code samples are provided "AS IS" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems