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
}
I don't know why keep occurring [22] invalid argument in the process.
the source code like this
int MainLoop()
{
struct timeval timeout;
fd_set event;
int n, maxfd, newfd, rc, err_cnt = 0;
CSocket* pSocket;
int chktime, currtime;
char buff[MAX_PACKET_SIZE];
int MaxPosition, CurrPosition = 0;
WmBoard *pWmBoard;
MaxPosition = pCWmBoard->GetMaxPosition();
g_CAgent.Create(g_CsPort, g_CsAddr);
chktime = GetTime();
while(g_Running) {
timeout.tv_sec = 1; /* Second */
timeout.tv_usec = 0; /* micro Second */
pSocket = NULL;
maxfd = g_SockList.GetEventMask(&event);
n = select(maxfd, &event, (fd_set *)0,(fd_set *)0,
(struct timeval *) &timeout);
if(n > 0) {
pSocket = g_SockList.GetEventSock(&event);
if(pSocket != NULL)
{
newfd = pSocket->Accept();
if(newfd > 0)
{
err_cnt = 0;
pWmBoard = GetMinClientCH(MaxPosition);
if (pWmBoard != NULL)
{
rc = SendFD(pWmBoard->PipeFd, (void *)" ", 1, newfd);
if (rc <= 0)
{
char szTmp[128];
sprintf(szTmp,"FDSend Error=[%d]errno=[%d]",
newfd,errno);
g_CAgent.SendMessage("!S001", g_PgmName, szTmp);
g_Log.Write("[%s:%d][E] %s",
g_PgmName, g_Pid, szTmp);
/*
if(errno == EBADF) g_Running = FALSE;
*/
#if 0
if(pWmBoard -> ProcessID > 1)
kill(pWmBoard->ProcessID, SIGTERM);
#endif
pWmBoard->PipeFd = -1;
}
}
else
{
g_Log.Write("[%s:%d][E] client is full. value=%d",
g_PgmName, g_Pid, g_CurrCHPos);
g_Running = FALSE;
}
close(newfd);
}
else
{
g_Log.Write("[%s:%d][E] accept error. errno = [%d]",
g_PgmName, g_Pid, errno);
switch(errno)
{
case EMFILE : break; /* Too many open files */
case ENOENT : /* No such file or directory */
case EAGAIN : /* Try again */
case EINVAL : /* Invalid argument */
case ENOMSG : ; /* No message of desired type */
default :
ResetSocket();
}
}
}
else
g_Log.Write("[%s:%d] GetEventSock is NULL", g_PgmName, g_Pid);
}
else if (n == 0) {
currtime = GetTime();
if( currtime == 0 && chktime != currtime)
{ // ¸ÅÀÏ ÁöÁ¤ÇÑ ½Ã°£ (24½Ã)
g_Log.ReOpen();
g_MaxUser = 0;
ClearTotalTR();
}
chktime = currtime;
}
if (getppid() <= 1){
g_Running = FALSE;
}
if(WhatTime()) SendWmBoardInfo();
}
g_CAgent.Close();
return FALSE;
}
We call the process as WmCL and the WmCL send data to WmCH for connection.
and I got log using strace command the result same as below.
select(19, [16 17 18], NULL, NULL, {1, 0}) = 1 (in [17], left {0, 914250})
accept(17, {sa_family=AF_INET, sin_port=htons(38610), sin_addr=inet_addr("114.122.207.70")}, [16]) = 20
sendmsg(9, {msg_name(0)=NULL, msg_iov(1)=[{" ", 1}], msg_controllen=24, {cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, {20}}, msg_flags=MSG_OOB|MSG_DONTROUTE|MSG_CTRUNC|0x10}, 0) = 1
close(20) = 0
getppid() = 17099
select(19, [16 17 18], NULL, NULL, {1, 0}) = 0 (Timeout)
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=344, ...}) = 0
getppid() = 17099
select(19, [16 17 18], NULL, NULL, {1, 0}) = 0 (Timeout)
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=344, ...}) = 0
getppid() = 17099
select(19, [16 17 18], NULL, NULL, {1, 0}) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=18976, si_status=0, si_utime=1173, si_stime=797} ---
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG, NULL) = 18976
close(7) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=344, ...}) = 0
sendto(19, "m20192.168.103.22 1028360WmCL "..., 161, 0, {sa_family=AF_INET, sin_port=htons(8499), sin_addr=inet_addr("192.168.201.17")}, 16) = 161
write(5, "[10:28:36-000002][WmCL:17236][E]"..., 63) = 63
socketpair(PF_LOCAL, SOCK_STREAM, 0, [7, 20]) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f287d4219f0) = 23210
close(20) = 0
as following the log, 'select' result is '?' but I don't know why the result is '?'....
Can you advise to me to fix this problem?
How do I find the port name for a bluetooth device with a specific device name?
I have this code, which enumerates all bluetooth devices, but doesn't give me their port name:
HBLUETOOTH_DEVICE_FIND founded_device;
BLUETOOTH_DEVICE_INFO device_info;
device_info.dwSize = sizeof(device_info);
BLUETOOTH_DEVICE_SEARCH_PARAMS search_criteria;
search_criteria.dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS);
search_criteria.fReturnAuthenticated = TRUE;
search_criteria.fReturnRemembered = FALSE;
search_criteria.fReturnConnected = FALSE;
search_criteria.fReturnUnknown = FALSE;
search_criteria.fIssueInquiry = FALSE;
search_criteria.cTimeoutMultiplier = 0;
founded_device = BluetoothFindFirstDevice(&search_criteria, &device_info);
if(founded_device == NULL)
return -1;
do {
wstring ws = device_info.szName;
cout << string(ws.begin(), ws.end()) << endl;
} while (BluetoothFindNextDevice(founded_device, &device_info));
And then I have this code, which enumerates all port names but doesn't give me the device name:
DWORD bytesNeeded = 0;
DWORD portCount = 0;
BOOL ret = EnumPorts(nullptr, 2, nullptr, 0, &bytesNeeded, &portCount);
BYTE *ports = new BYTE[bytesNeeded];
if(EnumPorts(nullptr, 2, (LPBYTE)ports, bytesNeeded, &bytesNeeded, &portCount))
{
PORT_INFO_2 *portInfo = (PORT_INFO_2*)ports;
for(DWORD i = 0; i < portCount; ++i)
cout << portInfo[i].pPortName << endl;
}
delete [] ports;
I need to automatically connect to a specific device when my app is started, so I need to either get the port name for the bluetooth device in the first piece of code so I can connect to it, or check each portname in the second piece of code to make sure it's the right device before connecting to it.
How do I do it?
I remember struggling with this in the past.
the only solution i found was to use sockets for communicating with the Bluetooth device using its address, then use the send() and recv() methods for communicating with the device.
// assuming you have the BT device address in blueToothDeviceAddr;
char blueToothDeviceAddr[18];
SOCKET sock;
SOCKADDR_BTH sa = { 0,0,0,0 };
int sa_len = sizeof(sa);
// initialize windows sockets
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD( 2, 0 );
if( WSAStartup( wVersionRequested, &wsaData ) != 0 )
{
ExitProcess(100);
}
// parse the specified Bluetooth address
if( SOCKET_ERROR == WSAStringToAddress( blueToothDeviceAddr, AF_BTH,
NULL, (LPSOCKADDR) &sa, &sa_len ) )
{
ExitProcess(101);
}
// query it for the right port
// create the socket
sock = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if( SOCKET_ERROR == sock )
{
ExitProcess(102);
}
// fill in the rest of the SOCKADDR_BTH struct
GUID pService = (GUID)SerialPortServiceClass_UUID;
SOCKADDR_BTH outSA;
sa.port = SDPGetPort(blueToothDeviceAddr, (LPGUID) &pService,&outSA);
if( sa.port == 0 )
{
ExitProcess(103);
}
// in case you have a pass code you need to register for authetication callback
// look the web for this part
// connect to the device
if( SOCKET_ERROR == connect( sock, (LPSOCKADDR) &outSA, sa_len ) )
{
int lastError = GetLastError();
ExitProcess(105);
}
Under the key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\BTHENUM
you can find a subkey which has a list of keys containing the device address.
Under this last key, you can find a subkey named Device Parameters which finally has the PortName value.
The code is written in C++ with MFC libraries and is tested under Windows XP, 7 and 10. I hope it helps you !
// Returns the outgoing COM port of a bluetooth device given by address
int GetBluetoothCOM( CString sAddr )
{
int iPort = 0;
HKEY hKey_1;
DWORD KeyNdx_1 = 0;
DWORD MaxKeyLen_1;
char KeyNam_1[ MAX_PATH + 1 ];
LONG RetVal_1;
sAddr.MakeUpper();
sAddr.Replace( ":", "" );
sAddr.Replace( " ", "" );
// Enumerate keys under: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\BTHENUM
RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Enum\\BTHENUM", NULL, KEY_READ | KEY_ENUMERATE_SUB_KEYS, &hKey_1 );
while( true )
{
MaxKeyLen_1 = MAX_PATH;
RetVal_1 = RegEnumKeyEx( hKey_1, KeyNdx_1, KeyNam_1, &MaxKeyLen_1, NULL, NULL, NULL, NULL );
if( RetVal_1 == ERROR_NO_MORE_ITEMS )
{
break;
}
if( RetVal_1 == ERROR_SUCCESS )
{
HKEY hKey_2;
DWORD KeyNdx_2 = 0;
DWORD MaxKeyLen_2;
char KeyNam_2[ MAX_PATH + 1 ];
LONG RetVal_2;
// Enumerate subkeys
RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Enum\\BTHENUM\\" + CString( KeyNam_1 ), NULL, KEY_READ | KEY_ENUMERATE_SUB_KEYS, &hKey_2 );
while( true )
{
MaxKeyLen_2 = MAX_PATH;
RetVal_2 = RegEnumKeyEx( hKey_2, KeyNdx_2, KeyNam_2, &MaxKeyLen_2, NULL, NULL, NULL, NULL );
if( RetVal_2 == ERROR_NO_MORE_ITEMS )
{
break;
}
if( RetVal_2 == ERROR_SUCCESS )
{
// Find out if the key name contains &ADDRESS_
CString sKey = "SYSTEM\\CurrentControlSet\\Enum\\BTHENUM\\" + CString( KeyNam_1 ) + "\\" + CString( KeyNam_2 );
sKey.MakeUpper();
if( sKey.Find( "&" + sAddr + "_" ) != -1 )
{
HKEY hKey;
char szPort[ 100 + 1 ];
DWORD dwLen = 100;
// I find out the device
RegOpenKeyEx( HKEY_LOCAL_MACHINE, sKey + "\\Device Parameters", 0, KEY_READ, &hKey );
if( RegQueryValueEx( hKey, "PortName", NULL, NULL, ( LPBYTE ) &szPort, &dwLen ) == ERROR_SUCCESS )
{
szPort[ dwLen ] = 0;
CString sPort = CString( szPort );
sPort.MakeUpper();
if( sPort.Find( "COM" ) == -1 )
{
RegCloseKey( hKey );
continue;
}
sPort.Replace( "COM", "" );
sPort.Trim();
iPort = atoi( sPort.GetBuffer() );
if( iPort != 0 )
{
RegCloseKey( hKey );
break;
}
}
RegCloseKey( hKey );
}
}
++KeyNdx_2;
}
RegCloseKey( hKey_2 );
if( iPort != 0 )
{
break;
}
}
++KeyNdx_1;
};
RegCloseKey( hKey_1 );
return iPort;
}