Skip navigation

OK, here it is.

Pure torture.


#define _CRT_SECURE_NO_DEPRECATE

// Before running this program, make sure you have a
// mysql32 dsn set up to connect to the 'test' database.

// Run these queries:
/*
create table users( id int, name varchar( 255 ) );

insert into users( id, name ) values ( 1, 'bob' ), ( 2, 'john' ) ;

select * from users ;
*/







#include <windows.h>
#include <stdio.h>
#include <stddef.h>     // for offsetof()
#include <cguid.h>

#include <oledb.h>
#include <oledberr.h> 
#include <msdaguid.h>
#include <msdasql.h>

#include <comdef.h>     // for _com_error()

#include <vector>
using namespace std ;


#define FIELD_NAME_LEN 255

struct User
{
  int id ;
  
  // Because the field is a varchar(255), we
  // represent that here as a char array with
  // 255 bytes maximum.
  char name[ FIELD_NAME_LEN ] ;

  User()
  {
    id = 1 ;
    strcpy( name, "unnamed user" ) ;
  }

  User( int i_id, char * i_name )
  {
    id = i_id ;
    strcpy( name, i_name ) ;
  }
} ;











///
/// Dump an error to the console,
/// including any information we can
/// get from the system about
/// the specific error.
///
void DumpError( HRESULT hr )
{
  printf( "HRESULT: %d\n", hr ) ;

  _com_error err( hr ) ;
  BSTR bstr = err.Description() ;
  printf( "Description: %s\n", bstr );
  wprintf( L"_com_error.ErrorMessage(): %s\n", err.ErrorMessage()  ) ;

  IErrorInfo * errorInfo ;
  GetErrorInfo( 0, &errorInfo );
  if( errorInfo )
  {
    BSTR description;
    errorInfo->GetDescription( & description );

    // description will be a more descriptive error message than just formatting the 
    // HRESULT because it is set by the COM server code at the point of the error

    wprintf( L"\nIErrorInfo says:  %s\n", description ) ;
  }

  printf("\n");
}



/// Its very important to CHECK ALL return codes.
/// Its a real ass-biter when something goes wrong
/// and the notification is hidden because you
/// didn't check the HRESULT of every command.
bool CHECK( HRESULT hr, char * msg, bool printSucceededMsg=false, bool quit=true )
{
  if( SUCCEEDED( hr ) )
  {
    if( printSucceededMsg )  printf( "%s succeeded\n", msg ) ;

    return true ;
  }
  else
  {
    printf( "%s has FAILED!!\n", msg ) ;

    DumpError( hr ) ;

    if( quit )  FatalAppExitA( 0, msg ) ;

    return false ;
  }
}




