TheSwamp

Code Red => .NET => Topic started by: mohnston on February 08, 2011, 01:27:07 AM

Title: Get DBMOD for non-active drawing
Post by: mohnston on February 08, 2011, 01:27:07 AM
Is there a way to find out if a drawing is modified without using the DBMOD system variable?
That variable really should have been put at the document level, or better yet a property of the document or database.
It really isn't a system variable when you think about it.

If I have multiple drawings open and want to know the status (save-wise) of each I don't want to have to activate each drawing to do it.

Is there a way to find out if a drawing is modified without using the DBMOD system variable?
Title: Re: Get DBMOD for non-active drawing
Post by: kaefer on February 08, 2011, 02:47:51 AM
If I have multiple drawings open and want to know the status (save-wise) of each I don't want to have to activate each drawing to do it.

Is there a way to find out if a drawing is modified without using the DBMOD system variable?

Not that I know of. But if you can get at the variable with one Interop call, you don't have to activate the document.

(Example with late binding in F#)
Code: [Select]
    let docmods =
        Application.DocumentManager
        |> Seq.cast<obj>
        |> Seq.choose
            (function
                | :? Document as doc ->
                    let adoc = doc.AcadDocument
                    let dbmod =
                        adoc.GetType().InvokeMember(
                            "GetVariable",
                            System.Reflection.BindingFlags.InvokeMethod,
                            null, adoc, [| "DBMOD" |] )
                        |> unbox<int16>
                    Some(doc.Name, dbmod)
                | _ -> None
            )
Title: Re: Get DBMOD for non-active drawing
Post by: Alexander Rivilis on February 08, 2011, 03:16:16 AM
You can use P/Invoke: long __cdecl acdbGetDbmod(class AcDbDatabase *pDb) from acdb17.dll (for AutoCAD 2007...2009) or acdb18.dll (for AutoCAD 2010...2012)
EntryPoint = "?acdbGetDbmod@@YAJPAVAcDbDatabase@@@Z" for AutoCAD x86 and
EntryPoint = "?acdbGetDbmod@@YAJPEAVAcDbDatabase@@@Z" for AutoCAD x64
Title: Re: Get DBMOD for non-active drawing
Post by: mohnston on February 09, 2011, 02:54:05 PM
I could use some help with the PInvoke solution.
I have this that does not work:
Code: [Select]
    class cMFile
    {
        public cMFile()
        { }
        [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl,
                    EntryPoint = "?acdbGetDbmod@@YAJPAVAcDbDatabase@@@Z")]
        public static extern long acdbGetDbmod(class AcDbDatabase *pDb);
       
        internal static void ShowFiles()
        {
            Document adoc = acadApp.DocumentManager.MdiActiveDocument;
            Editor ed = adoc.Editor;
            DocumentCollection dColl = acadApp.DocumentManager;
            foreach (Document doc in dColl)
            {
                ed.WriteMessage(doc.Name + " - ");
                if (acdbGetDbmod(doc.Database) > 0)
                {
                    ed.WriteMessage(" changes not saved\n");
                }
                else
                {
                    ed.WriteMessage(" no changes\n");
                }
            }
        }
    }

It's probably obvious to you that this line is not correct in this context.
public static extern long acdbGetDbmod(class AcDbDatabase *pDb)

I understand that it's looking for a database, but is that a pointer or an object or ?
This is my first real go at pinvoke, thus the dumbness.
Title: Re: Get DBMOD for non-active drawing
Post by: dan.glassman on February 09, 2011, 02:57:45 PM
Code: [Select]
[DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl,
                    EntryPoint = "?acdbGetDbmod@@YAJPAVAcDbDatabase@@@Z")]
public static extern long acdbGetDbmod([color=red]IntPtr pDb[/color]);

use:

Code: [Select]
acdbGetDbmod(doc.Database.UnmanagedObject);
Title: Re: Get DBMOD for non-active drawing
Post by: Alexander Rivilis on February 09, 2011, 03:33:32 PM
Without any testing:
Code: [Select]
using System;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;

[assembly: CommandClass(typeof(Rivilis.GetDbMod))]

namespace Rivilis
{
  public class GetDbMod
  {
    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl,
         EntryPoint = "?acdbGetDbmod@@YAJPAVAcDbDatabase@@@Z")]
    private static extern Int32 acdbGetDbmod18x32(IntPtr db);
    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl,
         EntryPoint = "?acdbGetDbmod@@YAJPEAVAcDbDatabase@@@Z")]
    private static extern Int64 acdbGetDbmod18x64(IntPtr db);

    public static Int32 acdbGetDbmod(ref Database db)
    {
      switch (IntPtr.Size)
      {
        case 4:
          return (Int32)acdbGetDbmod18x32(db.UnmanagedObject);
        case 8:
          return (Int32)acdbGetDbmod18x64(db.UnmanagedObject);
      }
      return 0;
    }
    //------------------------------------
    // Test of GetDbmod
    //------------------------------------
    [CommandMethod("TestDbMode")]
    static public void TestDbMode()
    {
      Database db = Application.DocumentManager.MdiActiveDocument.Database;
      Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
      ed.WriteMessage("\nDBMODE={0}", acdbGetDbmod(ref db));
    }
 };
}
Title: Re: Get DBMOD for non-active drawing
Post by: mohnston on February 09, 2011, 03:54:52 PM
Thanks, that made the error go away . . . but

