I am having issues with Handles. I have Bytebeat (music in bytes) playing inside of a DWORD WINAPI function. When I try to terminate and close the thread, it straight up gives me the error in the title. This is my code:
#include <windows.h>
#pragma comment(lib, "Winmm.lib")
DWORD WINAPI bytebeat1(LPVOID) {
while (1) {
HWAVEOUT hwo = 0;
WAVEFORMATEX wfx = { WAVE_FORMAT_PCM, 1, 11000, 11000, 1, 8, 0 };
waveOutOpen(&hwo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
char buffer[11000 * 6];
for (DWORD t = 0; t < sizeof(buffer); t++)
buffer[t] = static_cast<char>(t & t + t / 256) - t * (t >> 15) & 64;
WAVEHDR hdr = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
waveOutPrepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutWrite(hwo, &hdr, sizeof(WAVEHDR));
waveOutUnprepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutClose(hwo);
Sleep(6000);
}
}
DWORD WINAPI bytebeat2(LPVOID) {
while (1) {
HWAVEOUT hwo = 0;
WAVEFORMATEX wfx = { WAVE_FORMAT_PCM, 1, 8000, 8000, 1, 8, 0 };
waveOutOpen(&hwo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
char buffer[8000 * 6];
for (DWORD t = 0; t < sizeof(buffer); t++)
buffer[t] = static_cast<char>(t, t / 5) >> t / 25 & t / 55 ^ t & 255 ^ (t / 150) ^ 2508025 * 24240835810 & (t / 100) * t / 6000 ^ 5000 * t / 2500 ^ 25 * t / 24;
WAVEHDR hdr = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
waveOutPrepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutWrite(hwo, &hdr, sizeof(WAVEHDR));
waveOutUnprepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutClose(hwo);
Sleep(6000);
}
}
int main() {
HANDLE beat1 = CreateThread(0, 0, bytebeat1, 0, 0, 0);
Sleep(6000);
TerminateThread(beat1, 0); CloseHandle(beat1);
Sleep(1000);
HANDLE beat2 = CreateThread(0, 0, bytebeat2, 0, 0, 0);
Sleep(6000);
TerminateThread(beat2, 0); CloseHandle(beat2);
}
I do not know why this is happening. The only fix is compiling it with G++ but I want it so I could just build it. Any help is appreciated. Thanks!
As the documentation makes clear, you can't use TerminateThread this way. Instead, replace the calls to Sleep with an interruptible sleep function that will terminate the thread cleanly and safely if requested to do so.
When you call TerminateThread, you are basically force-crashing your threads. They still have their own stack allocated and handles to Windows resources. They aren't cleaned up properly, causing your crash.
Here's a simple example of how to close your threads without any error. In a real-world scenario this is an unprofessional solution, but it shows the bare minimum that you need to do.
#include <windows.h>
#pragma comment(lib, "Winmm.lib")
volatile bool quit1 = false;
volatile bool quit2 = false;
DWORD WINAPI bytebeat1(LPVOID) {
while (!quit1) {
HWAVEOUT hwo = 0;
WAVEFORMATEX wfx = { WAVE_FORMAT_PCM, 1, 11000, 11000, 1, 8, 0 };
waveOutOpen(&hwo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
char buffer[11000 * 6];
for (DWORD t = 0; t < sizeof(buffer); t++)
buffer[t] = static_cast<char>(t & t + t / 256) - t * (t >> 15) & 64;
WAVEHDR hdr = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
waveOutPrepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutWrite(hwo, &hdr, sizeof(WAVEHDR));
waveOutUnprepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutClose(hwo);
Sleep(6000);
}
return 0;
}
DWORD WINAPI bytebeat2(LPVOID) {
while (!quit2) {
HWAVEOUT hwo = 0;
WAVEFORMATEX wfx = { WAVE_FORMAT_PCM, 1, 8000, 8000, 1, 8, 0 };
waveOutOpen(&hwo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
char buffer[8000 * 6];
for (DWORD t = 0; t < sizeof(buffer); t++)
buffer[t] = static_cast<char>(t, t / 5) >> t / 25 & t / 55 ^ t & 255 ^ (t / 150) ^ 2508025 * 24240835810 & (t / 100) * t / 6000 ^ 5000 * t / 2500 ^ 25 * t / 24;
WAVEHDR hdr = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
waveOutPrepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutWrite(hwo, &hdr, sizeof(WAVEHDR));
waveOutUnprepareHeader(hwo, &hdr, sizeof(WAVEHDR));
waveOutClose(hwo);
Sleep(6000);
}
return 0;
}
int main() {
HANDLE beat1 = CreateThread(0, 0, bytebeat1, 0, 0, 0);
Sleep(6000);
quit1 = true;
WaitForSingleObject(beat1, INFINITE);
CloseHandle(beat1);
Sleep(1000);
HANDLE beat2 = CreateThread(0, 0, bytebeat2, 0, 0, 0);
Sleep(6000);
quit2 = true;
WaitForSingleObject(beat2, INFINITE);
CloseHandle(beat2);
}
Our C++ code on Windows uses ODBC to talk to SQL Server.
We are in the process of porting to Linux and
getting this ODBC error for a datetime Stored Procedure argument.
Datetime field overflow. Fractional second precision exceeds the scale specified in the parameter binding.
Googling that error says to set scale to 3 on the ODBC bind.
We do that and it works on Windows but not Linux.
Here is a semi small code sample that reproduces the problem.
The sample is 330 lines of C++ and 100 lines of SQL.
(The original is 1000 times bigger.)
It seems to be related to a blob SP argument.
datetime before the blob work, and those after the blob fail.
I tried making a 3 column test table with (datetime, blob, datetime)
but the problem did not happen.
On Windows this test inserts 820 rows while
on Linux it inserts 700 because 120 fail.
Windows has "ODBC Driver 17 for SQL Server".
Linux is Ubuntu 20.04, unixODBC 2.3.7, ODBC 17.7
Create a database name bgb_test2.
Create a DSN named bgb8_sql2016 pointing to DB server.
Add SQL user 'bgb' with password 'Abc-123'.
Or change the code as you like.
SQL code
USE [bgb_test2]
GO
/****** Object: StoredProcedure [dbo].[sp_TTemporalAccounts_ins] Script Date: 5/28/2021 11:46:19 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TTemporalAccounts](
[acctId] [int] NOT NULL,
[rowClassifier] [char](16) NULL,
[grpClassifier] [char](16) NULL,
[LAD] [datetime] NULL,
[LAPD] [datetime] NULL,
[NAD] [datetime] NULL,
[AccountBlob] [text] NULL,
[StatementDate] [datetime] NULL,
[postBalance] [money] NULL,
[Balance] [money] NULL,
[CountDe] [int] NULL,
[bucketMTD1] [money] NULL,
[bucketMTD2] [money] NULL,
[bucketMTD3] [money] NULL,
[bucketCTD1] [money] NULL,
[bucketCTD2] [money] NULL,
[bucketCTD3] [money] NULL,
[parent01ATID] [int] NULL,
[parent01AID] [int] NULL,
[parent02ATID] [int] NULL,
[parent02AID] [int] NULL,
[parent03ATID] [int] NULL,
[parent03AID] [int] NULL,
[VisaInterestRate] [money] NULL,
[VisaLateCharge] [money] NULL,
[VisaParam1] [money] NULL,
[VisaParam2] [money] NULL,
[VisaParam3] [money] NULL,
[VisaParam4] [money] NULL,
[VisaParam5] [money] NULL,
[VisaParam6] [money] NULL,
[VisaParam7] [money] NULL,
[VisaParam8] [money] NULL,
[tpyNAD] [datetime] NULL,
[tpyLAD] [datetime] NULL,
[tpyBlob] [text] NULL,
[param_sig] [int] NULL,
[param_state] [varchar](171) NULL,
[param_lcd] [datetime] NULL,
[param_fvt] [datetime] NULL,
[_paramlvt] [datetime] NULL,
CONSTRAINT [csPk_TTemporalAccounts] PRIMARY KEY CLUSTERED
(
[acctId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE PROCEDURE [dbo].[sp_TTemporalAccounts_ins] (
#acctId int, #rowClassifier char (16), #grpClassifier char (16),
#LAD datetime, #LAPD datetime, #NAD datetime,
#AccountBlob text, #StatementDate datetime, #postBalance money,
#Balance money, #CountDe int,
#bucketMTD1 money, #bucketMTD2 money, #bucketMTD3 money,
#bucketCTD1 money, #bucketCTD2 money, #bucketCTD3 money,
#parent01ATID int, #parent01AID int,
#parent02ATID int, #parent02AID int,
#parent03ATID int, #parent03AID int,
#VisaInterestRate money, #VisaLateCharge money,
#VisaParam1 money, #VisaParam2 money, #VisaParam3 money,
#VisaParam4 money, #VisaParam5 money, #VisaParam6 money,
#VisaParam7 money, #VisaParam8 money,
#tpyNAD datetime, #tpyLAD datetime, #tpyBlob text,
#param_sig int, #param_state varchar (171),
#param_lcd datetime, #param_fvt datetime, #_paramlvt datetime
) AS SET NOCOUNT ON; SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET ARITHABORT ON; SET QUOTED_IDENTIFIER ON; SET CONCAT_NULL_YIELDS_NULL ON; SET NUMERIC_ROUNDABORT OFF;
INSERT INTO TTemporalAccounts ( [acctId], [rowClassifier], [grpClassifier], [LAD], [LAPD], [NAD],
[AccountBlob], [StatementDate], [postBalance], [Balance], [CountDe], [bucketMTD1], [bucketMTD2], [bucketMTD3],
[bucketCTD1], [bucketCTD2], [bucketCTD3], [parent01ATID], [parent01AID], [parent02ATID], [parent02AID],
[parent03ATID], [parent03AID], [VisaInterestRate], [VisaLateCharge],
[VisaParam1], [VisaParam2], [VisaParam3], [VisaParam4], [VisaParam5], [VisaParam6], [VisaParam7], [VisaParam8],
[tpyNAD], [tpyLAD], [tpyBlob],
[param_sig], [param_state], [param_lcd], [param_fvt], [_paramlvt])
VALUES ( #acctId, #rowClassifier, #grpClassifier, #LAD, #LAPD, #NAD, #AccountBlob, #StatementDate,
#postBalance, #Balance, #CountDe, #bucketMTD1, #bucketMTD2, #bucketMTD3, #bucketCTD1, #bucketCTD2, #bucketCTD3,
#parent01ATID, #parent01AID, #parent02ATID, #parent02AID, #parent03ATID, #parent03AID,
#VisaInterestRate, #VisaLateCharge,
#VisaParam1, #VisaParam2, #VisaParam3, #VisaParam4, #VisaParam5, #VisaParam6, #VisaParam7, #VisaParam8,
#tpyNAD, #tpyLAD, #tpyBlob,
#param_sig, #param_state, #param_lcd, #param_fvt, #_paramlvt)
GO
C++ code
#ifdef WIN32
#ifdef _DEBUG
#define _ITERATOR_DEBUG_LEVEL 1
#endif
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <sal.h>
#endif
#include <string.h>
#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include <string>
// Binding to datetime column is failing on Linux
RETCODE
BBCheckForInfo(SQLSMALLINT HandleType, SQLHANDLE h, RETCODE rc, const char* api, const char* name = 0)
{
SQLCHAR SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER NativeError;
SQLSMALLINT i, MsgLen;
SQLRETURN rc2;
long cpu_time = 0;
long elapsed_time = 0;
// Support growing these if needed
SQLCHAR* pMsg = Msg;
size_t MsgAlloc = sizeof(Msg);
if (name == 0) name = "";
// Print any errors or warnings.
if (rc != SQL_SUCCESS) {
// Get the status records.
i = 1;
while ((rc2 = SQLGetDiagRec(HandleType, h, i, SqlState, &NativeError, pMsg, MsgAlloc, &MsgLen)) == SQL_SUCCESS || rc2 == SQL_SUCCESS_WITH_INFO) {
if (rc2 == SQL_SUCCESS_WITH_INFO) {
printf("Msg[] too small, need %d bytes (have %d)\n", MsgLen, MsgAlloc);
if (pMsg != Msg) delete[] pMsg;
MsgAlloc = MsgLen + 100;
pMsg = new SQLCHAR[MsgAlloc + 10];
// Do not increment i so we retry getting the last error message
continue;
}
i++;
printf("Query %s API %s() Name %s State %s Native %d Msg %s\n",
name,
api,
name,
SqlState, NativeError, Msg);
} // while
if (rc2 != SQL_NO_DATA) {
printf("SQLGetDiagRec(%s) name %s returned %d\n", api, name, rc2);
}
} // if
if (pMsg != Msg) delete[] pMsg;
pMsg = 0;
if (rc == SQL_SUCCESS_WITH_INFO) rc = SQL_SUCCESS;
return rc;
}
enum class CI_type {
CI_int,
CI_char16,
CI_datetime,
CI_text,
CI_money,
CI_varchar171,
// CI_end
};
#define N_COL 41
struct ColInfo {
const char* name;
enum CI_type type;
} g_ColInfoArray[N_COL] = {
"acctId", CI_type::CI_int,
"rowClassifier", CI_type::CI_char16,
"grpClassifier", CI_type::CI_char16,
"LAD", CI_type::CI_datetime,
"LAPD", CI_type::CI_datetime,
"NAD", CI_type::CI_datetime,
"AccountBlob", CI_type::CI_text,
"StatementDate", CI_type::CI_datetime,
"postBalance", CI_type::CI_money,
"Balance", CI_type::CI_money,
"CountDe", CI_type::CI_int,
"bucketMTD1", CI_type::CI_money,
"bucketMTD2", CI_type::CI_money,
"bucketMTD3", CI_type::CI_money,
"bucketCTD1", CI_type::CI_money,
"bucketCTD2", CI_type::CI_money,
"bucketCTD3", CI_type::CI_money,
"parent01ATID", CI_type::CI_int,
"parent01AID", CI_type::CI_int,
"parent02ATID", CI_type::CI_int,
"parent02AID", CI_type::CI_int,
"parent03ATID", CI_type::CI_int,
"parent03AID", CI_type::CI_int,
"VisaInterestRate", CI_type::CI_money,
"VisaLateCharge", CI_type::CI_money,
"VisaParam1", CI_type::CI_money,
"VisaParam2", CI_type::CI_money,
"VisaParam3", CI_type::CI_money,
"VisaParam4", CI_type::CI_money,
"VisaParam5", CI_type::CI_money,
"VisaParam6", CI_type::CI_money,
"VisaParam7", CI_type::CI_money,
"VisaParam8", CI_type::CI_money,
"tpyNAD", CI_type::CI_datetime,
"tpyLAD", CI_type::CI_datetime,
"tpyBlob", CI_type::CI_text,
"param_sig", CI_type::CI_int,
"param_state", CI_type::CI_varchar171,
"param_lcd", CI_type::CI_datetime,
"param_fvt", CI_type::CI_datetime,
"_paramlvt", CI_type::CI_datetime,
// { 0, CI_type::CI_end }
};
void
main()
{
std::string ConStr;
ConStr += ";DSN=bgb8_sql2016"; // change
ConStr += ";DATABASE=bgb_test2";
ConStr += ";UID=bgb;PWD=Abc-123"; // change
ConStr += ";APP=DBB";
// const char* computername = dbbGetenv("COMPUTERNAME");
ConStr += ";WSID=host";
// ConStr += computername;
ConStr += ";AutoTranslate=no";
SQLHENV hEnv;
int rc;
rc = SQLAllocEnv(&hEnv);
rc = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
rc = BBCheckForInfo(SQL_HANDLE_ENV, hEnv, rc, "SQLSetEnvAttr", "SQL_ATTR_ODBC_VERSION");
SQLHDBC hDbc;
rc = SQLAllocConnect(hEnv, &hDbc);
rc = BBCheckForInfo(SQL_HANDLE_ENV, hEnv, rc, "SQLAllocConnect()");
// rc = SQLSetConnectAttr(hDbc, SQL_COPT_SS_BCP, (void*)SQL_BCP_ON, SQL_IS_INTEGER);
// BBCheckForInfo(SQL_HANDLE_DBC, hDbc, rc, "SQLSetConnectAttr", "SQL_COPT_SS_BCP");
short outlen = 0;
UCHAR outbuf[2048];
rc = SQLDriverConnect(hDbc, NULL, (UCHAR*)ConStr.c_str(), ConStr.length(), outbuf, sizeof(outbuf), &outlen, SQL_DRIVER_NOPROMPT);
rc = BBCheckForInfo(SQL_HANDLE_DBC, hDbc, rc, "SQLDriverConnect()");
SQLHSTMT hstmt;
SQLAllocStmt(hDbc, &hstmt);
rc = BBCheckForInfo(SQL_HANDLE_DBC, hDbc, rc, "SQLAllocStmt()");
int skey = 0;
for (int rows = 0; rows < 20; rows++) {
// for x in 0 .. 2^N_COL-1
// (c in x) means set col, else NULL
// Inserting 2^41 rows may hit practical limitations (BOOM)
for (int nn = 0; nn < N_COL; nn++) {
void* mem[N_COL];
for (int c = 0; c < N_COL; c++) {
// Set PK and column nn to values, rest are NULL
SQLHSTMT StatementHandle = hstmt;
SQLUSMALLINT ParameterNumber = c + 1;
SQLSMALLINT InputOutputType = SQL_PARAM_INPUT;
SQLSMALLINT ValueType = -1;
SQLSMALLINT ParameterType = -1;
SQLULEN ColumnSize = 0;
SQLSMALLINT DecimalDigits = 0;
SQLPOINTER ParameterValuePtr = 0;
SQLLEN BufferLength = 0;
SQLLEN* StrLen_or_IndPtr = 0;
switch (g_ColInfoArray[c].type) {
case CI_type::CI_int:
ValueType = SQL_C_DEFAULT;
ParameterType = SQL_INTEGER;
break;
case CI_type::CI_char16:
ValueType = SQL_C_CHAR;
ParameterType = SQL_CHAR;
ColumnSize = 16;
break;
case CI_type::CI_datetime:
ValueType = SQL_C_TYPE_TIMESTAMP;
ParameterType = SQL_TYPE_TIMESTAMP;
ColumnSize = 23;
DecimalDigits = 3;
break;
case CI_type::CI_text:
ValueType = SQL_C_CHAR;
ParameterType = SQL_LONGVARCHAR;
ColumnSize = 100;
break;
case CI_type::CI_money:
ValueType = SQL_C_CHAR;
ParameterType = SQL_VARCHAR;
break;
case CI_type::CI_varchar171:
ValueType = SQL_C_CHAR;
ParameterType = SQL_CHAR;
ColumnSize = 171;
break;
}
// (c == 0) is Primary Key
int value = 5;
if (c == 0) value = skey++;
if (c == 0 || c == nn) {
switch (g_ColInfoArray[c].type) {
case CI_type::CI_int:
{
SQLINTEGER* p = new SQLINTEGER;
mem[c] = p;
*p = value;
}
break;
case CI_type::CI_char16:
{
BufferLength = 16;
char* p = new char[BufferLength + 1];
mem[c] = p;
strcpy(p, "five");
}
break;
case CI_type::CI_datetime:
{
SQL_TIMESTAMP_STRUCT* p = new SQL_TIMESTAMP_STRUCT;
mem[c] = p;
p->year = 2021;
p->month = 5;
p->day = 27;
p->hour = 20;
p->minute = 21;
p->second = 0;
p->fraction = 0;
}
break;
case CI_type::CI_text:
{
BufferLength = 100;
char* p = new char[BufferLength + 1];
mem[c] = p;
strcpy(p, "five");
}
break;
case CI_type::CI_money:
{
BufferLength = 10;
char* p = new char[BufferLength + 1];
mem[c] = p;
strcpy(p, "555.55");
}
break;
case CI_type::CI_varchar171:
{
BufferLength = 171;
char* p = new char[BufferLength + 1];
mem[c] = p;
strcpy(p, "five");
}
break;
}
ParameterValuePtr = mem[c];
}
else {
SQLLEN* p = new SQLLEN;
mem[c] = p;
*p = SQL_NULL_DATA;
StrLen_or_IndPtr = p;
}
rc = SQLBindParameter(StatementHandle, ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, DecimalDigits, ParameterValuePtr, BufferLength, StrLen_or_IndPtr);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter");
} // for
rc = SQLExecDirect(hstmt, (SQLCHAR*)"{call [sp_TTemporalAccounts_ins](?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}", SQL_NTS);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLExecDirect()");
for (int c = 0; c < N_COL; c++) {
delete mem[c];
}
} // for
} // for
#if 0
// Table has (int, datetime, text, datetime)
// Did not fail like expected
SQLINTEGER null = SQL_NULL_DATA;
// rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 23, 3, 0, 0, &null);
// rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter()");
// rc = SQLExecDirect(hstmt, (SQLCHAR*)"{call [sp_TTemporalAccounts_ins](?)}", SQL_NTS);
// rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLExecDirect()");
// SQLcancel
SQL_TIMESTAMP_STRUCT ts;
ts.year = 2021;
ts.month = 5;
ts.day = 27;
ts.hour = 20;
ts.minute = 21;
ts.second = 0;
ts.fraction = 0;
rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 23, 3, &ts, 0, 0);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter()");
rc = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR, 23, 3, 0, 0, &null);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter()");
rc = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 23, 3, 0, 0, &null);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter()");
rc = SQLExecDirect(hstmt, (SQLCHAR*)"{call [sp_TTemporalAccounts_ins](?,?,?)}", SQL_NTS);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLExecDirect()");
rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 23, 3, 0, 0, &null);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter()");
rc = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR, 23, 3, 0, 0, &null);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter()");
rc = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, 23, 3, &ts, 0, 0);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLBindParameter()");
rc = SQLExecDirect(hstmt, (SQLCHAR*)"{call [sp_TTemporalAccounts_ins](?,?,?)}", SQL_NTS);
rc = BBCheckForInfo(SQL_HANDLE_STMT, hstmt, rc, "SQLExecDirect()");
// Bindings remain in effect until the application calls SQLBindParameter again,
// calls SQLFreeStmt with the SQL_RESET_PARAMS option,
// or calls SQLSetDescField to set the SQL_DESC_COUNT header field of the APD to 0
#endif
}
Is it possible to create a multiclient pipe? With one server and multiple clients? From the official documentation I have read that " A pipe server could use a single pipe instance to connect with multiple pipe clients by connecting to and disconnecting from each client in sequence, but performance would be poor" ( https://msdn.microsoft.com/en-us/library/windows/desktop/aa365594(v=vs.85).aspx ). Is this behaviour standard (can be done using some flags or something like that) or I have to implement this behaviour by myself? I have written a test with multiply clients, but when I trying to connect by the second client, I got error STATUS_PIPE_NOT_AVAILABLE.
There is my code, it quite big, but functions test_multiple_client and test_multiple_client2 is the same
void test_mutiple_client( PVOID arg )
{
OBJECT_ATTRIBUTES oa;
UNICODE_STRING us;
IO_STATUS_BLOCK iosb;
HANDLE thread = 0, event = 0, client = 0;
NTSTATUS r;
CLIENT_ID id;
LARGE_INTEGER timeout;
ULONG i;
us.Buffer = pipename;
us.Length = sizeof pipename - 2;
us.MaximumLength = us.Length;
oa.Length = sizeof oa;
oa.RootDirectory = 0;
oa.ObjectName = &us;
oa.Attributes = OBJ_CASE_INSENSITIVE;
oa.SecurityDescriptor = 0;
oa.SecurityQualityOfService = 0;
r = NtCreateEvent( &event, EVENT_ALL_ACCESS, NULL, NotificationEvent, 0 );
ok(r == STATUS_SUCCESS, "return wrong (%08lx)\n", r);
r = NtOpenFile( &client, GENERIC_READ | GENERIC_WRITE, &oa, &iosb, FILE_SHARE_READ|FILE_SHARE_WRITE, 0 );
ok(r == STATUS_SUCCESS, "return wrong %08lx\n", r);
dprintf("mc: client1 pipe created\n");
int thread_id = __sync_add_and_fetch(&g__clientsCounter, 1);
while (g__clientsCounter != 2);
dprintf("thread %d stated\n", thread_id);
r = NtReadFile( client, event, 0, 0, &iosb, &i, sizeof i, 0, 0 );
if (r == STATUS_PENDING)
r = NtWaitForSingleObject( event, TRUE, 0 );
ok (r == STATUS_SUCCESS, "read %ld returned %08lx\n", i, r);
ok (i == 13, "lol?????");
r = NtClose( client );
ok( r == STATUS_SUCCESS, "return wrong %08lx\n", r);
}
void test_mutiple_client2( PVOID arg )
{
OBJECT_ATTRIBUTES oa;
UNICODE_STRING us;
IO_STATUS_BLOCK iosb;
HANDLE thread = 0, event = 0, client = 0;
NTSTATUS r;
CLIENT_ID id;
LARGE_INTEGER timeout;
ULONG i;
us.Buffer = pipename;
us.Length = sizeof pipename - 2;
us.MaximumLength = us.Length;
oa.Length = sizeof oa;
oa.RootDirectory = 0;
oa.ObjectName = &us;
oa.Attributes = OBJ_CASE_INSENSITIVE;
oa.SecurityDescriptor = 0;
oa.SecurityQualityOfService = 0;
r = NtCreateEvent( &event, EVENT_ALL_ACCESS, NULL, NotificationEvent, 0 );
ok(r == STATUS_SUCCESS, "return wrong (%08lx)\n", r);
r = NtOpenFile( &client, GENERIC_READ | GENERIC_WRITE, &oa, &iosb, FILE_SHARE_READ|FILE_SHARE_WRITE, 0 );
ok(r == STATUS_SUCCESS, "return wrong %08lx\n", r);
dprintf("mc: client1 pipe created\n");
int thread_id = __sync_add_and_fetch(&g__clientsCounter, 1);
while (g__clientsCounter != 2);
dprintf("thread %d stated\n", thread_id);
r = NtReadFile( client, event, 0, 0, &iosb, &i, sizeof i, 0, 0 );
if (r == STATUS_PENDING)
r = NtWaitForSingleObject( event, TRUE, 0 );
ok (r == STATUS_SUCCESS, "read %ld returned %08lx\n", i, r);
ok (i == 13, "lol?????");
r = NtClose( client );
ok( r == STATUS_SUCCESS, "return wrong %08lx\n", r);
}
void test_multiple_connections( )
{
OBJECT_ATTRIBUTES oa;
UNICODE_STRING us;
IO_STATUS_BLOCK iosb;
HANDLE pipe = 0, thread = 0, event = 0;
NTSTATUS r;
CLIENT_ID id;
LARGE_INTEGER timeout;
ULONG i;
us.Buffer = pipename;
us.Length = sizeof pipename - 2;
us.MaximumLength = us.Length;
oa.Length = sizeof oa;
oa.RootDirectory = 0;
oa.ObjectName = &us;
oa.Attributes = OBJ_CASE_INSENSITIVE;
oa.SecurityDescriptor = 0;
oa.SecurityQualityOfService = 0;
timeout.QuadPart = -10000LL;
r = NtCreateNamedPipeFile( &pipe, GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE,
&oa, &iosb, FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN_IF, 0, TRUE,
TRUE, FALSE, /*Unlimited*/ -1, 0, 0, &timeout );
ok( r == STATUS_SUCCESS, "return wrong %08lx\n", r);
dprintf("mc: server pipe created\n");
r = RtlCreateUserThread( NtCurrentProcess(), NULL, FALSE,
NULL, 0, 0, &test_mutiple_client, NULL, &thread, &id );
ok( r == STATUS_SUCCESS, "failed to create thread\n" );
r = RtlCreateUserThread( NtCurrentProcess(), NULL, FALSE,
NULL, 0, 0, &test_mutiple_client2, NULL, &thread, &id );
ok( r == STATUS_SUCCESS, "failed to create thread\n" );
r = NtCreateEvent( &event, EVENT_ALL_ACCESS, NULL, NotificationEvent, 0 );
ok(r == STATUS_SUCCESS, "return wrong (%08lx)\n", r);
r = NtFsControlFile( pipe, event, 0, 0, &iosb, FSCTL_PIPE_LISTEN, 0, 0, 0, 0 );
if (r == STATUS_PENDING) {
dprintf("mc: pending\n");
r = NtWaitForSingleObject( event, TRUE, 0 );
}
ok( r == STATUS_SUCCESS, "failed to listen %08lx\n", r );
dprintf("mc: server pipe listen\n");
i = 13;
while (g__clientsCounter != 2);
dprintf("server started\n");
r = NtWriteFile( pipe, event, 0, 0, &iosb, &i, sizeof i, 0, 0 );
if (r == STATUS_PENDING)
r = NtWaitForSingleObject( event, TRUE, 0 );
ok (r == STATUS_SUCCESS, "write %ld returned %08lx\n", i, r);
dprintf("server write data\n");
r = NtClose( pipe );
ok( r == STATUS_SUCCESS, "return wrong %08lx\n", r);
}
The output is
mc: server pipe created
mc: pending
mc: server pipe listen
mc: client1 pipe created
478: return wrong c00000ac
mc: client1 pipe created
thread 2 stated
488: read 2013057864 returned c0000008
489: lol?????492: return wrong c0000008
server started
thread 1 stated
server write data
4 failed, 38 passed
I also have seen an answer of stack ( Number of Clients that can connect to a Named Pipe ) where mentioned that windows pipes can hold up to 256 clients