.NET Forum / Visual Studio.NET / Extensibility / July 2006
Accessing sln properties in Common Properties->Debug Source Files
|
|
Thread rating:  |
Don B. - 19 Jun 2006 21:13 GMT I have a native implementation of a debug engine for native code. I want to add support to use the contents of the Common Properties->Debug Source Files page from the solution properties. I don't know how to access these properties.
I had hoped that there would be a way to do it in IVSDebugger[2], but no luck there.
IVsSolution[2] supports GetProperty(), and one of the properties returns a string containing a semicolon-delimited list of the CLSIDs of the solution property pages, but that doesn't really seem like a productive path to follow.
How can I get at this data? thanks --Don
How can I get th
"Gary Chang[MSFT]" - 20 Jun 2006 04:31 GMT Hi Don,
Thank you posting!
This is a quick note to let you know that I am performing research on this issue and will get back to you as soon as possible. I appreciate your patience.
Thanks for your understanding.
Best regards,
Gary Chang Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
"Gary Chang[MSFT]" - 22 Jun 2006 03:20 GMT Hi Don,
Currently I have already forwarded this issue to our corresponding product team, we will update you as we get any results.
Thanks!
Best regards,
Gary Chang Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
"Ed Dore" - 27 Jun 2006 01:03 GMT Hi Don,
I've spent the better part of today digging through the VS sources (to no avail). From the looks of it, there is no programatic access to these properties short of reading them out of the user's .SUO. The default directories (the ones we pre-populate that property page with) are specified as a ';' deliniated list in the registry under the HKLM\SOFTWARE\Microsoft\VisualStudio\8.0\Source directories value.
But the actual list of directories used, is maintained by the debugger, and persisted to the .SUO file. I have some email out to the development team asking to confirm my suspicions regarding the having to read the values out of the .SUO. As soon as I hear back, I'll post back on this thread.
Thanks, Ed Dore [MSFT]
This post is "AS IS" with no warranties, and confers no rights.
Don B. - 28 Jun 2006 22:06 GMT Thanks Ed. Still interested on this end.
> Hi Don, > [quoted text clipped - 14 lines] > > This post is "AS IS" with no warranties, and confers no rights. "Ed Dore" - 12 Jul 2006 04:37 GMT Hi Don,
My apologies for the delayed response. I've been swamped with a large number of support cases the last couple weeks. I was also tripped up by several different problems in trying to solve this, but I was finally able to get this working this evening.
The debugger implements an interface called IVsPersistSolutionOpts that we can query for, off the IVsDebugger service. We then need to redefine a couple of interfaces because the Microsoft.VisualStudio.OLE.Interop assembly has a pretty heinous bug in it's definition of IStream.Write, which prevents us from implementing an object that derives from this interface. At least in this particular instance.
The last parameter to IStream::Write (in the native COM world) is a ULONG* pbWritten. The interop assembly declares this as an "out ulong". The problem is, it's perfectly legal to pass a NULL for this parameter (which unfortunately is exactly what the debugger does when we pass said IStream to it's IVsPersistSolutionOpts::WriteUserOptions.
We can't compile the code unless we make an assignment to the out parameter. But because it's NULL, any attempt to assign a value to it, will cause an exception to be thrown. Consequently, you'll have to redeclare both the IStream and IVsPersistSolutionOpts interfaces, as follows:
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("0000000c-0000-0000-C000-000000000046")] internal interface IStream { // ISequentialStream portion void Read([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1), Out] Byte[] pv, int cb, ulong[] pcbRead); void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Byte[] pv, int cb, ulong[] pcbWritten);
// IStream portion void Seek(Int64 dlibMove, int dwOrigin, IntPtr plibNewPosition); void SetSize(Int64 libNewSize); void CopyTo(IStream pstm, Int64 cb, IntPtr pcbRead, IntPtr pcbWritten); void Commit(int grfCommitFlags); void Revert(); void LockRegion(Int64 libOffset, Int64 cb, int dwLockType); void UnlockRegion(Int64 libOffset, Int64 cb, int dwLockType); void Stat(out Microsoft.VisualStudio.OLE.Interop.STATSTG pstatstg, int grfStatFlag); void Clone(out IStream ppstm); }
// Customized IVsPersistSolutionOpts, so that we can pass our customized IStream to the debugger's // IVsPersistSolutionOpts::WriteUserOptions. The IVsPersistSolutionOpts [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("53BA0F89-24DD-46E1-A7D6-ED24C039FBC4")] internal interface IVsPersistSolutionOpts { void SaveUserOptions([In, MarshalAs(UnmanagedType.Interface)] IVsSolutionPersistence pPersistence); void LoadUserOptions([In, MarshalAs(UnmanagedType.Interface)] IVsSolutionPersistence pPersistence, [In, ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSLOADUSEROPTS")] uint grfLoadOpts); void WriteUserOptions([In, MarshalAs(UnmanagedType.Interface)] IStream pOptionsStream, [In, ComAliasName("Microsoft.VisualStudio.OLE.Interop.LPCOLESTR"), MarshalAs(UnmanagedType.LPWStr)] string pszKey); void ReadUserOptions([In, MarshalAs(UnmanagedType.Interface)] IStream pOptionsStream, [In, ComAliasName("Microsoft.VisualStudio.OLE.Interop.LPCOLESTR"), MarshalAs(UnmanagedType.LPWStr)] string pszKey); }
Note that I've changed last param to both Read and Write to a ulong[], which allows us to check a NULL and readily make the assignment. I could have used an IntPtr, but they we'd have to muck with the Marshal class to return the pcbWritten and pcbRead values. Also, the IVsPersistSolutionOpts methods will now use our custom IStream, instead of the one defined in the interop assembly.
Next, we have to implement an object that supports our new IStream interface, and can store the information when IStream.Write is invoked. I created an object that derived from both the .Net MemoryStream object and our new IStream. The IStream.Write simply calls the MemoryStream.Write which stores the bits in a memory buffer. The ParseBuffer method then pulls both the source directory strings, and the file to ignore strings out of the buffer, and drops them into the member string arrays (sourceDirectories, and ignoredFiles).
// A dangerouly minimal IStream implementation that only supports IStream.Write, in order // to get the "DebuggerFindSource" stream in the .SUO. internal class OptStream : MemoryStream, Microsoft.ReadSUO.IStream { public string[] sourceDirectories; public string[] ignoredFiles;
// Parses the "DebuggerFindSourceStream and pulls out the Solution's Source File Directories, // and Ignored source files, from the .SUO. public void ParseBuffer() { base.Seek(0, SeekOrigin.Begin);
BinaryReader reader = new BinaryReader((Stream)this); System.Text.Encoding encoding = new System.Text.UnicodeEncoding(); // first two dwords are version numbers ulong verDirCache = reader.ReadUInt32(); ulong verStringList = reader.ReadUInt32(); // read Source Directories ulong numStrings = reader.ReadUInt32(); if (numStrings > 0) { sourceDirectories = new string[numStrings]; for (uint i = 0; i < numStrings; i++) { // persisted as unicode strings int numBytes = reader.ReadInt32(); char[] chars = encoding.GetChars(reader.ReadBytes(numBytes)); sourceDirectories[i] = new string(chars).TrimEnd('\0'); } }
// read Ignored Files verStringList = reader.ReadUInt32(); numStrings = reader.ReadUInt32();
if (numStrings > 0) { ignoredFiles = new string[numStrings]; for (uint j = 0; j < numStrings; j++) { // persisted as unicode strings int numBytes = reader.ReadInt32(); char[] chars = encoding.GetChars(reader.ReadBytes(numBytes)); ignoredFiles[j] = new string(chars).TrimEnd('\0'); } } }
#region IStream Members
public void Read(byte[] pv, int cb, ulong[] pcbRead) { throw new System.NotImplementedException("IStream::Read is not implemented."); }
public void Write(byte[] pv, int cb, ulong[] pcbWritten) { base.Write(pv, 0, cb); if (pcbWritten != null) pcbWritten[0] = (ulong)cb; }
public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition) { throw new System.NotImplementedException("IStream::Seek is not implemented."); }
public void SetSize(long libNewSize) { throw new System.NotImplementedException("IStream::SetSize is not implemented."); }
public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) { throw new System.NotImplementedException("IStream::CopyTo is not implemented."); }
public void Commit(int grfCommitFlags) { throw new System.NotImplementedException("IStream::Commit is not implemented."); }
public void Revert() { throw new System.NotImplementedException("IStream::Revert is not implemented."); }
public void LockRegion(long libOffset, long cb, int dwLockType) { throw new System.NotImplementedException("IStream::LockRegion is not implemented."); }
public void UnlockRegion(long libOffset, long cb, int dwLockType) { throw new System.NotImplementedException("IStream::UnlockRegion is not implemented."); }
public void Stat(out Microsoft.VisualStudio.OLE.Interop.STATSTG pstatstg, int grfStatFlag) { throw new System.NotImplementedException("IStream::Stat is not implemented."); }
public void Clone(out IStream ppstm) { throw new System.NotImplementedException("IStream::Clone is not implemented."); }
Finally, we just need to force the debugger to save it's settings to our IStream. For example:
private void OnReadSUO(object sender, EventArgs e) { // retrieve IVsPersistSolutionOpts interface from debugger object IVsPersistSolutionOpts dbgPersistSlnOpts = (IVsPersistSolutionOpts)GetService(typeof(IVsDebugger));
// create a memory based IStream (using System.IO.MemoryStream) OptStream optStream = new OptStream(); dbgPersistSlnOpts.WriteUserOptions((IStream)optStream, "DebuggerFindSource"); optStream.ParseBuffer();
if (optStream.sourceDirectories != null) { Debug.WriteLine("Source Directories:"); foreach (string dir in optStream.sourceDirectories) Debug.WriteLine(dir); }
if (optStream.ignoredFiles != null) { Debug.WriteLine("Ignored Source Files"); foreach (string file in optStream.ignoredFiles) Debug.WriteLine(file); } } }
The above code snippets are from my prototype package, and should probably be sufficient for you to implement a workable solution. However, if you'd like a copy of the prototype package I worked up, just fire me off an email (removing the ".online" from my email address), and I'd be more than happy to provide the entire project.
Sincerely, Ed Dore [MSFT]
This post is 'AS IS' with no warranties and confers no rights.
Don B. - 12 Jul 2006 20:14 GMT Thanks for following through on this, Ed. A couple of questions:
(1) we have a native implementation, so our assumption is that we don't need to worry about the workaround you describe. Correct?
(2) our debug engine is not currently a vs package, and so isn't currently able to do QueryService. Turning it into a vs package is probably not that difficult, but we want to make certain that we don't go down that path if it's breaking some architectural rules, or if it might cause the debug engine launch (or other functions) to break.
thanks --Don B
> Hi Don, > [quoted text clipped - 255 lines] > > This post is 'AS IS' with no warranties and confers no rights. Ed Dore - 13 Jul 2006 02:35 GMT Hi Don,
Shoot, I wish I'd have known that ahead of time :-)
You're correct, you don't need to worry about the interop issue if you're addin/package is native C++ code.
How to you interact with the IDE if you cannot call QueryService? If you're an addin, you can QI the DTE object for IServiceProvider, and call QS for that IVsDebugger interface. Our debugger's IVsPersistSolutionOpts::WriteUserOptions is the only way I could figure out how to get at the "DebuggerFindSource" stream in the .SUO. I don't think there's another way to do that unless you can reverse engineer the entire .SUO, which is next to impossible as many different packages dump stuff in there.
It's been a couple years since I've messed with DE's. Does your engine hold any interfaces on the IDE? I'll have to muck with the TI sample a bit and see if there's any way to pull an IServiceProvider from the IDE.
Sincerely, Ed Dore [MSFT]
This post is 'AS IS' with no warranties, and confers no rights.
Don B. - 13 Jul 2006 17:18 GMT Thanks Ed.
Barring any definitive message telling us "Don't make your DE into a vspackage", we will probably go ahead and make that change to see if it works. We have another service or two we want/need to get at from the DE as well.
thanks again --Don B
> Hi Don, > [quoted text clipped - 20 lines] > > This post is 'AS IS' with no warranties, and confers no rights. "Ed Dore" - 13 Jul 2006 23:01 GMT Hi Don,
If your DE is inproc, you can use ::GetCurrentProcessId() and then find the appropriate instance of the DTE object in the Running Object Table. That might be a viable alternative to implementing a VSIP package. But the package scenario could also be used as well.
The moniker in the ROT will have a format similar to "!VisualStudio.DTE.8.0:xxxx"
where xxxx is the Process ID for the instance(s) of DevEnv.exe running.
Sincerely, Ed Dore [MSFT]
This post is 'AS IS' with no warranties and confers no rights.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|