TheSwamp
Code Red => .NET => Topic started by: TyroneK on May 26, 2011, 03:35:06 AM
-
Hi everyone,
I'm in need of a little help. I'm trying to push page setups to non-opened drawings and am running into an "eWasOpenForWrite" when saving the drawing.
Anyway here's a simplified version of the code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
[assembly: CommandClass(typeof(PageSetups))]
[assembly: ExtensionApplication(typeof(PageSetups))]
public class PageSetups : IExtensionApplication
{
public void Initialize() { }
public void Terminate() { }
[CommandMethod( "TPS" )]
public static void TransferPageSetups()
{
string tgtDwg = @"C:\Test\Drawing2.dwg";
string setupName = "Setup1";
Database tgtDb = new Database( false, true );
try
{
// get the target database
tgtDb.ReadDwgFile( tgtDwg, FileOpenMode.OpenForReadAndWriteNoShare, true, "" );
// get the current database
Database srcDb = HostApplicationServices.WorkingDatabase;
using ( Transaction srcTr = srcDb.TransactionManager.StartTransaction() )
{
// get the pagesetup from the current drawing
var srcPsd = srcTr.GetObject(
srcDb.PlotSettingsDictionaryId, OpenMode.ForRead ) as DBDictionary;
ObjectId srcPsId = srcPsd.GetAt( setupName );
var srcPs = srcTr.GetObject( srcPsId, OpenMode.ForRead ) as PlotSettings;
using ( Transaction tgtTr = tgtDb.TransactionManager.StartTransaction() )
{
PlotSettings newPs = new PlotSettings( false );
// check the dict before
var tgtPsdBefore = tgtTr.GetObject(
tgtDb.PlotSettingsDictionaryId, OpenMode.ForRead ) as DBDictionary;
// copy over the plotsetting and add it to the target db dict
newPs.CopyFrom( srcPs );
newPs.AddToPlotSettingsDictionary( tgtDb );
// check the dict after
try
{
var tgtPsdAfter = tgtTr.GetObject(
tgtDb.PlotSettingsDictionaryId, OpenMode.ForRead ) as DBDictionary;
ObjectId tgtPsId = tgtPsdAfter.GetAt( setupName );
var tgtPs = tgtTr.GetObject( tgtPsId, OpenMode.ForRead ) as PlotSettings;
}
catch { }
tgtTr.Commit();
}
srcTr.Commit();
}
tgtDb.SaveAs( tgtDwg, true, DwgVersion.Newest, tgtDb.SecurityParameters );
}
catch ( Exception ex )
{
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( ex.Message );
}
finally
{
tgtDb.Dispose();
}
}
}
The problem seems to be with the AddToPlotSettingsDictionary() method as everything else seems to work fine. I included a couple of checks to make sure the plotsetting was being added to the target database properly and as far as I can tell it is.
What I don't quite understand is why essentially the same code works fine if a drawing is already open (and then saved manually).
Any insights would be much appreciated.
-
I think I might have solved my own problem.
I removed the
newPs.AddToPlotSettingsDictionary( tgtDb );
and added a long form transaction of opening the DBDictionary for write, setting the entry, then down grading both objects.
using ( Transaction tgtTr = tgtDb.TransactionManager.StartTransaction() )
{
PlotSettings newPs = new PlotSettings( false );
// get the target dict
var tgtPsd = tgtTr.GetObject( tgtDb.PlotSettingsDictionaryId, OpenMode.ForWrite ) as DBDictionary;
// copy over the plotsetting and add it to the target dict
newPs.CopyFrom( srcPs );
tgtPsd.SetAt( newPs.PlotSettingsName, newPs );
newPs.DowngradeOpen();
tgtPsd.DowngradeOpen();
tgtTr.Commit();
}
Anything else seemed to result in an "eWasOpenForWrite" or "accessed corrupted or protected memory" crash.
-
Hey TyroneK,
Welcome to the Swamp.
I believe your problem was from the new PlotSettings
PlotSettings newPs = new PlotSettings( false );
They were waiting for GC to Dispose them.
Normally by using GetObject() it will be associated with a transaction
Transaction.GetObject() or the Top most transaction if ObjectId.GetObject() used.
So if the transaction is in a 'using' block then transaction will dispose it.
Another simple way is to put the Plot Settings in a using block.
[CommandMethod("TPS")]
public void TransferPageSetups()
{
string tgtDwg = @"C:\Test\Drawing2.dwg";
string setupName = "Setup1";
Database currentDb = HostApplicationServices.WorkingDatabase;
Database targetDb = new Database(false, false);
targetDb.ReadDwgFile(tgtDwg, System.IO.FileShare.ReadWrite, false, "");
try
{
using (Transaction targetTrx = targetDb.TransactionManager.StartTransaction())
using (Transaction currentTrx = currentDb.TransactionManager.StartTransaction())
{
// get the pagesetup from the current drawing
DBDictionary currentPlotSettingsDict = (DBDictionary)currentTrx.GetObject(currentDb.PlotSettingsDictionaryId, OpenMode.ForRead);
ObjectId currentPlotSettingsId = currentPlotSettingsDict.GetAt(setupName);
PlotSettings currentPlotSettings = currentTrx.GetObject(currentPlotSettingsId, OpenMode.ForRead) as PlotSettings;
using (PlotSettings targetPlotSettings = new PlotSettings(false))
{
// copy over the plotsetting and add it to the target db dict
targetPlotSettings.CopyFrom(currentPlotSettings);
targetPlotSettings.AddToPlotSettingsDictionary(targetDb);
}
currentTrx.Commit();
targetTrx.Commit();
}
targetDb.SaveAs(tgtDwg, DwgVersion.Current);
}
catch (System.Exception ex)
{
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(ex.Message);
}
finally
{
//tgtDb.Dispose();
}
}
I think your database constructor should have false as both parameters Database targetDb = new Database(false, false);
Maybe wait to see what others say
-
Thanks for the welcome Jeff. I think I registered for these forums about 2.5 years ago but finally got around to participating.
I think your database constructor should have false as both parameters
This might well be true. System.Boolean specifying whether or not to associate this database to the current document. This sounds like what I don't want to have happen so I usually specify noDocument = true. Or does true mean that it is associating it?
Is there a reason that
PlotSettings newPs = new PlotSettings( false );
works fine for an active document but not a db.ReadDwgFile?
Also, I noticed that you commented out //tgtDb.Dispose();. Is there a reason you wouldn't want to dispose of the source database at the end of the transaction? I assume that the database objects disposed of automatically but does it happen immediately? Disposing of it manually is probably a habit I picked up from Kean.
Thanks for the much cleaner solution. Also I like that stacking of the using blocks; definitely saves on some nesting.
-
Is there a reason that
PlotSettings newPs = new PlotSettings( false );
works fine for an active document but not a db.ReadDwgFile?
I would have to see code that it works.
Also, I noticed that you commented out //tgtDb.Dispose();. Is there a reason you wouldn't want to dispose of the source database at the end of the transaction? I assume that the database objects disposed of automatically but does it happen immediately? Disposing of it manually is probably a habit I picked up from Kean.
Good Catch,
Should have put it in a using block which takes care of dispose for you.
The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object. You can achieve the same result by putting the object inside a try block and then calling Dispose in a finally block; in fact, this is how the using statement is translated by the compiler. The code example earlier expands to the following code at compile time (note the extra curly braces to create the limited scope for the object):
Link to quote above (http://msdn.microsoft.com/en-us/library/yh598w02(v=VS.100).aspx)
When ever you create a 'AutoCad Object' with new keyword it is automatically opened for write.
So it is good to put them in a using block
Here is some code then the IL code from IL DASM.
I marked in red the finally and endFinally
I marked in blue the dispose.
Notice how NODOH has no try catch block in C# code but the 'using' statement will create one and cause it to be disposed in a finally block that is generated for you
[CommandMethod("DOH")]
public void DOH()
{
Line lne = new Line(new Point3d(0, 0, 0), new Point3d(0, 0, 0));
try
{
}
catch (System.Exception)
{
}
finally
{
lne.Dispose();
}
}
[CommandMethod("NODOH")]
public void NODOH()
{
using (Line lne = new Line(new Point3d(0, 0, 0), new Point3d(0, 0, 0)))
{
}
}
DOH
.method public hidebysig instance void DOH() cil managed
{
.custom instance void [Acmgd]Autodesk.AutoCAD.Runtime.CommandMethodAttribute::.ctor(string) = ( 01 00 03 44 4F 48 00 00 ) // ...DOH..
// Code size 95 (0x5f)
.maxstack 5
.locals init ([0] class [Acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line lne)
IL_0000: nop
IL_0001: ldc.r8 0.0
IL_000a: ldc.r8 0.0
IL_0013: ldc.r8 0.0
IL_001c: newobj instance void [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d::.ctor(float64,
float64,
float64)
IL_0021: ldc.r8 0.0
IL_002a: ldc.r8 0.0
IL_0033: ldc.r8 0.0
IL_003c: newobj instance void [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d::.ctor(float64,
float64,
float64)
IL_0041: newobj instance void [Acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line::.ctor(valuetype [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d,
valuetype [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d)
IL_0046: stloc.0
.try
{
.try
{
IL_0047: nop
IL_0048: nop
IL_0049: leave.s IL_0050
} // end .try
catch [mscorlib]System.Exception
{
IL_004b: pop
IL_004c: nop
IL_004d: nop
IL_004e: leave.s IL_0050
} // end handler
IL_0050: nop
IL_0051: leave.s IL_005d
} // end .try
[color=red]finally[/color]
{
IL_0053: nop
IL_0054: ldloc.0
[color=blue]IL_0055: callvirt instance void [Acdbmgd]Autodesk.AutoCAD.Runtime.DisposableWrapper::Dispose()[/color]
IL_005a: nop
IL_005b: nop
[color=red]IL_005c: endfinally[/color]
} // end handler
IL_005d: nop
IL_005e: ret
} // end of method MyCommands::DOH
NODOH
.method public hidebysig instance void NODOH() cil managed
{
.custom instance void [Acmgd]Autodesk.AutoCAD.Runtime.CommandMethodAttribute::.ctor(string) = ( 01 00 05 4E 4F 44 4F 48 00 00 ) // ...NODOH..
// Code size 93 (0x5d)
.maxstack 5
.locals init ([0] class [Acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line lne,
[1] bool CS$4$0000)
IL_0000: nop
IL_0001: ldc.r8 0.0
IL_000a: ldc.r8 0.0
IL_0013: ldc.r8 0.0
IL_001c: newobj instance void [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d::.ctor(float64,
float64,
float64)
IL_0021: ldc.r8 0.0
IL_002a: ldc.r8 0.0
IL_0033: ldc.r8 0.0
IL_003c: newobj instance void [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d::.ctor(float64,
float64,
float64)
IL_0041: newobj instance void [Acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line::.ctor(valuetype [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d,
valuetype [Acdbmgd]Autodesk.AutoCAD.Geometry.Point3d)
IL_0046: stloc.0
.try
{
IL_0047: nop
IL_0048: nop
IL_0049: leave.s IL_005b
} // end .try
[color=red]finally[/color]
{
IL_004b: ldloc.0
IL_004c: ldnull
IL_004d: ceq
IL_004f: stloc.1
IL_0050: ldloc.1
IL_0051: brtrue.s IL_005a
IL_0053: ldloc.0
[color=blue]IL_0054: callvirt instance void [mscorlib]System.IDisposable::Dispose()[/color]
IL_0059: nop
[color=red] IL_005a: endfinally[/color]
} // end handler
IL_005b: nop
IL_005c: ret
} // end of method MyCommands::NODOH
-
Actually my first intent was to make the current document crap out but I can not get one to save
keep getting
efileshare error
[CommandMethod("DOH")]
public void DOH()
{
Document doc = Application.DocumentManager.MdiActiveDocument; // Active Document
Editor ed = doc.Editor; // Active Document's Editor
Database db = doc.Database; // Active Document Database
string strDWGName = doc.Name;
Line lne = new Line(new Point3d(0, 0, 0), new Point3d(0, 0, 0));
try
{
object obj = Application.GetSystemVariable("DWGTITLED");
if (System.Convert.ToInt16(obj) == 0)
{
strDWGName = @"C:\Test\Testing.dwg";
}
db.SaveAs(strDWGName, false, DwgVersion.Current, db.SecurityParameters);
}
catch (System.Exception ex)
{
ed.WriteMessage(ex.Message);
}
finally
{
lne.Dispose();
}
}
[CommandMethod("NODOH")]
public void NODOH()
{
Document doc = Application.DocumentManager.MdiActiveDocument; // Active Document
Editor ed = doc.Editor; // Active Document's Editor
Database db = doc.Database; // Active Document Database
string strDWGName = doc.Name;
using (Line lne = new Line(new Point3d(0, 0, 0), new Point3d(0, 0, 0)))
{
}
object obj = Application.GetSystemVariable("DWGTITLED");
if (System.Convert.ToInt16(obj) == 0)
{
strDWGName = @"C:\Test\Testing.dwg";
}
db.SaveAs(strDWGName, false, DwgVersion.Current, db.SecurityParameters);
}
-
I would have to see code that it works.
I have code that "works" for open documents but it only really works because the object is being disposed of automatically as the command finishes.
Forgot to call Dispose? (Autodesk.AutoCAD.DatabaseServices.PlotSettings): DisposableWrapper
If I attempt to incorporate a save into the command the same problem happens (unless the PlotSetting is disposed of properly).
Thanks for your insights on using blocks. I had been making use of them, but really only for Transactions and DocumentLocks, but no longer.
Edit:
I forgot to add that constructing a new Database with ( false, false ) instead of ( false, true ) causes problems with other commands in my tool suite. Although for this particular command doesn't seem to matter.