SaveAs in COM hanging AutoCAD - autocad

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);

Related

Trying to automate a process in console application

I am trying to get my console application to simulate dragging and dropping a file, so far I have had no luck.
The system throws a win32 exception stating it cannot find the file, since I know that is not really the problem I was hoping someone could shed some light on what might be causing this behavior.
I suspect it might be DEP. I can drag and drop the file and the process runs as expected, but I need to automate this.
I have created a filewatcher and am right now trying to figure out how to get the code to work, prior to making it a windows service.
But right now I am really stuck on this win32 error.
public class Watcher
{
public static void Main(string[] args)
{
Run();
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public static void Run()
{
string[] args = System.Environment.GetCommandLineArgs();
// If a directory is not specified, exit program.
if (args.Length != 2)
{
// Display the proper way to call the program.
Console.WriteLine("Usage: Watcher.exe (directory)");
return;
}
// Create a new FileSystemWatcher and set its properties.
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = args[1];
/* Watch for changes in LastAccess and LastWrite times, and
the renaming of files or directories. */
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// Only watch jpg files.
watcher.Filter = "*.jpg";
// Add event handlers.
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
// Begin watching.
watcher.EnableRaisingEvents = true;
// Wait for the user to quit the program.
Console.WriteLine("Press \'q\' to quit the sample.");
while (Console.Read() != 'q') ;
}
// Define the event handlers.
private static void OnChanged(object source, FileSystemEventArgs e)
{
// Specify what is done when a file is changed, created, or deleted.
Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
Process.Start(e.FullPath + "c:\\demo\\kr-pano\\mpr.bat");
}
Visit the link to see the full win32 exception details.
Win32Exception
Apparently the issue is being caused because the filesystemwatcher is single threaded, and when I try to launch my new process it stops the old one from running. All I needed to do was stub out a new runpano function and call it from my code, its working now.

How to tell if the active document is a text document?

I'm developing a Visual Studio extension in which one of the implemented commands needs to be available only when the active document is a text document (like e.g. the Visual Studio's "Toggle Bookmark" does). The problem is that I can't figure out how to tell when that's the case.
Right now I have a half working solution. In the package's Initialize method I subscribe to DTE's WindowActivated event, and then whenever a window is activated I check if the window DocumentData property is of type TextDocument:
protected override void Initialize()
{
base.Initialize();
var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
dte.Events.WindowEvents.WindowActivated += WindowEventsOnWindowActivated;
//More initialization here...
}
//This is checked from command's BeforeQueryStatus
public bool ActiveDocumentIsText { get; private set; } = false;
private void WindowEventsOnWindowActivated(Window gotFocus, Window lostFocus)
{
if (gotFocus.Kind != "Document")
return; //It's not a document (e.g. it's a tool window)
TextDocument textDoc = gotFocus.DocumentData as TextDocument;
ActiveDocumentIsText = textDoc != null;
}
The problem with this approach is that 1) Window.DocumentData is documented as ".NET Framework internal use only", and 2) this gives a false positive when a document that has both a code view and a design view (e.g. a .visxmanifest file) is open in design mode.
I have tried to use IVsTextManager.GetActiveView as well, but this is returning the last active text view opened - so if I open a .txt file and then a .png file, it returns data for the .txt file even if it's not the active document anymore.
So, how do I check if the active document is a text document, or the code view of a document that can have a designer... and if possible, not using "undocumented" classes/members?
UPDATE: I found a slightly better solution. Inside the window activated handler:
ActiveDocumentIsText = gotFocus.Document.Object("TextDocument") != null;
At least this one is properly documented, but I still have the problem of false positives with designers.
I finally got it. It's somewhat tricky, but it works and is 100% "legal". Here's the recipe:
1- Make the package class implement IVsRunningDocTableEvents. Make all the methods just return VSConstants.S_OK;
2- Add the following field and the following auxiliary method to the package class:
private IVsRunningDocumentTable runningDocumentTable;
private bool DocIsOpenInLogicalView(string path, Guid logicalView, out IVsWindowFrame windowFrame)
{
return VsShellUtilities.IsDocumentOpen(
this,
path,
VSConstants.LOGVIEWID_TextView,
out var dummyHierarchy2, out var dummyItemId2,
out windowFrame);
}
3- Add the following to the Initialize method of the package class:
runningDocumentTable = GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
runningDocumentTable.AdviseRunningDocTableEvents(this, out var dummyCookie);
4- Don't blink, here comes the magic! Implement the IVsRunningDocTableEvents.OnBeforeDocumentWindowShow method as follows:
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
{
runningDocumentTable.GetDocumentInfo(docCookie,
out var dummyFlags, out var dummyReadLocks, out var dummyEditLocks,
out string path,
out var dummyHierarchy, out var dummyItemId, out var dummyData);
IVsWindowFrame windowFrameForTextView;
var docIsOpenInTextView =
DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_Code, out windowFrameForTextView) ||
DocIsOpenInLogicalView(path, VSConstants.LOGVIEWID_TextView, out windowFrameForTextView);
//Is the document open in the code/text view,
//AND the window for that view is the one that has been just activated?
ActiveDocumentIsText = docIsOpenInTextView && pFrame == logicalViewWindowFrame;
return VSConstants.S_OK;
}