/// Gets you an ICommandText interface to
/// the database connected to in db.
/// ICommandText's let you send SQL queries
/// to the database.
ICommandText * GetCommand( IDBInitialize * dbInit )
{
  #pragma region talk about QueryInterface
  // QueryInterface is a funky method.

  // Basically you use it to "ask" the COM object you
  // are working with here WHETHER OR NOT IT SUPPORTS
  // ("implements") a specific interface.

  // To quote an excellent MSDN article:
  // "If an object supports an interface, it supports
  // all of the methods within that interface."
  //    http://msdn.microsoft.com/en-us/library/ms810892.aspx

  // If you're curious, take a look at the
  // "sampprov" project that comes with
  // the Windows SDK sample set
  // C:\Program Files\Microsoft SDKs\Windows\v6.1\Samples\dataaccess\oledb\sampprov

  // Therein, an implementation of the QueryInterface
  // method lies:

  /*
STDMETHODIMP CCommand::QueryInterface
(
	REFIID riid,				//@parm IN | Interface ID of the interface being queried for. 
	LPVOID * ppv				//@parm OUT | Pointer to interface that was instantiated      
)
{
	if( ppv == NULL )
		return ResultFromScode(E_INVALIDARG);

	//	This is the non-delegating IUnknown implementation
	if( riid == IID_IAccessor )
		*ppv = (LPVOID)m_pIAccessor;
	else if( riid == IID_ICommand )
		*ppv = (LPVOID)m_pICommandText;
	else if( riid == IID_ICommandText )
		*ppv = (LPVOID)m_pICommandText;
	else if( riid == IID_ICommandProperties )
		*ppv = (LPVOID)m_pICommandProperties;
	else if( riid == IID_IColumnsInfo )
		*ppv = (LPVOID)m_pIColumnsInfo;
	else if( riid == IID_IConvertType )
		*ppv = (LPVOID)m_pIConvertType;
	else if( riid == IID_IUnknown )
		*ppv = (LPVOID)this;
	else
		*ppv = NULL;
}
*/
  // OK Isn't that COOL?  All what happens is,
  // QueryInterface looks to see if "riid"
  // is one of the interface types this object
  // supports.

  // If it DOES support the interface you're
  // asking for, then it just points "ppv" to
  // its local member instance of that class.

  // If the object DOES NOT support the interface
  // you're querying for, then you get a null pointer back.

  // ISN'T THAT SIMPLE?  Its a lot simpler internally
  // than I initially thought.  Basically QueryInterface
  // is a Get_* method, only its very very generic.

  // Nothing is created in a QueryInterface method.
  // You get something that already exists within the
  // object.  QueryInterface lets you simply access it.

  // Also note that as a RULE, once a COM interface
  // is published, YOU CANNOT ADD OR REMOVE METHODS
  // FROM THAT INTERFACE.  Instead, you have to
  // define a NEW interface (hence why DirectX has
  // IDirect3D9, IDirect3D7.. etc.)
  #pragma endregion

  // So now, I ultimately want a COMMAND interface,

  // ICommandText <- IDBCreateCommand <- IDBCreateSession <- IDBInitialize
  // cmdText      <- cmdFactory       <- sessionFactory   <- dbInit
  // In words:
  // 1.  IDBInitialize spawns an IDBCreateSession
  // 2.  That IDBCreateSession is used to create an IDBCreateCommand
  // 3.  That IDBCreateCommand is used to create an ICommandText
  // 4.  The ICommandText is returned

  IDBCreateSession * sessionFactory ;
  HRESULT hr = dbInit->QueryInterface(

    IID_IDBCreateSession,      // I want an IID_IDBCreateSession interface.. which
    // I shall use to MAKE a session.

    (void**)&sessionFactory // and put it into sessionFactory variable.
  
  ) ;
  
  CHECK( hr, "creating session factory" ) ;
  
  

  // Get a command factory out of the session factory
  // We need the command factory to create ICommandText's,
  // ICommandText will hold our SQL queries eventually.
  IDBCreateCommand * cmdFactory ;
  CHECK( sessionFactory->CreateSession( NULL, IID_IDBCreateCommand, (IUnknown**)&cmdFactory ), "CreateSession" ) ;

  // Then throw away the session creator object.
  CHECK( sessionFactory->Release(), "releasing the session factory" ) ;

  // Create the command, saving in cmdText.
  // ICommandText interface contains text of an SQL query, eventually
  ICommandText * cmdText ;
  CHECK( cmdFactory->CreateCommand( NULL, IID_ICommandText, (IUnknown**) &cmdText ), "cmdFactory->CreateCommand()" ) ;
  CHECK( cmdFactory->Release(), "cmdFactory->Release()" ) ;
  

  return cmdText ;
}











