Navision COM interfaces

meeuwmeeuw Member Posts: 9
Hi Folks,

I'm currently investigating the Navision COM interfaces which can be accessed from outside Navision.
Søren Nielsen found some interesting information and blogged it on his blog: http://gotcal.com/

His test programs are a bit messy and programmed in C#, I couldn't see what was actually happening in his GotCAL.CSIDEInstaller class. Although most of the functions called are imported from C(++) DLLs C# does some strange magic on them.

I will publish my findings in this forum thread hoping some people find it usefull and maybe provide me with more information.

It seems Navision is advertising an object in the COM running object table. The running object table must fetched by the following API call:
GetRunningObjectTable(0, &rot);
The rot object contains a method to get an enumerator to enumerate the monikers to the running objects:
rot->EnumRunning(&enumMoniker);
When Navision is running you'll see three instances with different names but it seems it doesn't matter which one you choose. Weird. You can find the displayname of the moniker by calling:
CreateBindCtx(0, &bc);
moniker->GetDisplayName(bc, 0, &displayname);
When you've choosen the moniker which you would like to use you can get the object.
rot->GetObject(moniker, &iunknown);
This object has multiple interfaces:
  • IUnknown
  • IMarshal
  • {00000020-0000-0000-C000-000000000046}
  • {0000013D-0000-0000-C000-000000000046}
  • INSHyperlink
  • INSObjectDesigner
  • INSApplication
  • INSAppBase
  • IConnectionPointContainer
  • ISupportErrorInfo
(I found these by calling QueryInterface and using the interfaces found in the registry)

Now I really hope to find all declarations of these interfaces but for now I've only used the INSObjectDesigner from Søren. The IDL seems to be:
[object, uuid (50000004-0000-1000-0001-0000836BD2D2)]
interface INSObjectDesigner : IUnknown {
     HRESULT ReadObject([in] int objectType, [in] int objectId, [in] IStream *destination);
     HRESULT ReadObjects([in] LPOLESTR filter, [in] IStream *destination);
     HRESULT WriteObjects([in] IStream *source);
     HRESULT CompileObject([in] int objectType, [in] int objectId);
     HRESULT CompileObjects([in] LPOLESTR filter);
     HRESULT GetServerName([out] LPOLESTR *serverName);
     HRESULT GetDatabaseName([out] LPOLESTR *databaseName);
     HRESULT GetServerType([out] int *serverType);
     HRESULT GetCSIDEVersion([out] LPOLESTR *csideVersion);
     HRESULT GetApplicationVersion([out] LPOLESTR *applicationVersion);
     HRESULT GetCompanyName([out] LPOLESTR *companyName);
}

You can use this class by calling:
INSObjectDesignerPtr insobjectdesigner;
iunknown->QueryInterface(IID_INSObjectDesigner, (void**)&insobjectdesigner);

When you are using the methods with IStream parameters please note I'll have to rewind (Seek to 0) the IStream. Oh, the IStream can by constructed by calling an API.
CreateStreamOnHGlobal(NULL, TRUE, &strm);
insobjectdesigner->ReadObject(5, 1, strm);
LARGE_INTEGER li = {0};
strm->Seek(li, STREAM_SEEK_SET, 0);
strm->Read(buf, 100, &cbRead);

As I wrote, I hope this is usefull for someone and if someone has more information please tell me! Especially about the other interfaces.
«1