Code: [Select]
        [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl,
                    EntryPoint = "?acdbGetDbmod@@YAJPAVAcDbDatabase@@@Z")]
        public static extern int acdbGetDbmod(IntPtr pDb);
       
        internal static void ShowFiles()
        {
            Document adoc = acadApp.DocumentManager.MdiActiveDocument;
            Editor ed = adoc.Editor;
            DocumentCollection dColl = acadApp.DocumentManager;
            foreach (Document doc in dColl)
            {
                ed.WriteMessage(doc.Name + " - dbMod=");
                int dbm = acdbGetDbmod(doc.Database.UnmanagedObject);
                ed.WriteMessage(dbm.ToString() + "-");
                if (dbm > 0)
                {
                    ed.WriteMessage(" changes not saved\n");
                }
                else
                {
                    ed.WriteMessage(" no changes\n");
                }
               
            }
        }
When I returned a long the result was never 0 which it should be if there are no unsaved changes.
I changed the long to int and got 0 when there were no unsaved changes.
However, the value returned is not the dbMod value as far as I can tell.
DbMod is supposed to be 0 or a bitcode int as follows:
The setting is stored as a bitcode using the sum of the following values:
1 = Object database modified
4 = Database variable modified
8 = Window modified
16 = View modified
32 = Field modified

I'm getting 0 or some large number that doesn't make sense.
1028 if I change a view.
1285 if I modify/add an object
5140 if I pan the view

For my purposes I only need to know whether there are unsaved changes or not so this works.
Thanks for the help.

I suspect the values not matching up to the documentation has something to do with the data type I'm using. (int)
I tried long, uint, Int16, Int32 and none of them returned the appropriate value.
Title: Re: Get DBMOD for non-active drawing
Post by: kaefer on February 09, 2011, 04:11:12 PM
Without any testing:
With marginal testing...
Code: [Select]
    public static class DBMod
    {

        [DllImport("acdb18.dll", CallingConvention = CallingConvention.Cdecl,
            EntryPoint = "?acdbGetDbmod@@YAJPAVAcDbDatabase@@@Z")]
        public static extern int acdbGetDbmod(System.IntPtr pDb);

        static object GetVar(this Document doc, params object[] p)
        {
            return doc.AcadDocument.GetType().InvokeMember(
                "GetVariable",
                System.Reflection.BindingFlags.InvokeMethod,
                null, doc.AcadDocument, p );
        }

        [CommandMethod("ShowFiles")]
        public static void ShowFiles()
        {
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
            foreach (Document doc in Application.DocumentManager)
            {
                ed.WriteMessage(
                    "\n{0} {1}/{2} ",
                    doc.Database.Filename,
                    acdbGetDbmod(doc.Database.UnmanagedObject),
                    doc.GetVar("DBMOD")
                );
            }
        }
    }
...and funny results:
Quote
C:\ProgramData\Autodesk\MEP 2010\deu\Template\acadiso.dwt 1285/5
C:\ProgramData\Autodesk\MEP 2010\deu\Template\acadiso.dwt 0/0
Title: Re: Get DBMOD for non-active drawing
Post by: mohnston on February 09, 2011, 04:16:58 PM
Alexander, saw your post after I posted.
Thank you for the enhanced answer.
I notice that it should work with either 32 or 64 bit. I'll be creating a PInvoke wrapper/helper class.

The returned values still don't make sense but zero is always returned if there are no unsaved changes.

Any idea what the difference is between acdbGetDbmod and acdbGetDbmod18x32?
Title: Re: Get DBMOD for non-active drawing
Post by: dan.glassman on February 09, 2011, 05:25:53 PM
The documentation for dbmod must be part truth -- AutoCad is using more bits than documented.  Mask out the undocumented bits to get the expected result.

Code: [Select]
      switch (IntPtr.Size)
      {
        case 4:
          return (Int32)(acdbGetDbmod18x32(db.UnmanagedObject) [color=red]& 0x3d[/color]);
        case 8:
          return (Int32)(acdbGetDbmod18x64(db.UnmanagedObject) [color=red]& 0x3d[/color]);
      }

Title: Re: Get DBMOD for non-active drawing
Post by: mohnston on February 10, 2011, 12:20:02 PM
Thanks so much for the help.
I ended up with what I needed and more.
Code: [Select]
    public class PInvoke
    {
        const string ACDB_18 = "acdb18.dll";

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport(ACDB_18, CallingConvention = CallingConvention.Cdecl,
             EntryPoint = "?acdbGetDbmod@@YAJPAVAcDbDatabase@@@Z")]
        private static extern Int32 acdbGetDbmod18x32(IntPtr db);
        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport(ACDB_18, CallingConvention = CallingConvention.Cdecl,
             EntryPoint = "?acdbGetDbmod@@YAJPEAVAcDbDatabase@@@Z")]
        private static extern Int64 acdbGetDbmod18x64(IntPtr db);

        public static Int32 acdbGetDbmod(ref Database db)
        {
            switch (IntPtr.Size)
            {
                case 4:
                    return (Int32)(acdbGetDbmod18x32(db.UnmanagedObject) & 0x3d);
                case 8:
                    return (Int32)(acdbGetDbmod18x64(db.UnmanagedObject) & 0x3d);
            }
            return 0;
        }
       
        public static object GetVar(Document doc, params object[] p)
        {
            return doc.AcadDocument.GetType().InvokeMember(
                "GetVariable",
                System.Reflection.BindingFlags.InvokeMethod,
                null, doc.AcadDocument, p);
        }
    }