///
/// Execute a prepared INSERT statement with parameters.
///
void runInsertWithParameters( IDBInitialize * db ) 
{

  //////////
  // // DBPARAMINFO.
  // This array of structs
  // will tell OLE DB the DATATYPE and NAME of the
  // columns of the table we are running our
  // prepared INSERT statement against.

  vector<DBPARAMBINDINFO> bindInfo ;
  
  // Standard type name | Type indicator table
  // contains "DBTYPE_I4" => DBTYPE_I4 mappings
  // Basically you just quote all of them, except for example
  // "DBTYPE_CHAR" => DBTYPE_STR, and
  // "DBTYPE_VARCHAR" => DBTYPE_STR as well.
  DBPARAMBINDINFO firstColumnBindInfo = { 0 } ;
  firstColumnBindInfo.pwszDataSourceType = OLESTR( "DBTYPE_I4" ) ;  // !!CAN ALSO BE "int"
  firstColumnBindInfo.pwszName = NULL ;   // The name of the __parameter__
  // Most of the time this should be NULL.
  // DO NOT SET THIS to OLESTR( "id" ),
  // or OLESTR( "@id" ), or something like this.
  // MySQL doesn't seem to support named parameters.
  // Named parameters look like this:
  //   insert into tablename (col1, col2) values ( :param1, :param2 )
  // vs UNNAMED parameters:
  //   insert into tablename (col1, col2) values ( ?, ? )
  // We're using UNNAMED parameters here.  Don't
  // mistaken this field for the name of the COLUMN
  // in the table.  Because its not that.  EVEN THOUGH
  // the MSDN example DOES get this wrong.  See this post.
  firstColumnBindInfo.ulParamSize = 4 ;   // size in bytes (obviously 4 bytes for an I4 (int-4) field)
  firstColumnBindInfo.dwFlags = DBPARAMFLAGS_ISINPUT | DBPARAMFLAGS_ISSIGNED  ; // http://msdn.microsoft.com/en-us/library/ms714917(VS.85).aspx
  firstColumnBindInfo.bPrecision = 11 ;  // bPrecision is the maximum number of digits, expressed in base 10
  firstColumnBindInfo.bScale = 0 ;

  bindInfo.push_back( firstColumnBindInfo ) ;


  DBPARAMBINDINFO secondColumnBindInfo = { 0 } ;
  secondColumnBindInfo.pwszDataSourceType = OLESTR( "DBTYPE_VARCHAR" ) ; // !!can also be "varchar(255)"
  //secondColumnBindInfo.pwszName = NULL ;
  secondColumnBindInfo.ulParamSize = FIELD_NAME_LEN ;
  secondColumnBindInfo.dwFlags = DBPARAMFLAGS_ISINPUT  ;
  secondColumnBindInfo.bPrecision = 0 ;
  secondColumnBindInfo.bScale = 0 ;

  bindInfo.push_back( secondColumnBindInfo ) ;



  
  // now, cmdText is the variable we're working with.
  // The command requires the actual text
  // as well as an indicator of its language.
  ICommandText * cmdText = GetCommand( db ) ;
  
  // Set the SQL query that will run.
  CHECK( cmdText->SetCommandText( DBGUID_DBSQL, OLESTR("insert into users ( id, name ) values (?, ?)") ), "SetCommandText" ) ;

  // Now go down into the cmdWithParams and
  // Set its ParameterInfo with the BINDINFO
  // that we just specified above.
  ICommandWithParameters * cmdWithParams ;
  CHECK( cmdText->QueryInterface( IID_ICommandWithParameters, (void**)&cmdWithParams ), "get cmdWithParams" ) ;



  // the ORDER that parameter infomation will come in inside bindInfo
  ULONG ordinals[ 2 ] = { 1, 2 } ;
  CHECK( cmdWithParams->SetParameterInfo( bindInfo.size(), ordinals, &bindInfo[ 0 ] ), "SetParameterInfo" ) ;
  CHECK( cmdWithParams->Release(), "cmdWithParams release" ) ;

  
  

  // Prepare the command.
  // MSDN:  This optional interface encapsulates command optimization,
  // a separation of compile time and run time, as found in traditional
  // relational database systems. The result of this optimization is
  // a command execution plan.
  // http://msdn.microsoft.com/en-us/library/ms713621(VS.85).aspx

  ICommandPrepare * cmdPrepare ;
  CHECK( cmdText->QueryInterface( IID_ICommandPrepare, (void**)&cmdPrepare ), "Get ICommandPrepare interface" ) ;
  CHECK( cmdPrepare->Prepare( 0 ), "Command preparation" ) ;
  CHECK( cmdPrepare->Release(), "ICommandPrepare release" ) ;



  


  // Create parameter accessors.
  #pragma region create parameter accessors
  
  IAccessor * accessor ;
  CHECK( cmdPrepare->QueryInterface( IID_IAccessor, (void**)&accessor ), "Getting IAccessor interface" ) ;

  // DBBINDING
  vector<DBBINDING> bindings ;
  
  DBBINDING firstColumnBinding = { 0 } ;
  firstColumnBinding.iOrdinal = 1 ;
  firstColumnBinding.dwPart = DBPART_VALUE ;
  firstColumnBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED ;
  firstColumnBinding.eParamIO = DBPARAMIO_INPUT ;
  firstColumnBinding.cbMaxLen = 4 ;
  
  firstColumnBinding.wType = DBTYPE_I4 ;
  firstColumnBinding.obValue = offsetof( User, id ) ;  // get byte offset of id member
  printf( "offset of id field is %d\n", firstColumnBinding.obValue ) ;

  bindings.push_back( firstColumnBinding ) ;

  DBBINDING secondColumnBinding = { 0 } ; 
  secondColumnBinding.iOrdinal = 2 ;
  secondColumnBinding.dwPart = DBPART_VALUE ;
  secondColumnBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED ;
  secondColumnBinding.eParamIO = DBPARAMIO_INPUT ;
  secondColumnBinding.cbMaxLen = FIELD_NAME_LEN ;
  
  secondColumnBinding.wType = DBTYPE_STR ;
  secondColumnBinding.obValue = offsetof( User, name ); // get byte offset of name member.
  printf( "offset of name field is %d\n", secondColumnBinding.obValue ) ;

  bindings.push_back( secondColumnBinding ) ;

  // used to get information about the validity of our binding attempt
  vector<DBBINDSTATUS> rgStatus ;
  rgStatus.resize( 2 ) ;
  


  // The hAccessor will tie the BINDING information
  // to the actual dataset used.
  HACCESSOR hAccessor ;  
  printf( "%d\n", sizeof( User ) ) ;
  
  HRESULT hr = accessor->CreateAccessor(
    
    DBACCESSOR_PARAMETERDATA,    // Accessor that will be used to specify parameter data
    
    bindings.size(),    // Number of parameters being bound
    
    &bindings[0],       // Structure containing bind information
    
    sizeof( User ), // Size of parameter structure.  This MUST be a FIXED SIZE,
    // so you cannot use a generic char* pointer in User that points to
    // a variable length string (must be predetermined size, as it is
    // char[FIELD_NAME_LEN] in this example.)

    &hAccessor,     // will contain accessor handle after this function call

    &rgStatus[0]        // will contain information about binding validity
    // after this function call is complete
  
  ) ;

  if( !CHECK( hr, "Parameter accessor creation" ) )
  {
    printf( "binding status validity: %d %d (0 means OK)\n", rgStatus[ 0 ], rgStatus[ 1 ] ) ;
    puts(
      "DBBINDSTATUS_OK = 0,\n"
	    "DBBINDSTATUS_BADORDINAL = 1,\n"
	    "DBBINDSTATUS_UNSUPPORTEDCONVERSION = 2,\n"
	    "DBBINDSTATUS_BADBINDINFO = 3,\n"
	    "DBBINDSTATUS_BADSTORAGEFLAGS = 4,\n"
	    "DBBINDSTATUS_NOINTERFACE = 5,\n"
	    "DBBINDSTATUS_MULTIPLESTORAGE = 6\n" ) ;
  }
  
  
  #pragma endregion


  #pragma region create data and insert it.
  // Create the data to insert and put it
  // into a struct.
  vector<User> users ;

  User user1( 55, "bobobo" ) ;
  users.push_back( user1 ) ;

  User user2( 56, "waniel" ) ;
  users.push_back( user2 ) ;

  
  
  // ICommand::Execute page, DBPARAMS is in it
  DBPARAMS dbParams;
  
  // pData is a pointer to an array of
  // data that contains data in the order
  // described by bindInfo.
  dbParams.pData = &users[ 0 ] ;   // the data to insert.
  dbParams.cParamSets = users.size() ;  // Number of sets of parameters
  dbParams.hAccessor = hAccessor ; // Accessor to the parameters.  tells
  // dbParams how the byte array of data
  // in pData is divided up.

  // Execute the command.
  long cRowsAffected ;
  hr = cmdText->Execute( NULL, IID_NULL, &dbParams, &cRowsAffected, NULL ) ;
  
  CHECK( hr, "Execute for the insert statement" ) ;
  printf( "%d rows inserted.\n", cRowsAffected ) ;
  

  CHECK( accessor->ReleaseAccessor( hAccessor, NULL ), "Accessor ReleaseAccessor" ) ;
  CHECK( accessor->Release(), "Release Accessor" ) ;
  CHECK( cmdText->Release(), "Release commandText" ) ;
  #pragma endregion

  
}














