Как извлечь большие двоичные данные из таблицы MS SQL с помощью WinAPI?

#c #sql-server #winapi #odbc

#c #sql-сервер #winapi #odbc

Вопрос:

Мне нужно поместить и извлечь большие двоичные данные из таблицы ms sql.

Для размещения данных я использую ifstream и SQLPutData. Вот мой код для размещения данных (без проверки ошибок и выделения некоторых дескрипторов):

  char file[MAX_PATH] = "";
    strcpy(file, argv[2]);
    ifstream f(file, ios::binary | ios::ate);
    size_t sz = f.tellg();
    f.seekg(0, ios::beg);
    char *buf = new char[sz];
    char *ptr = buf;
    f.read(ptr, sz);
    f.close();
SQLLEN lbytes, cbBinarySize;
    PTR pParamID;
    lbytes = (SDWORD) sz;
    cbBinarySize = SQL_LEN_DATA_AT_EXEC(lbytes);

char SQL[512] = "INSERT INTO bindata (filename, binData) VALUES ('";
    strcat(SQL, file);
    strcat(SQL, "', ?)");

ret = SQLPrepare(stmt, (SQLCHAR *) SQL, SQL_NTS);
SQLSMALLINT dataType, decDigits, nullable;
    SQLUINTEGER paramsize;
ret = SQLDescribeParam(stmt, 1, amp;dataType, amp;paramsize, amp;decDigits, amp;nullable);
    ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, dataType, paramsize, 0, (VOID *)1, 0, amp;cbBinarySize);

    ret = SQLExecDirect(stmt, (SQLCHAR *) SQL, SQL_NTS);

    if (ret != SQL_NEED_DATA amp;amp; ret != SQL_SUCCESS amp;amp; ret != SQL_SUCCESS_WITH_INFO) return ret;

    ret = SQLParamData(stmt, amp;pParamID);
    if (ret == SQL_NEED_DATA) {
        while (lbytes > 5000 ) {

            ret = SQLPutData(stmt, (SQLCHAR *)ptr, 5000);
            lbytes -= 5000;
            ptr  = 5000;
        }

        ret = SQLPutData(stmt, ptr, lbytes);


        ret = SQLParamData(stmt, amp;pParamID);
        if (ret != SQL_SUCCESS) return ret;

}
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
delete[] ptr;
delete[] buf;
  

Для меня это работает нормально, у меня есть двоичные данные в поле таблицы VARBINARY (MAX). Теперь я хочу получить эти данные оттуда. Вот что я пытался сделать (я проверил статью MSDN https://msdn.microsoft.com/en-us/library/ms811006.aspx ):

 unsigned char* data = new unsigned char[2500000];
    SQLINTEGER cbFdata = SQL_NTS;

    SQLAllocHandle(SQL_HANDLE_STMT, dbc, amp;stmt);
    ret = SQLExecDirect(stmt, (SQLCHAR *) "SELECT binData FROM bindata WHERE filename='AgentCPP.exe'", SQL_NTS);
    if (ret != SQL_SUCCESS amp;amp; ret != SQL_SUCCESS_WITH_INFO) return ret;

    ret = SQLFetch(stmt);

    do {

        ret = SQLGetData(stmt, 1, SQL_C_BINARY, data, 5000, amp;cbFdata);

    } while (ret != SQL_NO_DATA_FOUND);


    SQLFreeHandle(SQL_HANDLE_STMT, stmt);
  

Но вместо массива, содержащего мои данные, я получаю некоторый мусор, который может быть только частью моих данных. Что я делаю не так?
Если бы я использовал .Net, возможно, я использовал какой-то массив byte[] или что-то в этом роде, но здесь я не могу. Не могли бы вы мне помочь?

UPD. Просто попытался сделать это с помощью C #, и мне потребовалось около 3 минут и дюжина строк кода. Просто чтобы показать, какой результат я пытаюсь получить — этот код создает полностью рабочий exe-файл из двоичных данных, который был помещен в базу данных моим первым блоком кода из этого вопроса:

 OdbcCommand com = new OdbcCommand(@"SELECT binData FROM bindata WHERE filename='AgentCPP.exe'", con);
        com.Connection = con;
        OdbcDataReader reader = com.ExecuteReader();

        if (reader.Read())
            binData =  (byte[]) reader.GetValue(0);

        reader.Dispose();
        com.Dispose();
        con.Close();

        FileStream fs = new FileStream(@"agent.exe", FileMode.Create, FileAccess.Write);
        fs.Write(binData, 0, binData.Length);
        fs.Close();
  

Как я могу добиться того же, используя c winapi?

Ответ №1:

Хорошо, кажется, я выяснил, что было не так в моем коде.

Во-первых, мне пришлось восстановить фактическую длину данных, а не просто использовать приблизительный размер массива 2500000, поэтому сначала я использовал поддельный указатель, чтобы получить размер данных, а затем использовал std::vector . Во-вторых, я поместил 5000 в SQLGetData как «часть» моих данных, что неверно, мне нужно использовать фактический размер данных. Итак, вот код, который отлично работает для меня:

 SQLINTEGER cbSize = SQL_NTS;

    ret = SQLExecDirect(stmt, (SQLCHAR *) "SELECT binData FROM bindata WHERE filename='AgentCPP.exe'", SQL_NTS);
    if (ret != SQL_SUCCESS amp;amp; ret != SQL_SUCCESS_WITH_INFO) return ret;

    ret = SQLFetch(stmt);

    char *ptr = new char[1];
    if ((ret = SQLGetData(stmt, 1, SQL_C_BINARY, ptr, 0, amp;cbSize) != SQL_NO_DATA))
    {

        vector<char> vec(cbSize);
        SQLGetData(stmt, 1, SQL_C_BINARY, amp;vec[0], cbSize, amp;cbSize);
        ofstream fout;
        fout.open("test.exe", ios::binary);
        fout.write(amp;vec[0], cbSize);
        fout.close();
        vec.clear();
        }
    delete[] ptr;