Is it possible to execute some code when visual studio enters break mode?

I have a large proces that I need to debug and the proces could stop at anytime. I have configured Visual Studio 2017, to stop at any thrown exception, as in, even if it is handled, because I want to see what caused the exception. What I need is some sort of alarm when this happens, so that I can leave the program to run and then alert me if anything comes up. The only thing I have found is an alarm sound when a break point is hit, but it might not be a break point and I need more than a sound, I need to be able to execute some code, so that I can make my Phone go nuts or whatever. Is there any way I can trigger code when the debugger enters break mode?
Thanks in advance.
It is, using a VS package. You'll need to add this attribute on top of the class in order for code to run on package startup:
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.SolutionExists)] ///Able to run code on solution startup
Add these class values variables:
private DTE2 applicationObject;
private BuildEvents buildEvents;
private DebuggerEvents debugEvents;
then the following code can run:
protected override void Initialize()
{
base.Initialize();
applicationObject = (DTE2)GetService(typeof(DTE));
buildEvents = applicationObject.Events.BuildEvents;
debugEvents = applicationObject.Events.DebuggerEvents;
SetupEventHandlers();
}
And finally the code we have "all" being waiting for:
private void SetupEventHandlers()
{
//buildEvents.OnBuildDone += (scope, action) =>
//{
//};
debugEvents.OnEnterBreakMode += delegate (dbgEventReason reason, ref dbgExecutionAction action)
{
};
//var componentModel =
// GetGlobalService(typeof(SComponentModel)) as IComponentModel;
//if (componentModel == null)
//{
// Debug.WriteLine("componentModel is null");
// return;
//}
//var operationState = componentModel.GetService<IOperationState>();
//operationState.StateChanged += OperationStateOnStateChanged;
}

Not receiving messages after sometime