///
///  Delete rows just added using simple execution.
///
void myDelete( IDBInitialize*  pIDBInitialize ) 
{
  ICommandText* pICommandText = GetCommand( pIDBInitialize ) ;


  // Set the command text for first delete statement and execute
  // the command
  pICommandText->SetCommandText(
    
    DBGUID_DBSQL,
    OLESTR( "delete from users where id=6" )
    
  ) ;
  
  long cRowsAffected ;
  pICommandText->Execute( NULL, IID_NULL, NULL, &cRowsAffected, NULL ) ;

  printf( "%d rows deleted.\n", cRowsAffected ) ;

  // Do it again.
  pICommandText->SetCommandText(
    
    DBGUID_DBSQL,
    OLESTR( "delete from users where id=7" )
  
  ) ;

  pICommandText->Execute(NULL, IID_NULL, NULL, &cRowsAffected, NULL);

  printf("%d rows deleted.\n", cRowsAffected);

  pICommandText->Release();

  return;
}












void SetBSTRProperty( DBPROP & props, DWORD propertyId, OLECHAR * stringValue )
{
  props.dwPropertyID = propertyId ;
  props.vValue.vt = VT_BSTR ;
  props.vValue.bstrVal = SysAllocString( stringValue ) ;
}




























DBPROP preinitDBPROP()
{
  DBPROP prop ;
  VariantInit( &prop.vValue ) ; // equivalent: prop.vValue.vt = VT_EMPTY ;
  prop.dwOptions = DBPROPOPTIONS_REQUIRED ;
  prop.colid = DB_NULLID ;
  return prop ;
}