Comments

  • meeuwmeeuw Member Posts: 9
    Update!

    I've found a great tool called NDR INSPECTOR (by Vito Plantamura) which extracts the definition of the navision interfaces!
    It can be downloaded here: http://www.vitoplantamura.com/index.asp ... rinspector

    This is the IDL as far as I know it:
    import "objidl.idl";
    [object, uuid (50000004-0000-1000-0001-0000836BD2D2)]
    interface INSObjectDesigner : IUnknown { 
         HRESULT ReadObject([in] int objectType, [in] int objectId, [in] IStream *destination);
         HRESULT ReadObjects([in] LPOLESTR filter, [in] IStream *destination);
         HRESULT WriteObjects([in] IStream *source);
         HRESULT CompileObject([in] int objectType, [in] int objectId);
         HRESULT CompileObjects([in] LPOLESTR filter);
         HRESULT GetServerName([out] LPOLESTR *serverName);
         HRESULT GetDatabaseName([out] LPOLESTR *databaseName);
         HRESULT GetServerType([out] int *serverType);
         HRESULT GetCSIDEVersion([out] LPOLESTR *csideVersion);
         HRESULT GetApplicationVersion([out] LPOLESTR *applicationVersion);
         HRESULT GetCompanyName([out] LPOLESTR *companyName);
    }
    interface INSRec;
    [object, uuid (50000004-0000-1000-0011-0000836BD2D2)]
    interface INSCallbackEnum : IUnknown {
         HRESULT proc3([in] INSRec *a);
         HRESULT proc4([in] int a, [in] LPOLESTR b, [in] LPOLESTR c);
         HRESULT proc5([in] int a, [in] LPOLESTR b);
         HRESULT proc6([in] int a, [in] LPOLESTR b);
         HRESULT proc7([in] int a, [in] LPOLESTR b, [in] LPOLESTR c, [in] LPOLESTR d, [in] int e, [in] int f);
    }
    [object, uuid (50000004-0000-1000-0007-0000836BD2D2)]
    interface INSRec : IUnknown {
         HRESULT proc3([in] int a, [in] LPOLESTR b, [in] int c);
         HRESULT proc4([in] int a, [out] LPOLESTR *b);
         HRESULT proc5([in] INSCallbackEnum *a);
    }
    [object, uuid (50000004-0000-1000-0006-0000836BD2D2)]
    interface INSTable : IUnknown {
         HRESULT proc3([in] INSRec *a);
         HRESULT proc4([in] INSRec *a);
         HRESULT proc5([in] INSRec *a);
         HRESULT proc6([out] INSRec **a);
         HRESULT proc7([in] int a, [in] LPOLESTR *b);
         HRESULT proc8([in] INSCallbackEnum *a);
         HRESULT proc9([in] INSCallbackEnum *a);
         HRESULT proc10([in] INSCallbackEnum *a, [in] int b);
         HRESULT proc11([in] INSRec *a);
         HRESULT GetID([out] int *a);
         HRESULT proc13([out] int *a);
    }
    [object, uuid (50000004-0000-1000-0010-0000836BD2D2)]
    interface INSAppBase : IUnknown {
         HRESULT GetTable([in] int a, [out] INSTable **b);
         HRESULT GetInfo([out] LPOLESTR *a, [out] LPOLESTR *databasename, [out] LPOLESTR *company, [out] LPOLESTR *d);
         HRESULT proc5(void);
         HRESULT proc6([in] int a);
         HRESULT proc7([in] INSCallbackEnum *a, [in] int b);
    }
    [object, uuid (50000004-0000-1000-0000-0000836BD2D2)]
    interface INSHyperlink : IUnknown {
         HRESULT Open([in] LPOLESTR link);
         HRESULT proc4([out] int *a);
    }
    [object, uuid (50000004-0000-1000-0008-0000836BD2D2)]
    interface INSMenuButton : IUnknown {
         HRESULT proc3([in] LPOLESTR a);
         HRESULT proc4([in] int a, [in] LPOLESTR b, [in] LPOLESTR c);
    }
    [object, uuid (50000004-0000-1000-0003-0000836BD2D2)]
    interface INSForm : IUnknown {
         HRESULT proc3([out] LPOLESTR *a);
         HRESULT proc4([out] LPOLESTR *a);
         HRESULT proc5([out] INSRec **a);
         HRESULT proc6([out] INSTable **a);
         HRESULT proc7([out] int **a);
         HRESULT proc8([out] INSMenuButton **a);
         HRESULT proc9(void);
    }
    [object, uuid (50000004-0000-1000-0002-0000836BD2D2)]
    interface INSApplication : IUnknown {
         HRESULT GetForm([out] INSForm **form);
    }
    

    notes:
    - GetForm returns a null pointer in form
    - I haven't made an implementation of INSCallbackEnum yet so I couldn't test it
    - INSHyperlink->proc4 returns a large (constant) number, I don't know what it is.
    - The first and last parameter of INSAppBase are empty in my current setup
  • SNielsenSNielsen Member Posts: 37
    Great news. Love the tool from VitoPlantamura's site. Thanks for the update.
  • gerdhuebnergerdhuebner Member Posts: 155
    meeuw wrote:
    When Navision is running you'll see three instances with different names but it seems it doesn't matter which one you choose. Weird.
    Each of the three instances belongs to the application, the database and the company respectively. Try to start the client without opening a company or a database, respectively and you will find only 2 or 1 instance, respectively. You can also guess that from the DisplayName of the moniker.
  • gerdhuebnergerdhuebner Member Posts: 155
    edited 2009-03-20
    Recently I investigated the INSApplication interface and discovered some nice possiblities. Through the INSRec interface you can retrieve the value of any field of the record, which is shown in the client's currently active form (may be with INSEnumCallback it is possible to step through all open forms, but I 've got no idea how...). As there is no typelib information available, you have to implement the interfaces in the usual way (be aware that the names of the methods are not official in any way) - here is the declaration for C#:
        [ComImport, Guid("50000004-0000-1000-0001-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface INSObjectDesigner
        {
            int ReadObject([In] int objectType, [In] int objectId, [In] IStream destination);
            int ReadObjects([In] String filter, [In] IStream destination);
            int WriteObjects([In] IStream source);
            int CompileObject([In] int objectType, [In] int objectId);
            int CompileObjects([In] String filter);
            int GetServerName(out String serverName);
            int GetDatabaseName(out String databaseName);  // gets database folder and name
            int GetServerType(out int serverType);   // 1=SQL, 2=Classic
            int GetCSIDEVersion(out String csideVersion);  // "DE 5.0 SP1", "DE 6.00", etc.
            int GetApplicationVersion(out String applicationVersion);  // "DE Dynamics NAV 5.0 SP1", "DE Dynamics NAV 6.0", etc.
            int GetCompanyName(out String companyName);
        }
        
        [ComImport, Guid("50000004-0000-1000-0011-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSCallbackEnum
        {
            int proc3([In,MarshalAs(UnmanagedType.Interface)] INSRec a);
            int proc4([In] int a, [In] string b, [In] string c);
            int proc5([In] int a, [In] string b);
            int proc6([In] int a, [In] string b);
            int proc7([In] int a, [In] string b, [In] string c, [In] string d, [In] int e, [In] int f);
        }
    
        [ComImport, Guid("50000004-0000-1000-0007-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSRec
        {
            int proc3([In] int a, [In] String b, [In] int c);
            int GetFieldValue([In] int a, out String b);
            int proc5([In,MarshalAs(UnmanagedType.Interface)] INSCallbackEnum a);
        }
    
        [ComImport, Guid("50000004-0000-1000-0006-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSTable
        {
            int proc3([In, MarshalAs(UnmanagedType.Interface)] INSRec a);
            int proc4([In, MarshalAs(UnmanagedType.Interface)] INSRec a);
            int proc5([In, MarshalAs(UnmanagedType.Interface)] INSRec a);
            int proc6([Out, MarshalAs(UnmanagedType.Interface)] out INSRec a);
            int proc7([In] int a, [In] string b);
            int proc8([In,MarshalAs(UnmanagedType.Interface)] INSCallbackEnum a);
            int proc9([In,MarshalAs(UnmanagedType.Interface)] INSCallbackEnum a);
            int proc10([In,MarshalAs(UnmanagedType.Interface)] INSCallbackEnum a, [In] int b);
            int proc11([In,MarshalAs(UnmanagedType.Interface)] INSRec a);
            int GetID(out int a);
            int proc13(out int a);
        }
    
        [ComImport, Guid("50000004-0000-1000-0010-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSAppBase
        {
            int GetTable([In] int a, [Out,MarshalAs(UnmanagedType.Interface)] out INSTable b);
            int GetInfos(out string a, out string databasename, out string company, out string d);
            int proc5();
            int proc6([In] int a);
            int proc7([In] string a);
            int proc8([In, MarshalAs(UnmanagedType.Interface)] INSCallbackEnum a, [In] int b);
        }
    
        [ComImport, Guid("50000004-0000-1000-0000-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSHyperlink
        {
            int Open([In] string link);
            int proc4(out int a);
        }
    
        [ComImport, Guid("50000004-0000-1000-0008-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSMenuButton
        {
            int proc3([In] string a);
            int proc4([In] int a, [In] string b, [In] string c);
        }
    
        [ComImport, Guid("50000004-0000-1000-0003-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSForm
        {
            int GetHyperlink(out String a);        // gets Hyperlink with Fieldcaptions
            int GetID(out String a);               // gets active object's type (Form) and ID
            int GetRec([Out,MarshalAs(UnmanagedType.Interface)] out INSRec a);
            int GetTable([Out, MarshalAs(UnmanagedType.Interface)] out INSTable a);
            int GetLanguageID(out int a);          // gets Language ID of application (1033, etc.)
            int proc8([Out, MarshalAs(UnmanagedType.Interface)] out INSMenuButton a);
            int proc9();
        }
    
        [ComImport, Guid("50000004-0000-1000-0002-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSApplication
        {
            int GetForm([Out,MarshalAs(UnmanagedType.Interface)] out INSForm form);
        }
    

    A new concept in the whole discussion is the MarshalAs directive, which permits the use of complex data types within COM Interop.
    Note also that the declaration of the interfaces is sufficient. We don't have to declare any COM coclasses because we are not creating new objects (NAV client must be already running).
    Here is a (minimal) sample C#-code-snippet which retrieves the value of field no. 2 (Name) for the open Customer Card. Note that the customer card has to be opened and should be activated.
    INSApplication IA = (INSApplication)RunningObjectTable.GetRunningCOMObjectByName("!C/SIDE!navision://client/run?");
    INSForm IF;
    IA.GetForm(out IF);
    INSRec IR;
    IF.GetRec(out IR);
    String str;
    IR.GetFieldValue(2, out str);
    System.Console.WriteLine(str);
    
    The RunningObjectTable.GetRunnigCOMObjectByName function can be found here:
    http://www.mycsharp.de/wbb2/thread.php?threadid=36340
    During my tests, I had a problem that every now and then the customer card was not the active form. In this case IF == null is returned and you will encounter a System.NullReferenceExeption in the above code. To get the customer card the active form, just switch to the NAV client and move the customer card around (do not work in full screen modus) in order to make it the active form (it is not sufficient to just click into the card form).
  • meeuwmeeuw Member Posts: 9
    great work gerdhuebner thanks a lot for sharing!

    INSApplication.GetForm returned only 0 for me, did you do something special in Navision (open a certain form or something like that?)
  • gerdhuebnergerdhuebner Member Posts: 155
    meeuw wrote:
    INSApplication.GetForm returned only 0 for me, did you do something special in Navision (open a certain form or something like that?)
    Well, as I mentioned above, in certain cases IF == null (in the sample code above), but meanwhile I think that this method is mainly useful, if you have opened the form before with the INSHyperlink.Open method.
    The following code worked for me in all cases. All you have to do is to start NAV Client and open a company (not even a form).
    INSApplication IA = (INSApplication)RunningObjectTable.GetRunningCOMObjectByName("!C/SIDE!navision://client/run?");
    INSHyperlink IH = (INSHyperlink)IA;  
    IH.Open("navision://client/run?target=Form%2021");    // open Form21, Customer Card
    INSForm IF;
    IA.GetForm(out IF);
    INSRec IR;
    IF.GetRec(out IR);
    String str;
    IR.GetFieldValue(2, out str);         // get value of Field2, Name
    System.Console.WriteLine(str);
    
    With the INSHyperlink.Open method you can open an arbitrary form showing an arbitrary record (just create a Link for an open form with the Edit, "Copy Link" from the main menu of NAV, paste this link into a texteditor and customize it - for some reason the direct paste into the visual studio editor doesn't work for me).
    With that method you can draw arbitrary data from NAV Client (supposed you have got a form in NAV Client).
    It would be nice however, if there would be a possibility to close the form again after use... - as a workaround one can use OnTimer trigger with Currform.Close to close the form automatically after 1 sec, e. g.

    Note that the data retrieved with the INSRec.GetFieldValue method can be manipulated within the form. It seems to be that the Rec variable of the form is used to get the field data. But the fields of Rec can be set manually in the OnAfterGetCurrRecord trigger of the form, e. g.
    So unfortunately this method cannot be used to retrieve for example the license no. of the NAV license from the virtual "License Information" table 2000000040 in a safe way...
  • janpieterjanpieter Member Posts: 298
    Argh! great topic!

    I'm trying to make an alternative object designer.

    Anyone knows how to implement the new/design/run buttons maybe using one of these interfaces? If we would now that we could build a much better object designer with many more functions.
    In a world without Borders or Fences, who needs Windows and Gates?
  • kinekine Member Posts: 12,562
    And why you want to do that, when the current designer is going to be changed? ;-)
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • janpieterjanpieter Member Posts: 298
    It would be nice however, if there would be a possibility to close the form again after use... - as a workaround one can use OnTimer trigger with Currform.Close to close the form automatically after 1 sec, e. g.

    The INSHyperlink interface Open returns an integer value.
    What happens if you pass this returned value into the Proc4 function of the same inteface? The first parameter also accepts an integer.
    In a world without Borders or Fences, who needs Windows and Gates?
  • gerdhuebnergerdhuebner Member Posts: 155
    janpieter wrote:
    ...What happens if you pass this returned value into the Proc4 function of the same inteface? ...
    Well, I never tried it out... - but, by the way, if you are developing a new object designer, don't forget a function to filter out only the objects that are different (newer/older) than already existing objects. This is, I think, the most urgent issue to be improved.
    Concerning the COM Interface for objects, etc. I would be mainly interested in a way to retrieve the actual NAV license no. from a running client (like the info that is displayed, if one clicks Extra/License Information), because this would allow something like a copy protection for addon components like COM automation servers, or OCXs, etc.) - this information cannot in general be found in the license file of the client, because the NAV license file (fin.flf) need only to be on the NAV server or in the SQL database, respectively, so the information can only be retrieved from a running NAV client process...
  • janpieterjanpieter Member Posts: 298
    Well, I never tried it out... - but, by the way, if you are developing a new object designer, don't forget a function to filter out only the objects that are different (newer/older) than already existing objects. This is, I think, the most urgent issue to be improved.

    The system will connect to a kind of 'source safe' environment. Will be much more advanced then this. And what to think of extra long version list codes?? ;) A lot more features then you can currently imagine.
    Concerning the COM Interface for objects, etc. I would be mainly interested in a way to retrieve the actual NAV license no. from a running client (like the info that is displayed, if one clicks Extra/License Information), because this would allow something like a copy protection for addon components like COM automation servers, or OCXs, etc.) - this information cannot in general be found in the license file of the client, because the NAV license file (fin.flf) need only to be on the NAV server or in the SQL database, respectively, so the information can only be retrieved from a running NAV client process...

    I gues the SERIALNUMBER C/AL instruction isn't good enough :). Maybe C/Front has a function? The function might be hidden somewhere in those interfaces.

    Maybe it is a good idea to create a DLL that supports the interface we have now with just the Proc functions, put it in downloads of Mibuso so everyone interested can start testing those functions and see what they do and post there findings in the download topic?? With all gathered knowledge we can create a more programmer friendly DLL. Who knows what extra possibilities come available.

    Has someone created something like this already??
    In a world without Borders or Fences, who needs Windows and Gates?
  • roumenfroumenf Member Posts: 4
    INSHyperlink.proc4(out int a) returns the handle of the main Navision window.
    so proc4 could be renamed to GetNavWindow, GetNavWindowHandle or something like that.

    by the way, great investigation!
  • janpieterjanpieter Member Posts: 298
    roumenf wrote:
    INSHyperlink.proc4(out int a) returns the handle of the main Navision window.
    so proc4 could be renamed to GetNavWindow, GetNavWindowHandle or something like that.

    Nice work. :thumbsup: =D> I already had my own function for this, although i have had an ocasion where this function did not prove relaible. So i think i'm going to replace the function with this one in the future.


    More functions please !! :)
    In a world without Borders or Fences, who needs Windows and Gates?
  • roumenfroumenf Member Posts: 4
    edited 2009-07-27
    on the next page..
  • roumenfroumenf Member Posts: 4
    here is something interesting:

    proc8 of INSAppBase is actually EnumTables.

    it takes as parameters a callback enumerator and a table ID. if table ID is 0, all Navision tables are enumerated, including the virtual ones.

    the function invokes proc6 (I named it "NextTable") with 1st parameter - the table ID, and 2nd - the table name.

    more news are on the way abt INSTable's enumeration methods. :)
  • roumenfroumenf Member Posts: 4
    1. proc10 of INSTable is "EnumFields(callbackEnum, languageID)"
    if languageID is 0 it uses the default Nav language.

    the function invokes proc7 of INSCallbackEnum (I named it "NextFieldDef") for each table field with params: a=fieldID, b=fieldName, c=fieldCaption, d=dataType, e=dataLength, f=1.

    2. proc9 of INSTable is "EnumRecords(callbackEnum)"

    the function invokes proc3 of INSCallbackEnum ("NextRecord") for each table record with params: a=theRecord

    3. proc8 of INSTable - invokes nothing, but as to me should invoke proc5 of INSCallbackEnum. a mistery. still investigating...

    4. proc5 of INSRec is "EnumFieldValues(callbackEnum)"

    the function invokes proc4 of INSCallbackEnum ("NextFieldValue") for each record field with params: a=fieldID, b=fieldValue, c=dataType.
  • janpieterjanpieter Member Posts: 298
    Great work!!

    I wished i had more time to be able to dig into this :cry:
    In a world without Borders or Fences, who needs Windows and Gates?
  • rotoflexrotoflex Member Posts: 14
    HI all,

    Just found this really interesting thread, thanks all for posting all this information! I was wondering if it is possible to invoke some of these same methods from inside Navision with automation objects? I tried referencing rotaccess.dll and invoking GetObjectByMoniker but I can't find any suitable parameter for that call that would actually work.

    I'm not sure if that's even the right way to access this problem, but hey, you got to start somewhere. So, any hints would be appreciated!

    Regards,

    rotoflex
  • meeuwmeeuw Member Posts: 9
    I'm creating a automation proxy object which can be used from navision (or any other scripting language), when I finish a beta version I will post it to this forum.
  • rotoflexrotoflex Member Posts: 14
    meeuw wrote:
    I'm creating a automation proxy object which can be used from navision (or any other scripting language), when I finish a beta version I will post it to this forum.

    How's your project progressing?
  • meeuwmeeuw Member Posts: 9
    Hi Rotoflex, I've uploaded the 1.0 version to mibuso downloads:

    http://www.mibuso.com/dlinfo.asp?FileID=1193
  • kinekine Member Posts: 12,562
    I will just add my actual interfaces definitions as I played with them for a while:
        [ComImport, Guid("50000004-0000-1000-0011-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSCallbackEnum
        {
            int NextRecord([In, MarshalAs(UnmanagedType.Interface)] INSRec record);
            int NextFieldValue([In] int fieldo, [In] string fieldValue, [In] string dataType);
            int NextFilterValue([In] int fieldNo, [In] string filterValue);
            int NextTable([In] int tableNo, [In] string tableName);
            int NextFieldDef([In] int fieldNo, [In] string fieldName, [In] string fieldCaption, [In] string dataType, [In] int dataLength, [In] int f);
        }
    
        [ComImport, Guid("50000004-0000-1000-0007-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSRec
        {
            int SetFieldValue([In] int fieldNo, [In] String value, [In] bool validate);
            int GetFieldValue([In] int fieldNo, out String value);
            int EnumFieldValues([In, MarshalAs(UnmanagedType.Interface)] INSCallbackEnum callback);
        }
    
        [ComImport, Guid("50000004-0000-1000-0006-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSTable
        {
            int Delete([In, MarshalAs(UnmanagedType.Interface)] INSRec rec);
            int Insert([In, MarshalAs(UnmanagedType.Interface)] INSRec rec);  
            int Modify([In, MarshalAs(UnmanagedType.Interface)] INSRec rec);  
            int Init([Out, MarshalAs(UnmanagedType.Interface)] out INSRec rec); 
            int SetFilter([In] int fieldNo, [In] string filterValue); 
            int EnumFilters([In, MarshalAs(UnmanagedType.Interface)] INSCallbackEnum callback); 
            int EnumRecords([In, MarshalAs(UnmanagedType.Interface)] INSCallbackEnum callback);
            int EnumFields([In, MarshalAs(UnmanagedType.Interface)] INSCallbackEnum callback, [In] int languageI);
            int Find([In, MarshalAs(UnmanagedType.Interface)] INSRec rec);
            int GetID(out int tableId);
            int proc13(out int a);
        }
    
        [ComImport, Guid("50000004-0000-1000-0010-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSAppBase
        {
            int GetTable([In] int a, [Out, MarshalAs(UnmanagedType.Interface)] out INSTable table);
            int GetInfos(out string servername, out string databasename, out string company, out string username);
            int StartTrans(); //start the write transaction in the client
            int proc6([In] bool a);
            int Error([In] string message);  //Display error in client, roll back the transaction
            int EnumTables([In, MarshalAs(UnmanagedType.Interface)] INSCallbackEnum a, [In] int flag); //meaning of flag is not known for me yet
        }
    
        [ComImport, Guid("50000004-0000-1000-0005-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSHook
        {
            int proc3([In, MarshalAs(UnmanagedType.Interface)] INSAppBase appBase);
        }
    
        [ComImport, Guid("50000004-0000-1000-0009-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSMenuButtonEvents
        {
            int proc3();
            int proc4([In] int a);
        }
    
        [ComImport, Guid("50000004-0000-1000-0004-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSApplicationEvents
        {
            int OnFormOpen([In,MarshalAs(UnmanagedType.Interface)] INSForm form);
            int proc4([In, MarshalAs(UnmanagedType.Interface)] INSForm form, [In] String b);
            int OnActiveChanged([In] bool active); //when the client get/lost focus
            int OnCompanyClose(); //when company/db is closed/re-opened
        }
    
        [ComImport, Guid("50000004-0000-1000-0000-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSHyperlink
        {
            int Open([In] string link);
            int GetNavWindowHandle(out int handle);
        }
    
        [ComImport, Guid("50000004-0000-1000-0008-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSMenuButton
        {
            int proc3([In] string a);
            int proc4([In] int a, [In] string b, [In] string c);
        }
    
        [ComImport, Guid("50000004-0000-1000-0003-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSForm
        {
            int GetHyperlink(out String hyperlink);        // gets Hyperlink with Fieldcaptions
            int GetID(out String formId);               // gets active object's type (Form) and ID
            int GetRec([Out, MarshalAs(UnmanagedType.Interface)] out INSRec recod);
            int GetTable([Out, MarshalAs(UnmanagedType.Interface)] out INSTable table);
            int GetLanguageID(out int languageID);          // gets Language ID of application (1033, etc.)
            int GetButton([Out, MarshalAs(UnmanagedType.Interface)] out INSMenuButton menuButton); //never succeeded to call it correctly, each time end with error in NAV client...
            int proc9();
        }
    
        [ComImport, Guid("50000004-0000-1000-0002-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSApplication
        {
            int GetCurrentForm([Out, MarshalAs(UnmanagedType.Interface)] out INSForm form);
        }
    

    As you can see, you can enumerate records from any table, filter them, insert/modify/delete them. Even you can subscribe for some events from the client. I just didn't find the way to work with the INSMenuButton (never retrieved it successfully), INSMenuButtonEvents (because I never got INSMenuButton), INSHook (do not know where to take the ConnectionPoint supporting it).
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • DakkonDakkon Member Posts: 192
    Would you mind posting an example of using INSApplicationEvents? I'm a little unsure of how you are supposed to subscribe to the events. ](*,) Thanks a ton to everyone on this thread that put in work on deciphering this stuff. Also congrats on making number 61 on the list Kine :mrgreen:
    Thad Ryker
    I traded my sanity for a railgun :mrgreen:
  • kinekine Member Posts: 12,562
    It is not so hard. The object implements IConnectionPointContainer interface. Thus you can use this interface to hook on this events. Simplified code can look like this:
                int cookie;
                IConnectionPoint CP;
                IConnectionPointContainer connCont = navObject as IConnectionPointContainer;
                Guid g = new Guid("50000004-0000-1000-0004-0000836BD2D2"); //the interface guid
                appEvents = new NAVApplicationEvents();
                connCont.FindConnectionPoint(g, out CP);
                if (CP != null)
                {
                     CP.Advise(appEvents, out cookie);
                }
    
    Where NAVApplicationEvents is class implementing the INSApplicationEvents interface... You need to release the connection point when approprieate etc.
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • DakkonDakkon Member Posts: 192
    Ahh ok makes sense :) . I will try this out when I get home tonight, thanks for the help. I'm working on creating a clean .Net wrapper around all of this that obscures most of the dirty work and creates standard .Net event handlers, and IEnumerable classes etc. I'll make sure to post my resulting project to Mibuso for anyone else interested in making .Net tools that communicate with a Navision client :D . Thanks a TON Kine, I would be months behind where I'm at now if not for all of the interfaces you have worked out!
    On a somewhat related note one of my other projects is a Navision Object Parser that will parse any Navision text object file into a .Net class instance. Currently properties are only parsed as a text blob but I am adding contextual parsing of all the property types such that you should be able to do something like the following to retrieve the spanish caption for an instance of the customer table:
    string SpanishCaption = Customer.Properties["Caption"]["ESM"].Value.ToString();
    
    The project includes a serializer for writing to/from the Navision text object format. All classes are also serializable using the binary and xml serializers in the .Net framework. I will be open sourcing my parser (probably under Apache 2 license) once I have it finished and cleaned up. I'm shooting for a release in second quarter of next year.
    Thad Ryker
    I traded my sanity for a railgun :mrgreen:
  • kinekine Member Posts: 12,562
    Ok, thanks for the info. This serialization could be usable for creating custom language for visual studio to be able to edit C/AL objects in VS with all the features around... this is tip for others who have time and want to learn something new. It will be very good to have VS addin for C/AL.
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • DakkonDakkon Member Posts: 192
    I'm actually working on an editor project too ;)
    I started with visual studio but after various frustrations went with a third party editor/parser framework which I have built upon. I actually have a working editor with auto-formatting, syntax highlighting and various code completion features as well as "Go To Definition" support. I'm still a way from having a finished product. My editor project is the one piece I will not be open sourcing, partly because I hope to make a little profit from all the work I have put into it and also because I'm using licensed components within it. I will offer an express (free) edition with most of the features available. Things like refactoring and some other tools will be available in a professional (pay) version. I am looking for more testers right now if you are interested.
    My goal is to release most of the projects I'm building my editor upon as open source, so that other people in the community can create some very cool tools. I figure if someone uses my own tools to build a better editor than my own, then I just need to work that much harder ;)
    Thad Ryker
    I traded my sanity for a railgun :mrgreen:
  • DakkonDakkon Member Posts: 192
    Ok so one last question about the interfaces :D
    I was under the mistaken assumption that one of the methods would return an instance of an INSCallbackEnum, but apparently not. Where do you get this instance from?

    After looking at it a bit I'm guessing I create a new class that implements the INSCallbackEnum and then pass that instance into the methods that take one, and they in turn call the appropriate callback methods on my class?
    Thad Ryker
    I traded my sanity for a railgun :mrgreen:
  • kinekine Member Posts: 12,562
    Yes, your assumption is correct. Just create own class implementing the interface and pass instance of this class as a parameter... :thumbsup:
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • DakkonDakkon Member Posts: 192
    The first discovery of my digging into these is that the flag parameter is a table number. If you pass in 0 it returns all tables, otherwise it only enumerates the one table specified. I'm not really sure what the point of that is, though I guess you could use it to get the table name since the table object returned from GetTable() provides no method to retrieve the name.
        [ComImport, Guid("50000004-0000-1000-0010-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface INSAppBase
        {
            int GetTable([In] int a, [Out, MarshalAs(UnmanagedType.Interface)] out INSTable table);
            int GetInfos(out string servername, out string databasename, out string company, out string username);
            int StartTrans(); //start the write transaction in the client
            int proc6([In] bool a);
            int Error([In] string message);  //Display error in client, roll back the transaction
            int EnumTables([In, MarshalAs(UnmanagedType.Interface)] INSCallbackEnum a, [In] int tableNo); // tableNo is either a specific table number or 0 for all tables
        }
    

    Something else worth metioning is that while EnumTables does return some of the virtual tables, it does not return all of them. Strangely enough, however, you can still use GetTable() to retrieve an instance to any of those tables :-k . I'll continue to post further discoveries as I build my wrapper.
    Thad Ryker
    I traded my sanity for a railgun :mrgreen:
Sign In or Register to comment.