1 //------------------------------------------------------------------------------
2 // <copyright file="OleDbWrapper.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.OleDb {
12 using System.Data.Common;
13 using System.Data.ProviderBase;
14 using System.Diagnostics;
15 using System.Runtime.CompilerServices;
16 using System.Runtime.ConstrainedExecution;
17 using System.Runtime.InteropServices;
19 // SafeHandle wrapper around 'DataLinks' object which pools the native OLE DB providers.
20 // expect 1 per app-domain
21 sealed internal class OleDbServicesWrapper : WrappedIUnknown {
23 // we expect to store IDataInitialize instance pointer in base.handle
25 // since we only have one DataLinks object, caching the delegate here is valid as long we
26 // maintain an AddRef which is also the lifetime of this class instance
27 private UnsafeNativeMethods.IDataInitializeGetDataSource DangerousIDataInitializeGetDataSource;
29 // DataLinks (the unknown parameter) is created via Activator.CreateInstance outside of the SafeHandle
30 internal OleDbServicesWrapper(object unknown) : base() {
31 if (null != unknown) {
32 RuntimeHelpers.PrepareConstrainedRegions();
34 // store the QI result for IID_IDataInitialize
35 base.handle = Marshal.GetComInterfaceForObject(unknown, typeof(UnsafeNativeMethods.IDataInitialize)); //
37 // native COM rules are the QI result is the 'this' pointer
38 // the pointer stored at that location is the vtable
39 // since IDataInitialize is a public,shipped COM interface, its layout will not change (ever)
40 IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
41 IntPtr method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size); // GetDataSource is the 4'th vtable entry
42 DangerousIDataInitializeGetDataSource = (UnsafeNativeMethods.IDataInitializeGetDataSource)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDataInitializeGetDataSource));
46 internal void GetDataSource(OleDbConnectionString constr, ref DataSourceWrapper datasrcWrapper) {
48 UnsafeNativeMethods.IDataInitializeGetDataSource GetDataSource = DangerousIDataInitializeGetDataSource;
49 bool mustRelease = false;
50 RuntimeHelpers.PrepareConstrainedRegions();
52 DangerousAddRef(ref mustRelease);
54 // this is the string that DataLinks / OLE DB Services will use to create the provider
55 string connectionString = constr.ActualConnectionString;
57 // base.handle is the 'this' pointer for making the COM call to GetDataSource
58 // the datasrcWrapper will store the IID_IDBInitialize pointer
59 // call IDataInitiailze::GetDataSource via the delegate
60 hr = GetDataSource(base.handle, IntPtr.Zero, ODB.CLSCTX_ALL, connectionString, ref ODB.IID_IDBInitialize, ref datasrcWrapper);
67 if (hr < 0) { // ignore infomsg
68 if (OleDbHResult.REGDB_E_CLASSNOTREG == hr) {
69 throw ODB.ProviderUnavailable(constr.Provider, null);
71 Exception e = OleDbConnection.ProcessResults(hr, null, null);
72 Debug.Assert(null != e, "CreateProviderError");
75 else if (datasrcWrapper.IsInvalid) {
76 SafeNativeMethods.Wrapper.ClearErrorInfo();
77 throw ODB.ProviderUnavailable(constr.Provider, null);
79 Debug.Assert(!datasrcWrapper.IsInvalid, "bad DataSource");
83 // SafeHandle wrapper around 'Data Source' object which represents the connection
84 // expect 1 per OleDbConnectionInternal
85 sealed internal class DataSourceWrapper : WrappedIUnknown {
87 // we expect to store IDBInitialize instance pointer in base.handle
89 // construct a DataSourceWrapper and used as a ref parameter to GetDataSource
90 internal DataSourceWrapper() : base() {
93 internal OleDbHResult InitializeAndCreateSession(OleDbConnectionString constr, ref SessionWrapper sessionWrapper) {
95 bool mustRelease = false;
96 IntPtr idbCreateSession = IntPtr.Zero;
97 RuntimeHelpers.PrepareConstrainedRegions();
99 DangerousAddRef(ref mustRelease);
101 // native COM rules are the QI result is the 'this' pointer
102 // the pointer stored at that location is the vtable
103 // since IUnknown is a public,shipped COM interface, its layout will not change (ever)
104 IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
105 IntPtr method = Marshal.ReadIntPtr(vtable, 0);
107 // we cache the QueryInterface delegate to prevent recreating it on every call
108 UnsafeNativeMethods.IUnknownQueryInterface QueryInterface = constr.DangerousDataSourceIUnknownQueryInterface;
110 // since the delegate lifetime is longer than the original instance used to create it
111 // we double check before each usage to verify the delegates function pointer
112 if ((null == QueryInterface) || (method != Marshal.GetFunctionPointerForDelegate(QueryInterface))) {
113 QueryInterface = (UnsafeNativeMethods.IUnknownQueryInterface)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IUnknownQueryInterface));
114 constr.DangerousDataSourceIUnknownQueryInterface = QueryInterface;
117 // native COM rules are the QI result is the 'this' pointer
118 // the pointer stored at that location is the vtable
119 // since IDBInitialize is a public,shipped COM interface, its layout will not change (ever)
120 vtable = Marshal.ReadIntPtr(base.handle, 0);
121 method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size); // Initialize is the 4'th vtable entry
123 // we cache the Initialize delegate to prevent recreating it on every call
124 UnsafeNativeMethods.IDBInitializeInitialize Initialize = constr.DangerousIDBInitializeInitialize;
126 // since the delegate lifetime is longer than the original instance used to create it
127 // we double check before each usage to verify the delegates function pointer
128 if ((null == Initialize) || (method != Marshal.GetFunctionPointerForDelegate(Initialize))) {
129 Initialize = (UnsafeNativeMethods.IDBInitializeInitialize)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBInitializeInitialize));
130 constr.DangerousIDBInitializeInitialize = Initialize;
133 // call IDBInitialize::Initialize via the delegate
134 hr = Initialize(base.handle);
136 // we don't ever expect DB_E_ALREADYINITIALIZED, but since we checked in V1.0 - its propagated along
137 if ((0 <= hr) || (OleDbHResult.DB_E_ALREADYINITIALIZED == hr)) {
139 // call IUnknown::QueryInterface via the delegate
140 hr = (OleDbHResult)QueryInterface(base.handle, ref ODB.IID_IDBCreateSession, ref idbCreateSession);
141 if ((0 <= hr) && (IntPtr.Zero != idbCreateSession)) {
143 // native COM rules are the QI result is the 'this' pointer
144 // the pointer stored at that location is the vtable
145 // since IDBCreateSession is a public,shipped COM interface, its layout will not change (ever)
146 vtable = Marshal.ReadIntPtr(idbCreateSession, 0);
147 method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size); // CreateSession is the 4'th vtable entry
149 UnsafeNativeMethods.IDBCreateSessionCreateSession CreateSession = constr.DangerousIDBCreateSessionCreateSession;
151 // since the delegate lifetime is longer than the original instance used to create it
152 // we double check before each usage to verify the delegates function pointer
153 if ((null == CreateSession) || (method != Marshal.GetFunctionPointerForDelegate(CreateSession))) {
154 CreateSession = (UnsafeNativeMethods.IDBCreateSessionCreateSession)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateSessionCreateSession));
155 constr.DangerousIDBCreateSessionCreateSession = CreateSession;
158 // if I have a delegate for CreateCommand directly ask for IDBCreateCommand
159 if (null != constr.DangerousIDBCreateCommandCreateCommand) {
160 // call IDBCreateSession::CreateSession via the delegate directly for IDBCreateCommand
161 hr = CreateSession(idbCreateSession, IntPtr.Zero, ref ODB.IID_IDBCreateCommand, ref sessionWrapper);
162 if ((0 <= hr) && !sessionWrapper.IsInvalid) {
163 // double check the cached delegate is correct
164 sessionWrapper.VerifyIDBCreateCommand(constr);
168 // otherwise ask for IUnknown (it may be first time usage or IDBCreateCommand not supported)
169 hr = CreateSession(idbCreateSession, IntPtr.Zero, ref ODB.IID_IUnknown, ref sessionWrapper);
170 if ((0 <= hr) && !sessionWrapper.IsInvalid) {
171 // and check support for IDBCreateCommand and create delegate for CreateCommand
172 sessionWrapper.QueryInterfaceIDBCreateCommand(constr);
179 if (IntPtr.Zero != idbCreateSession) {
180 // release the QI for IDBCreateSession
181 Marshal.Release(idbCreateSession);
184 // release the AddRef on DataLinks
191 internal IDBInfoWrapper IDBInfo(OleDbConnectionInternal connection) {
192 Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|datasource> %d#, IDBInfo\n", connection.ObjectID);
193 return new IDBInfoWrapper(ComWrapper());
195 internal IDBPropertiesWrapper IDBProperties(OleDbConnectionInternal connection) {
196 Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|datasource> %d#, IDBProperties\n", connection.ObjectID);
197 return new IDBPropertiesWrapper(ComWrapper());
202 // SafeHandle wrapper around 'Session' object which represents the session on the connection
203 // expect 1 per OleDbConnectionInternal
204 sealed internal class SessionWrapper : WrappedIUnknown {
206 // base.handle will either reference the IUnknown interface or IDBCreateCommand interface
207 // if OleDbConnectionString.DangerousIDBCreateCommandCreateCommand exists
208 // the CreateSession call will ask directly for the optional IDBCreateCommand
209 // otherwise it is the first call or known that IDBCreateCommand isn't supported
211 // we cache the DangerousIDBCreateCommandCreateCommand (expecting base.handle to be IDBCreateCommand)
212 // since we maintain an AddRef on IDBCreateCommand it is safe to use the delegate without rechecking its function pointer
213 private UnsafeNativeMethods.IDBCreateCommandCreateCommand DangerousIDBCreateCommandCreateCommand;
215 internal SessionWrapper() : base() {
218 // if OleDbConnectionString.DangerousIDBCreateCommandCreateCommand does not exist
219 // this method will be called to query for IDBCreateCommand (and cache that interface pointer)
220 // or it will be known that IDBCreateCommand is not supported
221 internal void QueryInterfaceIDBCreateCommand(OleDbConnectionString constr) {
222 // DangerousAddRef/DangerousRelease are not neccessary here in the current implementation
223 // only used from within OleDbConnectionInternal.ctor->DataSourceWrapper.InitializeAndCreateSession
225 // caching the fact if we have queried for IDBCreateCommand or not
226 // the command object is not supported by all providers, they would use IOpenRowset
227 // SQLBU:446413, SQLHotfix:1138 -- added extra if condition
228 // If constr.HaveQueriedForCreateCommand is false, this is the first time through this method and we need to set up the cache for sure.
229 // If two threads try to set the cache at the same time, everything should be okay. There can be multiple delegates that point to the same unmanaged function.
230 // If constr.HaveQueriedForCreateCommand is true, we have already tried to query for IDBCreateCommand on a previous call to this method, but based on that alone,
231 // we don't know if another thread set the flag, or if the provider really doesn't support commands.
232 // If constr.HaveQueriedForCreateCommand is true and constr.DangerousIDBCreateCommandCreateCommand is not null, that means that another thread has set it after we
233 // determined we needed to call QueryInterfaceIDBCreateCommand -- otherwise we would have called VerifyIDBCreateCommand instead
234 // In that case, we still need to set our local DangerousIDBCreateCommandCreateCommand, so we want to go through the if block even though the cache has been set on constr already
235 if (!constr.HaveQueriedForCreateCommand || (null != constr.DangerousIDBCreateCommandCreateCommand)) {
236 IntPtr idbCreateCommand = IntPtr.Zero;
237 RuntimeHelpers.PrepareConstrainedRegions();
239 // native COM rules are the QI result is the 'this' pointer
240 // the pointer stored at that location is the vtable
241 // since IUnknown is a public,shipped COM interface, its layout will not change (ever)
242 IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
243 IntPtr method = Marshal.ReadIntPtr(vtable, 0);
244 UnsafeNativeMethods.IUnknownQueryInterface QueryInterface = (UnsafeNativeMethods.IUnknownQueryInterface)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IUnknownQueryInterface));
246 int hresult = QueryInterface(base.handle, ref ODB.IID_IDBCreateCommand, ref idbCreateCommand); //
247 if ((0 <= hresult) && (IntPtr.Zero != idbCreateCommand)) {
248 vtable = Marshal.ReadIntPtr(idbCreateCommand, 0);
249 method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);
251 DangerousIDBCreateCommandCreateCommand = (UnsafeNativeMethods.IDBCreateCommandCreateCommand)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateCommandCreateCommand));
252 constr.DangerousIDBCreateCommandCreateCommand = DangerousIDBCreateCommandCreateCommand;
255 // caching the fact that we have queried for IDBCreateCommand
256 constr.HaveQueriedForCreateCommand = true;
259 if (IntPtr.Zero != idbCreateCommand) {
260 IntPtr ptr = base.handle;
261 base.handle = idbCreateCommand;
262 Marshal.Release(ptr);
266 //else if constr.HaveQueriedForCreateCommand is true and constr.DangerousIDBCreateCommandCreateCommand is still null, it means that this provider doesn't support commands
269 internal void VerifyIDBCreateCommand(OleDbConnectionString constr) {
270 // DangerousAddRef/DangerousRelease are not neccessary here in the current implementation
271 // only used from within OleDbConnectionInternal.ctor->DataSourceWrapper.InitializeAndCreateSession
273 Debug.Assert(constr.HaveQueriedForCreateCommand, "expected HaveQueriedForCreateCommand");
274 Debug.Assert(null != constr.DangerousIDBCreateCommandCreateCommand, "expected DangerousIDBCreateCommandCreateCommand");
276 // native COM rules are the QI result is the 'this' pointer
277 // the pointer stored at that location is the vtable
278 // since IDBCreateCommand is a public,shipped COM interface, its layout will not change (ever)
279 IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
280 IntPtr method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);
282 // obtain the cached delegate to be cached on this instance
283 UnsafeNativeMethods.IDBCreateCommandCreateCommand CreateCommand = constr.DangerousIDBCreateCommandCreateCommand;
285 // since the delegate lifetime is longer than the original instance used to create it
286 // we double check before each usage to verify the delegates function pointer
287 if ((null == CreateCommand) || (method != Marshal.GetFunctionPointerForDelegate(CreateCommand))) {
288 CreateCommand = (UnsafeNativeMethods.IDBCreateCommandCreateCommand)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateCommandCreateCommand));
289 constr.DangerousIDBCreateCommandCreateCommand = CreateCommand;
291 // since this instance can be used to create multiple commands
292 // cache it on the class so that the function pointer doesn't have to be validated every time
293 DangerousIDBCreateCommandCreateCommand = CreateCommand;
296 internal OleDbHResult CreateCommand(ref object icommandText) {
297 // if (null == CreateCommand), the IDBCreateCommand isn't supported - aka E_NOINTERFACE
298 OleDbHResult hr = OleDbHResult.E_NOINTERFACE;
299 UnsafeNativeMethods.IDBCreateCommandCreateCommand CreateCommand = DangerousIDBCreateCommandCreateCommand;
300 if (null != CreateCommand) {
301 bool mustRelease = false;
302 RuntimeHelpers.PrepareConstrainedRegions();
304 DangerousAddRef(ref mustRelease);
306 // call IDBCreateCommand::CreateCommand via the delegate directly for IDBCreateCommand
307 hr = CreateCommand(base.handle, IntPtr.Zero, ref ODB.IID_ICommandText, ref icommandText);
318 internal IDBSchemaRowsetWrapper IDBSchemaRowset(OleDbConnectionInternal connection) {
319 Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, IDBSchemaRowset\n", connection.ObjectID);
320 return new IDBSchemaRowsetWrapper(ComWrapper());
323 internal IOpenRowsetWrapper IOpenRowset(OleDbConnectionInternal connection) {
324 Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, IOpenRowset\n", connection.ObjectID);
325 return new IOpenRowsetWrapper(ComWrapper());
328 internal ITransactionJoinWrapper ITransactionJoin(OleDbConnectionInternal connection) {
329 Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, ITransactionJoin\n", connection.ObjectID);
330 return new ITransactionJoinWrapper(ComWrapper());
334 // unable to use generics bacause (unknown as T) doesn't compile
335 internal struct IDBInfoWrapper : IDisposable {
336 // _unknown must be tracked because the _value may not exist,
337 // yet _unknown must still be released
338 private object _unknown;
339 private UnsafeNativeMethods.IDBInfo _value;
341 internal IDBInfoWrapper(object unknown) {
343 _value = (unknown as UnsafeNativeMethods.IDBInfo);
346 internal UnsafeNativeMethods.IDBInfo Value {
352 public void Dispose() {
353 object unknown = _unknown;
356 if (null != unknown) {
357 Marshal.ReleaseComObject(unknown);
362 internal struct IDBPropertiesWrapper : IDisposable {
363 private object _unknown;
364 private UnsafeNativeMethods.IDBProperties _value;
366 internal IDBPropertiesWrapper(object unknown) {
368 _value = (unknown as UnsafeNativeMethods.IDBProperties);
369 Debug.Assert(null != _value, "null IDBProperties");
372 internal UnsafeNativeMethods.IDBProperties Value {
374 Debug.Assert(null != _value, "null IDBProperties");
379 public void Dispose() {
380 object unknown = _unknown;
383 if (null != unknown) {
384 Marshal.ReleaseComObject(unknown);
389 internal struct IDBSchemaRowsetWrapper : IDisposable {
390 private object _unknown;
391 private UnsafeNativeMethods.IDBSchemaRowset _value;
393 internal IDBSchemaRowsetWrapper(object unknown) {
395 _value = (unknown as UnsafeNativeMethods.IDBSchemaRowset);
398 internal UnsafeNativeMethods.IDBSchemaRowset Value {
404 public void Dispose() {
405 object unknown = _unknown;
408 if (null != unknown) {
409 Marshal.ReleaseComObject(unknown);
414 internal struct IOpenRowsetWrapper : IDisposable {
415 private object _unknown;
416 private UnsafeNativeMethods.IOpenRowset _value;
418 internal IOpenRowsetWrapper(object unknown) {
420 _value = (unknown as UnsafeNativeMethods.IOpenRowset);
421 Debug.Assert(null != _value, "null IOpenRowsetWrapper");
424 internal UnsafeNativeMethods.IOpenRowset Value {
426 Debug.Assert(null != _value, "null IDBProperties");
431 public void Dispose() {
432 object unknown = _unknown;
435 if (null != unknown) {
436 Marshal.ReleaseComObject(unknown);
441 internal struct ITransactionJoinWrapper : IDisposable {
442 private object _unknown;
443 private NativeMethods.ITransactionJoin _value;
445 internal ITransactionJoinWrapper(object unknown) {
447 _value = (unknown as NativeMethods.ITransactionJoin);
450 internal NativeMethods.ITransactionJoin Value {
456 public void Dispose() {
457 object unknown = _unknown;
460 if (null != unknown) {
461 Marshal.ReleaseComObject(unknown);