int main()
{
  // Init OLE and set up the DLLs.
  CoInitialize( NULL ) ;



  // We start with the IDBInitialize interface.
  // The purpose of IDBInitialize is to Initializes a data source object
  IDBInitialize * db = NULL;
  
  

  // Get the task memory allocator.
  // At this point, the original example creates
  // an IMalloc interface.
  // IMalloc can be used to allocate and manage memory.  It doesn't seem to be used in this
  // example, but I'm leaving it here for now.
  IMalloc * iMalloc = NULL;
  CHECK( CoGetMalloc( MEMCTX_TASK, &iMalloc ), "GetMalloc" ) ;
  




  #pragma region Initialize the data source
  
  // Create an instance of the MSDASQL (ODBC) provider.
  // ODBC is for connecting to an sql-based data source
  CHECK( CoCreateInstance( CLSID_MSDASQL, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize, (void**)&db), "starting db" ) ;

  #pragma region talk about variants
  // Set properties for this connection.
  // Declaring the properties for establishing
  // a connection to the database.

  // VARIANT PROPERTY SET.
  // Now HERE is a weird structure you don't see every day.
  // The VARIANT.

  // You might think this data structure is weird.
  // That's because it is.

  // A VARIANT encapsulates ALL the primitive types
  // that are possible in a database.  Look at its
  // struct definition in the header file or on msdn if you wish.
  
  // Notice how it has inside it like EVERY CONCEIVABLE
  // datatype:  DATE*, SHORT*, BYTE*, and even IUnknown.

  // It also has a property VARTYPE.  VARTYPE is set to
  // one of the values in the VARENUM enumeration.
  // So, using a VARIANT as an argument is a way to ANY
  // data type, specifying WHAT TYPE this VARIANT ACTUALLY
  // represents (i.e. what type its internal byte data
  // is to be interpretted as.).

  // MSDN about the vt field:  Contains the type code for the variant,
  //                           which governs how the variant is interpreted.
  #pragma endregion
  
  
  // Initialize common property options.

  // You "have to" call VariantInit() before
  // passing a variant to a function.  What VariantInit does is
  // simply set the vt field to VT_EMPTY.
  
  vector<DBPROP> props ;
  
  // Level of prompting that will be done
  // to complete the connection process.
  DBPROP initPrompt = preinitDBPROP() ;

  initPrompt.dwPropertyID = DBPROP_INIT_PROMPT ;
  initPrompt.vValue.vt = VT_I2 ;      // 2 byte integer (short).
  initPrompt.vValue.iVal = DBPROMPT_NOPROMPT ; 
  
  props.push_back( initPrompt ) ;
  


  
  // Data source name-- using the same one we set up
  // in the ODBC example https://bobobobo.wordpress.com/2009/07/11/working-with-odbc-from-c/
  DBPROP dsn = preinitDBPROP() ;
  SetBSTRProperty( dsn, DBPROP_INIT_DATASOURCE, OLESTR( "mysql32" ) ) ;
  //SetBSTRProperty( dsn, DBPROP_INIT_DATASOURCE, OLESTR( "sqlserver" ) ) ;
  props.push_back( dsn ) ;

  DBPROP username = preinitDBPROP() ;
  SetBSTRProperty( username, DBPROP_AUTH_USERID, OLESTR( "root" ) ) ;   // User ID
  props.push_back( username ) ;

  DBPROP pass = preinitDBPROP() ;
  SetBSTRProperty( pass, DBPROP_AUTH_PASSWORD, OLESTR( "" ) ) ; // Password
  props.push_back( pass ) ;
  


  // Set up the propSet, and
  // link it to the DBPROP array
  // we just created above.
  DBPROPSET propSet ;

  // Now this is an initialization property set.
  propSet.guidPropertySet = DBPROPSET_DBINIT ;

  // tell it the # of properties specified
  propSet.cProperties = props.size() ;

  // And now we assign our properties structure
  propSet.rgProperties = &props[ 0 ] ;

  // Retrieve the IDBProperties interface.
  IDBProperties * dbProperties ;
  db->QueryInterface( IID_IDBProperties, (void**)&dbProperties );
  CHECK( dbProperties->SetProperties( 1, &propSet ), "Set properties" ) ;

  #pragma region free and unload
  // Free the BSTRs that were allocated using SysAllocString()
  SysFreeString( dsn.vValue.bstrVal ) ;
  SysFreeString( username.vValue.bstrVal ) ;
  SysFreeString( pass.vValue.bstrVal ) ;

  CHECK( dbProperties->Release(), "dbprops release" ) ;
  #pragma endregion




  #pragma endregion


  // Now, after we have set the properties, fire up the database by initalizing it
  CHECK( db->Initialize(), "db->Initialize" ) ;
  




  // now set up for batch insertion
  // I want to batch insert.  In order for 
  // Sets of multiple parameters
  // (cParamSets is greater than one) can be specified only if
  // DBPROP_MULTIPLEPARAMSETS is VARIANT_TRUE

  // Note that we CANNOT set this property inline with
  // the previous batch of property state settings because
  // DBPROP_MULTIPLEPARAMSETS belongs to the DBPROPSET_DATASOURCEINFO,
  // and NOT the DBPROPSET_DBINIT set.
  props.clear();

  DBPROP multipleParamsets = preinitDBPROP() ;
  
  multipleParamsets.dwPropertyID = DBPROP_MULTIPLEPARAMSETS ;
  multipleParamsets.vValue.vt = VT_BOOL ;
  multipleParamsets.vValue.boolVal = VARIANT_TRUE ;
  
  props.push_back( multipleParamsets ) ;



  // reset the propset and reuse it
  // Now this is "Data Source Information" property set,
  // which supports setting the DBPROP_MULTIPLEPARAMSETS item
  propSet.guidPropertySet = DBPROPSET_DATASOURCEINFO ;

  // tell it the # of properties specified
  propSet.cProperties = props.size() ;

  // And now we assign our properties structure
  propSet.rgProperties = &props[ 0 ] ;





  // re-get the dbProperties interface.
  db->QueryInterface( IID_IDBProperties, (void**)&dbProperties );
  CHECK( dbProperties->SetProperties( 1, &propSet ), "Set properties 2" ) ;





  CHECK( dbProperties->Release(), "dbprops release 2" ) ;

















  // Execute a prepared statement with parameters.
  runInsertWithParameters( db ) ;
  





  // Delete rows just added.
  myDelete( db ) ;



  
  if( db != NULL )
  {
    db->Uninitialize() ;
    db->Release() ;
  }

  if( iMalloc != NULL )
    iMalloc->Release() ;

  CoUninitialize() ;

  return 0 ;
}