I am using JNA to access User32 functions (I dont think it has got to do with Java here, more of concept problem). In my application, I have a Java process which communicates with the Canon SDK. To dispatch any messages I am using the below function:
private void peekMessage(WinUser.MSG msg) throws InterruptedException {
int hasMessage = lib.GetMessage(msg, null, 0, 0);
if (hasMessage != 0) {
lib.TranslateMessage(msg);
lib.DispatchMessage(msg);
}
Thread.sleep(1);
}
peekMessage is called in a loop and it all works well. Whenever an Image is taken from camera, I get the event and do the rest.
But I have observed, say after about 15 seconds (sometimes never or sometimes just at start) of no activity with camera, taking picture does not give me any download event. Later the whole application becomes unusable as it doesn't get any events from camera.
What can be the reason for this? Please let me know of any other info needed, I can paste the respective code along.
Edit:
Initialization:
Map<String, Integer> options = new HashMap<String, Integer>();
lib = User32.INSTANCE;
hMod = Kernel32.INSTANCE.GetModuleHandle("");
options.put(Library.OPTION_CALLING_CONVENTION, StdCallLibrary.STDCALL_CONVENTION);
this.EDSDK = (EdSdkLibrary) Native.loadLibrary("EDSDK/dll/EDSDK.dll", EdSdkLibrary.class, options);
private void runNow() throws InterruptedException {
while (!Thread.currentThread().isInterrupted()) {
Task task = queue.poll();
if (task != null) {
int taskResult = task.call();
switch (taskResult) {
case (Task.INITIALIZE_STATE):
break;
case (Task.PROCESS_STATE):
break;
case (Task.TERMINATE_STATE): {
//queue.add(new InitializeTask());
Thread.currentThread().interrupt();
break;
}
default:
;
}
}
getOSEvents();
}
}
WinUser.MSG msg = new WinUser.MSG();
private void getOSEvents() throws InterruptedException {
if (isMac) {
receiveEvents();
} else {
peekMessage(msg);
}
}
Above, whenever I get my camera event, it add's it to the queue and in each loop I check the queue to process any Task. One more important information: This is a process running on cmd and has no window. I just need the events from my camera and nothing else.
The code where I register callback functions:
/**
* Adds handlers.
*/
private void addHandlers() {
EdSdkLibrary.EdsVoid context = new EdSdkLibrary.EdsVoid(new Pointer(0));
int result = EDSDK.EdsSetObjectEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsObjectEvent_All), new ObjectEventHandler(), context).intValue();
//above ObjectEventHandler contains a function "apply" which is set as callback function
context = new EdSdkLibrary.EdsVoid(new Pointer(0));
result = EDSDK.EdsSetCameraStateEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsStateEvent_All), new StateEventHandler(), context).intValue();
//above StateEventHandler contains a function "apply" which is set as callback function
context = new EdSdkLibrary.EdsVoid(new Pointer(0));
result = EDSDK.EdsSetPropertyEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsStateEvent_All), new PropertyEventHandler(), context).intValue();
//above PropertyEventHandler contains a function "apply" which is set as callback function
}
You are getting ALL messages from ALL windows that belong to this thread, that includes all mouse moves, paints etc. if you aren't rapidly calling this function your message queue will overflow and cause the behavior you describe.
The sleep you definitely don't want as GetMessage yields if no message is waiting.
So if there exists a normal message pump(s) (i.e GetMessage/DispatchMessage) loop somewhere else for this threads window(s) then you should let that pump do most of the work, perhaps use wMsgFilterMin, wMsgFilterMax to just get the event message you require; or even better in this case use peekmessage with PM_NOREMOVE (then you will need your sleep
call as peekmessage returns immediately).
Alternatively provide the hWnd of the window that generates the event to reduce the workload.
Use spy++ to look into which windows this thread owns and what messages are being produced.
To take this answer further please provide answers to: what else is this thread doing and what windows does it own; also is this message pump the only one or do you call into the SDK API where it may be pumping messages too?
There is an OpenSource project wrapping EDSDK with JNA and it has a version of your code that is probably working better:
https://github.com/kritzikratzi/edsdk4j/blob/master/src/edsdk/api/CanonCamera.java#L436
Unfortunately this is not platform independent and specifically the way things work on windows. I am currently in the process of trying to get a MacOS version of things working at:
https://github.com/WolfgangFahl/edsdk4j

File being used by another process in a Windows Service with a StreamReader

I have searched numerous articles and tried several different ways of solving this issue.
Note that this process works fine in a Console app or a Windows app with the exact same code. So, there's nothing wrong with the logic. It's the way the Windows Service is processing these files that is somehow not letting subsequent files get through.
I tried reading a simple article to actually debug the service, but I don't have the "Attach To Process" selection under my Debug menu using Visual Studio 2010.
http://msdn.microsoft.com/en-us/library/7a50syb3(v=vs.100).aspx
I really need some help here, because my options have really run out.
Here is the simple scenario:
I have a FileSystemWatcher in the OnStart method that kicks off another method to process a file on my local machine. These are just simple txt files. When the first file is processed, everything is normal and I get the following in my Event log:
Data successfully inserted for file 20121212_AZM_Journey_MIS.txt
When I try and process subsequent files (exact same format, but a different name --- with the date), I get the following in my Event log:
The process cannot access the file 'C:\Projects\Data\VendingStats\20121213_AZM_Journey_MIS.txt' because it is being used by another process.
Note that the files are watched for in this same directory when created.
Here is my code in my Windows Service. Can someone please let me know how I can solve this issue? Note that e.FullPath evaluates to "C:\Projects\Data\VendingStats" on my local machine.
public partial class VendingStatsService : ServiceBase
{
public VendingStatsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
FileSystemWatcher Watcher = new FileSystemWatcher(#"C:\Projects\Data\VendingStats");
Watcher.EnableRaisingEvents = true;
Watcher.Created += new FileSystemEventHandler(**Watcher_Created**);
Watcher.Filter = "*.txt";
Watcher.IncludeSubdirectories = false;
}
private void Watcher_Created(object sender, FileSystemEventArgs e)
{
try
{
using (TextReader tr = new StreamReader(e.FullPath))
{
// code here to insert data from file into a DB
}
Dispose();
}
catch (Exception ex)
{
EventLog.WriteEntry("VendorStats", "Error in the Main:" + "\r\n\r\n" + ex.Message + "\r\n\r\n" + ex.InnerException);
return;
}
}
public void Dispose()
{
GC.Collect();
}

Resources