I am working on a project where I need to automate some workflows in Excel, and I have hit a pretty nasty roadblock. In the project, I am using Visual Studio Tools For Office to create a document level add-in. A user uses a ribbon control that is part of this project to automate copying of worksheets from workbooks external to the project. The external workbooks are loaded from SQL blobs and written to disk. The add-in code opens each workbook, copies a worksheet into the add-in workbook, and then closes that external workbook. Typically, the first workbook works just fine, but opening a subsequent workbook will throw an AccessViolationException.
public void AddSheetFromTempFile(string tempfilePath)
{
Sheets sheets = null;
Excel.Workbook workbook = null;
Excel.Workbooks books = null;
try
{
books = this.Application.Workbooks;
//Throws AccessViolationException
workbook = books.Open(tempfilePath, 0, true, 5,
String.Empty, String.Empty, true, XlPlatform.xlWindows,
String.Empty, true, false, 0, true, true, false);
sheets = workbook.Worksheets;
sheets.Copy(After: this.GetLastWorksheet());
workbook.Close(SaveChanges: false);
}
finally
{
if (sheets != null)
{
Marshal.FinalReleaseComObject(sheets);
}
if (workbook != null)
{
Marshal.FinalReleaseComObject(workbook);
}
if (books != null)
{
Marshal.FinalReleaseComObject(books);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
//extension method for getting last worksheet
public static Microsoft.Office.Interop.Excel.Worksheet
GetLastWorksheet(this Microsoft.Office.Tools.Excel.WorkbookBase workbook)
{
int veryHiddenSheets = 0;
foreach(Worksheet sheet in workbook.Worksheets)
{
if(sheet.Visible == XlSheetVisibility.xlSheetVeryHidden)
{
veryHiddenSheets++;
}
}
int lastIndex = workbook.Worksheets.Count - veryHiddenSheets;
return workbook.Worksheets[lastIndex];
}
So I've narrowed down the issue to a set of repeatable steps. This issues seems to be stemming from cases where you add some N sheets to the workbook, then delete them, and re-add a sheet. I enabled native debugging sugggested here http://social.msdn.microsoft.com/forums/en-US/vsto/thread/48cd3e88-d3a6-4943-b272-6d7ea81e11e3. I see the following call-stack when the exception above.
ntdll.dll!_ZwWaitForSingleObject#12() + 0x15 bytes
ntdll.dll!_ZwWaitForSingleObject#12() + 0x15 bytes
kernel32.dll!_WaitForSingleObjectExImplementation#12() + 0x43 bytes
[External Code]
First-chance exception at 0x2ff2489e in Excel.exe: 0xC0000005: Access violation reading location 0x00000000.
A first chance exception of type 'System.AccessViolationException' occurred in PublicCompModel.DLL
An exception of type 'System.AccessViolationException' occurred in PublicCompModel.DLL but was not handled in user code
Not sure if I am misusing the COM object, but I definitely find it odd that I can replicate this with deleting all of the sheets and that this is local to Excel.
After a lots of debugging, a support ticket with Microsoft's VSTO team, and some long nights, I finally got to the answer. The issue was not stemming from my code, but from the workbook itself. Initially we had written the code as a standalone project and then integrated sheets from our users' spreadsheet model. The key problem is that when you start copying sheets from other workbooks you bring along with them named references to the workbooks. Our user group provided us with a file that had hundreds of bad references which we hadn't previously seen.
Even if you suppress the warnings with the Application object, these events are still firing in the background. The large number of events firing while C# was manipulating the state of the workbook resulted in an AccessViolationException.
The lesson I learned: make sure to clean up the workbook and observe how the workbook is behaving without your code. Due to timing issues, we were forced to rewrite the solution in VBA while Microsoft debugged our code. Before we ever cleaned up the resources, the VBA code ran stabler which might stem from the fact that it is interpreted and, by my observations, running on a single thread.
As an aside, if you're working with VSTO in the context of a document add-in you should be careful about freeing references. In many cases, you probably don't need to do this, because Excel is probably cleaning this up for you. Freeing COM objects is considered dangerous.
I remember having something similar. While I do not remember the specifics, here is my template for saving and closing workbook. I hope this helps in some way.
xlWorkBook.SaveAs(fileName, Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
xlWorkBook.Close(true, misValue, misValue);
xlApp.Quit();
//Release objects
releaseObject(xlWorkSheet);
releaseObject(xlWorkBook);
releaseObject(xlApp);
...
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
Response.Write("Exception Occured while releasing object " + ex.ToString());
}
finally
{
GC.Collect();
}
}
Related
I am trying to fetch Customer data to parse them into customer object to display on TableView. The following code sometimes works, sometimes not. Whenever it does crash, it shows Customer data is empty in the foreach loop even though I run the same code every time. I do not have clue what could be wrong in this circumstances. I am quite new on this platform. If I am missing anything/ extra information, please let me know.
namespace TableViewExample
{
public partial class MyDataServices : ContentPage
{
private ODataClient mODataClient;
private IEnumerable <IDictionary<string,object>> Customers;
public MyDataServices ()
{
InitializeComponent ();
InitializeDataService ();
GetDataFromOdataService ();
TableView tableView = new TableView{ };
var section = new TableSection ("Customer");
foreach (var customers in Customers) {
//System.Diagnostics.Debug.WriteLine ((string)customers ["ContactName"]);
var name = (string)customers ["ContactName"];
var cell = new TextCell{ Text = name };
section.Add (cell);
}
tableView.Root.Add (section);
Padding = new Thickness (10, 20, 10, 10);
Content = new StackLayout () {
Children = { tableView }
};
}
private void InitializeDataService(){
try {
mODataClient = new ODataClient ("myURL is here");
}
catch {
System.Diagnostics.Debug.WriteLine("ERROR!");
}
}
private void GetDataFromOdataService (){
try {
Customers = mODataClient.For ("Customers").FindEntries ();
}
catch {
System.Diagnostics.Debug.WriteLine("ERROR!");
}
}
}
}
Its hard helping out here, however here are some things to consider:-
It sounds like the dataservice could either be not contactable / offline; too busy or it could even be throwing an exception itself and returning a data response that you are not expecting to receive, that then triggers an exception and crash in your application as your always expecting an exact response without catering for any abnormal responses / events.
If you are contacting an external service over the internet it may just be your internet connection is slow / faulty and not returning the information fast enough as other possibilities.
In your code you are assuming that you always get a response from the server - and that this response will always be of an anticipated structure that your expecting to decode - without factoring in any possibility of abnormal responses returned by the dataservice. I have not used ODataClient personally, so not sure how it behaves in the event of maybe no data received / timeout or in your case the dataservice and how it behaves internally in the response to a bad-request etc.
I am assuming an exception would get thrown, and you do get your debug line executed indicating a failure.
You may want to also adjust this statement so that you write out the exception as well, i.e.:-
private void GetDataFromOdataService ()
{
try
{
Customers = mODataClient.For ("Customers").FindEntries ();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("ERROR!" + ex.ToString());
}
}
If there was a bad response, then the line at Customers = ..... would throw the exception as there may be no Customers returned or some other information packaged in the response from the dataservice.
The Customers variable would also be null at this point I am assuming due to this failing.
So when you get back to your code at foreach (var customers in Customers) { it will then throw a null reference exception as Customers is infact null.
As all your current code executes in the constructor without any try and catch block around this, it will also crash your application at this point as well.
Also you are doing all of this work in the constructor. Try seperating this out. I haven't investigated exactly where the constructor gets called in an iOS page life-cycle, however, if it is in the viewDidLoad, then you have something like 10 seconds for everything to complete, otherwise it will exit automatically. I imagine in your case, this isn't applicable however.
Going forward also try putting your layout controls in the constructor, and move your data task to maybe the OnAppearing override instead.
Using async would definitely be advisable as well, but remember you need to inspect the response from your dataservice, as the error could be embedded within the response also and you will need to detect when it is OK to process the data.
I'm using QLPreviewController to preview files.
But when I open big files memory grows and don't disposing.
Ex: iPhone 4s, open 6 Mb txt and 5Mb txt after - app will freeze or crash. Look into Instruments - used memory 230Mb.
I have field in class:
private QLPreviewController previewController;
public void OpenAnotherFile()
{
if (previewController != null) {
previewController.DataSource.Dispose ();
previewController.DismissViewController (false, null);
} else {
previewController = new QLPreviewController ();
}
// loading new file here
previewController.DataSource = new QLPreview ();
previewController.ReloadData ();
view.AddSubview (previewController.View);
}
This variant without recreation of previewController, but i tried previewController.Dispose() and recreation. Tried call GC.Collect() - nothing.
I tried many cases (disposing datasource, view and so), but memory is not clearing.
I suggest - I'm not right myself. But where?.
Please, help.
Thanks.
VS2010 is driving me nuts: whenever I rebuild, the "Error List" warnings from the previous compilation are persisted and any new warnings are simply added to the end of the list. Over time, this list becomes ridiculously long and unwieldy.
I'm using the Chirpy 2.0 tools to run JSHint and JSLint on my JS files, and these tools generate a lot of false positives.
I've been looking for an easy way to clear the contents of this window, but the only manual mechanism that works 100% of the time is to close and re-open the solution. Not very elegant.
I'd like to write a small VS Plug-In or some code that gets called right before a compilation to clear out this list so I can focus only on new warnings for the currently loaded file(s).
I see a .Clear() method for the Output window but not for the Error List. Is this doable?
Once upon a time I was an Add-In/VSIX Package/MEF developer ...
The answer is shortly no, but I have to do it on the long way:
Add-Ins, packages (Managed or not) have access to the VS service level separatedly. Every error belongs to the reporter (If they are manage them as Chirpy do), so you can not handle the errors created by Chirpy 2.0
I take a several look to it's source code and it is persist it's erros gained by the tools in a Singleton collection called TaskList.
The deletion of the collection elements is happening in several point of code in the latest release through the RemoveAll method:
First: after the soulution is closed.
by this:
private static string[] buildCommands = new[] { "Build.BuildSelection", "Build.BuildSolution", "ClassViewContextMenus.ClassViewProject.Build" };
private void CommandEvents_BeforeExecute(string guid, int id, object customIn, object customOut, ref bool cancelDefault) {
EnvDTE.Command objCommand = default(EnvDTE.Command);
string commandName = null;
try {
objCommand = this.App.Commands.Item(guid, id);
} catch (System.ArgumentException) {
}
if (objCommand != null) {
commandName = objCommand.Name;
var settings = new Settings();
if (settings.T4RunAsBuild) {
if (buildCommands.Contains(commandName)) {
if (this.tasks != null) {
this.tasks.RemoveAll();
}
Engines.T4Engine.RunT4Template(this.App, settings.T4RunAsBuildTemplate);
}
}
}
}
As you may see, clear of results depends on many thigs.
First on a setting (which I don't know where to set on GUI or configs, but seems to get its value form a check box).
Second the array of names which are not contains every build commands name.
So I see a solution, but only on the way to modify and rebuild/redepeloy your own version from Chirpy (and make a Pull request):
The code souldn't depend on the commands, and their names. (rebuilds are missing for example)
You could change the method above something like this:
this.eventsOnBuild.OnBuildBegin += ( scope, action ) =>
{
if (action != vsBuildAction.vsBuildActionDeploy)
{
if (this.tasks != null)
{
this.tasks.RemoveAll();
}
if (settings.T4RunAsBuild && action != vsBuildAction.vsBuildActionClean)
{
Engines.T4Engine.RunT4Template(this.App, settings.T4RunAsBuildTemplate);
}
}
};
Or with something equivalent handler method instead of lambda expression.
You shold place it into the subscription OnStartupComplete method of Chirp class.
The unsubscription have to placed into OnDisconnection method in the same class. (As for all other subscribed handlers...)
Update:
When an Add-In disconneced, it isn't means the Studio will be closed immediately. The Add-In could be unloaded. So you should call the RemoveAll from OnDisconneconnection too. (Or Remove and Dispose the TaskList...)
Update2:
You can also make a custom command, and bind it to a hotkey.
I have a project which contains multiple forms that have a microsoft visio activex control in them.
For some reason they keep interfering with one another. Here's a snip it of my code, simplified:
private void OpenRDM()
{
if (RDMform == null)
{
// Constructor opens a new form, and populates a Visio control on it
RDMform = new RDM.MainForm();
}
RDMform.Show();
}
private void OpenPSM()
{
if (PSMform == null)
{
// Constructor opens a new form, and populates a Visio control on it
PSMform = new PSM.MainForm();
}
PSMform.Show(); // Problem occurs here, PSM Visio control gains contents of RDM's Visio Control
}
public void main()
{
OpenRDM();
RDMform.Hide();
OpenPSM();
PSMForm.Hide();
}
I can call one of these functions and it works fine, but if I hide the form, and then call the other one, its Visio control loads with the contents of the first form, instead of the correct new stuff. I traced it, and the second Visio control is populated just fine, until the second Show() method is called, at which point it mysteriously goes back to the contents of the first Visio control.
I can't figure out what is going on here, Visio seems to think that they are the same control.
Edit*
In response to comment about setting the .src property. The source is a behemoth, so I can't reasonably post everything it does. But here's those lines.
in RDM:
If visioControl.Src <> TemplateFullName Then
visioControl.Src = TemplateFullName
Else
visioControl.Src = ""
visioControl.Src = TemplateFullName
End If
in PSM:
drawingControl.Src = string.Empty;
drawingControl.Src = vstFilepath;
InitializeFlowDiagram(dbFilepath); // updates shapes from database values
Both modules actually use the same visio diagram including page names, they just display it in different ways. So I'd like to be able to back and forth between them without having to reload everything. Maybe the activeWindow is involved in the problem, I'll have to do more research.
I'm implementing an application which uses COM in AutoCAD's ObjectARX interface to automate drawing actions, such as open and save as.
According to the documentation, I should be able to call AcadDocument.SaveAs() and pass in a filename, a "save as type" and a security parameter. The documentation explicitly statses that if security is NULL, no security related operation is attempted. It doesn't, however, give any indication of the correct object type to pass as the "save as type" parameter.
I've tried calling SaveAs with a filename and null for the remaining arguments, but my application hangs on that method call and AutoCAD appears to crash - you can still use the ribbon but can't do anything with the toolbar and can't close AutoCAD.
I've got a feeling that it's my NULL parameters causing grief here, but the documentation is severely lacking in the COM/VBA department. In fact it says the AcadDocument class doesn't even have a SaveAs method, which it clearly does.
Has anyone here implemented the same thing? Any guidance?
The alternative is I use the SendCommand() method to send a _SAVEAS command, but my application is managing a batch of drawing and needs to know a) if the save fails, and b) when the save completes (which I'm doing by listening to the EndSave event.)
EDIT
Here's the code as requested - all it's doing is launching AutoCAD (or connecting to the running instance if it's already running), opening an existing drawing, then saving the document to a new location (C:\Scratch\Document01B.dwg.)
using (AutoCad cad = AutoCad.Instance)
{
// Launch AutoCAD
cad.Launch();
// Open drawing
cad.OpenDrawing(#"C:\Scratch\Drawing01.dwg");
// Save it
cad.SaveAs(#"C:\Scratch\Drawing01B.dwg");
}
Then in my AutoCad class (this._acadDocument is an instance of the AcadDocument class.)
public void Launch()
{
this._acadApplication = null;
const string ProgramId = "AutoCAD.Application.18";
try
{
// Connect to a running instance
this._acadApplication = (AcadApplication)Marshal.GetActiveObject(ProgramId);
}
catch (COMException)
{
/* No instance running, launch one */
try
{
this._acadApplication = (AcadApplication)Activator.CreateInstance(
Type.GetTypeFromProgID(ProgramId),
true);
}
catch (COMException exception)
{
// Failed - is AutoCAD installed?
throw new AutoCadNotFoundException(exception);
}
}
/* Listen for the events we need and make the application visible */
this._acadApplication.BeginOpen += this.OnAcadBeginOpen;
this._acadApplication.BeginSave += this.OnAcadBeginSave;
this._acadApplication.EndOpen += this.OnAcadEndOpen;
this._acadApplication.EndSave += this.OnAcadEndSave;
#if DEBUG
this._acadApplication.Visible = true;
#else
this._acadApplication.Visible = false;
#endif
// Get the active document
this._acadDocument = this._acadApplication.ActiveDocument;
}
public void OpenDrawing(string path)
{
// Request AutoCAD to open the document
this._acadApplication.Documents.Open(path, false, null);
// Update our reference to the new document
this._acadDocument = this._acadApplication.ActiveDocument;
}
public void SaveAs(string fullPath)
{
this._acadDocument.SaveAs(fullPath, null, null);
}
From the Autodesk discussion groups, it looks like the second parameter is the type to save as, and may be required:
app = new AcadApplicationClass();
AcadDocument doc = app.ActiveDocument;
doc.SaveAs("d:\Sam.dwg",AcSaveAsType.acR15_dwg,new Autodesk.AutoCAD.DatabaseServices.SecurityParameters());
Since you are in AutoCAD 2010, the type should be incremented to acR17_dwg or acR18_dwg.
Judging by the link to AutoDesk's forum on this topic, it sounds like as you need to close the object after saving...and remove the null's...If I were you, I'd wrap up the code into try/catch blocks to check and make sure there's no exception being thrown!
I have to question the usage of the using clause, as you're Launching another copy aren't you? i.e. within the OpenDrawing and Save functions you are using this._acadApplication or have I misunderstood?
using (AutoCad cad = AutoCad.Instance)
{
try{
// Launch AutoCAD
cad.Launch();
// Open drawing
cad.OpenDrawing(#"C:\Scratch\Drawing01.dwg");
// Save it
cad.SaveAs(#"C:\Scratch\Drawing01B.dwg");
}catch(COMException ex){
// Handle the exception here
}
}
public void Launch()
{
this._acadApplication = null;
const string ProgramId = "AutoCAD.Application.18";
try
{
// Connect to a running instance
this._acadApplication = (AcadApplication)Marshal.GetActiveObject(ProgramId);
}
catch (COMException)
{
/* No instance running, launch one */
try
{
this._acadApplication = (AcadApplication)Activator.CreateInstance(
Type.GetTypeFromProgID(ProgramId),
true);
}
catch (COMException exception)
{
// Failed - is AutoCAD installed?
throw new AutoCadNotFoundException(exception);
}
}
/* Listen for the events we need and make the application visible */
this._acadApplication.BeginOpen += this.OnAcadBeginOpen;
this._acadApplication.BeginSave += this.OnAcadBeginSave;
this._acadApplication.EndOpen += this.OnAcadEndOpen;
this._acadApplication.EndSave += this.OnAcadEndSave;
#if DEBUG
this._acadApplication.Visible = true;
#else
this._acadApplication.Visible = false;
#endif
// Get the active document
// this._acadDocument = this._acadApplication.ActiveDocument;
// Comment ^^^ out? as you're instantiating an ActiveDocument below when opening the drawing?
}
public void OpenDrawing(string path)
{
try{
// Request AutoCAD to open the document
this._acadApplication.Documents.Open(path, false, null);
// Update our reference to the new document
this._acadDocument = this._acadApplication.ActiveDocument;
}catch(COMException ex){
// Handle the exception here
}
}
public void SaveAs(string fullPath)
{
try{
this._acadDocument.SaveAs(fullPath, null, null);
}catch(COMException ex){
// Handle the exception here
}finally{
this._acadDocument.Close();
}
}
Thought I'd include some links for your information.
'Closing Autocad gracefully'.
'Migrating AutoCAD COM to AutoCAD 2010'.
'Saving AutoCAD to another format'
Hope this helps,
Best regards,
Tom.
I've managed to solve this in a non-optimal, very imperfect way so I'd still be interested to hear if anyone knows why the SaveAs method crashes AutoCAD and hangs my application.
Here's how I did it:
When opening a document or creating a new one, turn off the open/save dialog boxes:
this._acadDocument.SetVariable("FILEDIA", 0);
When saving a document, issue the _SAVEAS command passing in "2010" as the format and the filename (fullPath):
string commandString = string.Format(
"(command \"_SAVEAS\" \"{0}\" \"{1}\") ",
"2010",
fullPath.Replace('\\', '/'));
this._acadDocument.SendCommand(commandString);
When exiting AutoCAD turn file dialog prompting back on (probably isn't necessary but just makes sure):
this._acadDocument.SetVariable("FILEDIA", 1);
With C# and COM, when there are optional arguments, you need to use Type.Missing instead of null:
this._acadDocument.SaveAs(fullPath, Type.Missing, Type.Missing);
But since Visual Studio 2010, you can simply omit the optional arguments:
this._acadDocument.SaveAs(fullPath);