// Glossary:  (Extracted from http://msdn.microsoft.com/en-us/library/ms810892.aspx
// DATA PROVIDER:  A data provider is one that owns data and exposes it in a tabular form.
// Some examples are relational database systems and spreadsheets.

// SERVICE PROVIDER:  A service provider is any OLE DB component
// that does not own the data but encapsulates some service by
// producing and consuming data through OLE DB interfaces.
// Examples are query processors and transaction managers.

// BSTR:  http://msdn.microsoft.com/en-us/library/ms221069.aspx

// The BSTR is a strange animal.  It is like a Pascal string
// in that its LENGTH is stored in the first (4) bytes.
// The character data itself then follows, then there are
// TWO null terminators at the end, regardless of whether
// _UNICODE is #defined or not.

// You're supposed to create BSTR's with SysAllocString()
// and to delete them with SysFreeString().






// Important links and pages.

// ADO faq:  http://support.microsoft.com/kb/183606

// Listing of ALL OLE DB Interfaces (objects that start with I):
// http://msdn.microsoft.com/en-us/library/ms709709(VS.85).aspx

// Listing of ALL OF THE OLE DB Structures And Enumerated Types with LINKS to each
// http://msdn.microsoft.com/en-us/library/ms716934(VS.85).aspx

// Listing of ALL OLE DB PROPERTIES
// http://msdn.microsoft.com/en-us/library/ms723066(VS.85).aspx

// Data type mappings:  maps VARIANT types to OLE DB data types
// (e.g. BOOLEAN => DBTYPE_BOOL)
// http://msdn.microsoft.com/en-us/library/bb231286(VS.85).aspx


// PROPERTIES BY PROPERTY GROUPS:  This is the page that tells
// you which DBPROP_* fits under what DBPROPSET_*
// http://msdn.microsoft.com/en-us/library/ms714404(VS.85).aspx

// Fixed-Length and Variable-Length Data Types
// http://msdn.microsoft.com/en-us/library/ms715955(VS.85).aspx

Download code from esnips (thanks esnips!)

Advertisements

One Comment

  1. mieć pojęcie? – zajął się major. Frodo
    nieoczekiwanie pokiwał makówką. http://www.gregenscene.

    blogspot.co.uk Po policzkach spływały mu łzy, Major jednym
    szarpnięciem zdarł taśmę zaklejającą usta. Frodo za sprawą chwilę szczegółowo oddychał.
    Z
    warg spłynęła strużka farby. – Właściwie?

    – zachęcił Kirpiczew, – Co pragniesz mi
    ogłosić?
    Czuł głód, Czyżby w gruncie rzeczy bez trudu, owszem w lot?
    Bez kołowrotu? Strata. Frodo splunął
    kr.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: