GotCAL.CSideInstaller Source

Monday, March 24, 2008 by Administrator

(note this is a post in progress, please post comments etc.) 

This is the second part of Dynamics NAV and the ROT Table, which was posted in december 2007. In this article i will discuss some of the uses of the interfaces, and also give give you access to the sourcecode for a simple application created in Visual Studio 2005.

First off let's take a look at the Interface for the NSObjectDesigner class:

IObjectDesigner.cs:
namespace GotCAL.CSIDEInstaller
{
    using System;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;

    [ComImport, Guid("50000004-0000-1000-0001-0000836BD2D2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IObjectDesigner
    {
        [PreserveSig, DispId(1)]
        int ReadObject([In] int objectType, [In] int objectId, [In] IStream destination);
        [PreserveSig, DispId(2)]
        int ReadObjects([In] string filter, [In] IStream destination);
        [PreserveSig, DispId(3)]
        int WriteObjects([In] IStream source);
        [PreserveSig, DispId(4)]
        int CompileObject([In] int objectType, [In] int objectId);
        [PreserveSig, DispId(5)]
        int CompileObjects([In] string filter);
        [PreserveSig, DispId(6)]
        int GetServerName(out string serverName);
        [PreserveSig, DispId(7)]
        int GetDatabaseName(out string databaseName);
        [PreserveSig, DispId(8)]
        int GetServerType(out int serverType);
        [PreserveSig, DispId(9)]
        int GetCSIDEVersion(out string csideVersion);
        [PreserveSig, DispId(10)]
        int GetApplicationVersion(out string applicationVersion);
        [PreserveSig, DispId(11)]
        int GetCompanyName(out string companyName);
    }
}

as you can see we import the COM library, which we discovered in the registry. In the registry it shows it has 14 methods, so far i only know 11 of them. These were found using reflection on the Celenia Version Control software, and it is also the way (i guess!) the Developers Toolkit can extract the ojbects through the CSide Client.

Most of the methods are very simple, and just return a string, and thus they are very easy to implement, eg.:

public string GetDatabaseName()
        {
            string databaseName;
            int num = this._objectDesigner.GetDatabaseName(out databaseName);
            return databaseName;
        }

As most of you who have been playing around with this after the first post have discovered the biggest issue, is reading and writing the streams that holds the actual objects. At first I used "AuthHelper.dll" a library that somehow could handle the reading and writing of streams. But here is a StreamSupport class that handles it in .NET:

  namespace GotCAL.CSIDEInstaller
{
    using System;
    using System.IO;
    using System.Runtime.InteropServices.ComTypes;

    public class StreamSupport
    {
        public static unsafe IStream ToIStream(Stream stream, ref IStream comStream)
        {
            byte[] buffer = new byte[stream.Length];
            stream.Read(buffer, 0, buffer.Length);
            uint num = 0;
            IntPtr pcbWritten = new IntPtr((void*) &num);
            comStream.Write(buffer, buffer.Length, pcbWritten);
            return comStream;
        }

        public static unsafe MemoryStream ToMemoryStream(IStream comStream)
        {
            MemoryStream stream = new MemoryStream();
            byte[] pv = new byte[100];
            uint num = 0;
            IntPtr pcbRead = new IntPtr((void*) &num);
            do
            {
                num = 0;
                comStream.Read(pv, pv.Length, pcbRead);
                stream.Write(pv, 0, (int) num);
            }
            while (num > 0);
            return stream;
        }
    }
}

Hope to get some feedback from all you out there, please post a comment, idea, or just a quick hello.

Download the sample application here.

18 comment(s) for “GotCAL.CSideInstaller Source”

  1. Bruce McMillan Says:
    Hi,

    have tried to down load the source code, but my virus scanner complains it has an infected file.

    can you please re-post the source code or fwd via email

    Thanks

    Bruce
  2. Jan Pieter Says:
    Hello,

    We have some great benefits with this invention. Thanks to everyone sharing this information.

    Gheubner i changed the readobject to readobjects like you said. I discovered that in Navision version 3.70 and earlier this doesn't work in localized version. Because for instance the field "type" is called "Soort" in the Dutch version.

    There is a remedy. Instead of using words just us numbers. To extract codeunit 1 in a save way, Navision version and language independent use this filter string with the ReadObjects function:

    WHERE(Field1=CONST(5),Field3=CONST(1))

    This was just my little contribution back to the community ;)
  3. Jan Pieter Says:
    Hello,

    We have some great benefits with this invention. Thanks to everyone sharing this information.

    Gheubner i changed the readobject to readobjects like you said. I discovered that in Navision version 3.70 and earlier this doesn't work in localized version. Because for instance the field "type" is called "Soort" in the Dutch version.

    There is a remedy. Instead of using words just us numbers. To extract codeunit 1 in a save way, Navision version and language independent use this filter string with the ReadObjects function:

    WHERE(Field1=CONST(5),Field3=CONST(1))

    This was just my little contribution back to the community ;)
  4. Christian Says:
    Very interesting! I am currently looking into how to integrate the C/SIDE environment with a standard version control system, and obviously programmatic import/export of objects as text is an essential ingredient. Do any of you know if there already exists such integrations?
  5. Tim Says:
    This seems to be a great tool, I am testing the demo application and I am able to use a lot of the functions. I am however having an issue with the read objects example on the menu. I have tried several different versions of Navision but I always seem to receive the error "Method returned an error. HRESULT = 0x80040155".

    Have I missed a step in the installation or am I missing a dll or ocx on my workstation. I have tried on several other workstations but I have not been able to get this function to work.

    Tim
  6. Tim Says:
    This seems to be a great tool, I am testing the demo application and I am able to use a lot of the functions. I am however having an issue with the read objects example on the menu. I have tried several different versions of Navision but I always seem to receive the error "Method returned an error. HRESULT = 0x80040155".

    Have I missed a step in the installation or am I missing a dll or ocx on my workstation. I have tried on several other workstations but I have not been able to get this function to work.

    Tim
  7. ghuebner Says:
    by the way:
    an implementation example for the function GetRunningCOMObjectByName can be found here
    http://www.mycsharp.de/wbb2/thread.php?threadid=36340
  8. ghuebner Says:
    @Soren:
    There is annother way to handle the IStream interface without the need for unsafe code or MemoryStream. The essential point is, that some methods of IStream use int* (Pointer to int) as parameters. This gives rise to a problem, if one implements those pointers in the managed memory area as IntPtr, e.g. - The trick is to simply define the pointers in the unmanaged memory area (global heap). The following (minimized) code-snipet copies an object to a file:
    IntPtr pLEN = Marshal.AllocHGlobal(4);
    IStream pOutStm;
    CreateStreamOnHGlobal(0, true, out pOutStm);
    INSObjectDesigner IOD = (INSObjectDesigner)RunningObjectTable.GetRunningCOMObjectByName("!C/SIDE!navision://client/run?");
    int res = IOD.ReadObjects("WHERE(Type=CONST(Table),ID=CONST(3))", pOutStm);
    pOutStm.Seek(0, 0, pLEN);
    FileStream outputStream = new FileStream(@"C:\Temp\Table3.txt", FileMode.Create, FileAccess.Write);
    int cnt, LEN = 4096;
    byte[] buffer = new byte[LEN];
    do
    {
    pOutStm.Read(buffer, LEN, pLEN);
    cnt = Marshal.ReadInt32(pLEN);
    outputStream.Write(buffer, 0, cnt);
    } while (cnt == LEN);

    The essential point is the variable pLEN which resides on the (unmanaged) global memory heap.
  9. ghuebner Says:
    @Benjamin:
    The problem with the native language object caption, etc. seems to be only with the method ReadObject. If you try ReadObjects("WHERE(Type=CONST(Table),ID=CONST(3))",pOutStm), e.g., instead, the object is read out exactly as with the classic text format object export from NAV.

  10. Soren Says:
    Take a look at this thread on mibuso: http://mibuso.com/forum/viewtopic.php?t=28707
  11. Benjamin Says:
    Hello,

    Has somebody also the problem that the object name (first line of the) file contains the "Object Caption" and not the object names and that the boolean values (Yes,No) are translated into the language which is chosen in the NAV Client?

    If yes does someone have a solution for this problem?
  12. HaZzard Says:
    Hey Soren,
    sry it don't works :(
    He dose not Like:
    Dim pcbRead As New IntPtr(DirectCast(AddressOf num, Void*))

    Regards HaZz
  13. Soren Says:
    I think this should do it in VB.NET:

    Public Shared Function ToMemoryStream(ByVal comStream As IStream) As MemoryStream
    Dim stream As New MemoryStream
    Dim pv As Byte() = New Byte(100 - 1) {}
    Dim num As UInt32 = 0
    Dim pcbRead As New IntPtr(DirectCast(AddressOf num, Void*))
    Do
    num = 0
    comStream.Read(pv, pv.Length, pcbRead)
    stream.Write(pv, 0, CInt(num))
    Loop While (num > 0)
    Return stream
    End Function


    Let me know ...
  14. HaZzard Says:
    Hey,

    really Nice Work!!!!
    But i got one Problem!
    Ive tried to Convert this Code to VB.NET but i got an Problem with this Function:

    public static unsafe MemoryStream ToMemoryStream(IStream comStream)
    {
    MemoryStream stream = new MemoryStream();
    byte[] pv = new byte[100];
    uint num = 0;
    IntPtr pcbRead = new IntPtr((void*) &num);
    do
    {
    num = 0;
    comStream.Read(pv, pv.Length, pcbRead);
    stream.Write(pv, 0, (int) num);
    }
    while (num > 0);
    return stream;
    }

    any help would be really nice!
    Best Regards HaZz
  15. Jacob Says:
    Hi,

    I have been using your code as a base to write an object re-numbering utility.

    Everything works great but I cant upload a memory stream back into nav using

    nav.WriteObjectFromStream(memStream);

    It doesn't work in your example code or the modified code I have written. It returns "HRESULT 0x80004005 import stopped at line 7" which is not very helpful.

    Using different encoding for the memory stream it gives the same error but gets up to a maximum of around line 100.

    Please, how did you get this to work? I need help so please e-mail me ASAP.

    Regards,
    Jacob
  16. Arjan Says:
    This is great. If I understand this correctly this would enable us to make things such as an IDE or some versioning control system.

    From which NAV version can i use this? 3.6/4/5?

    If i can use this on older databases, i would very much like to make an improved editor.

    (It would be easy to create an editor with syntax highlighting, unlimited line length, code completion)
  17. Soren Says:
    Is your Dynamics NAV client running? In order for it to be referenced through ROT, it needs to be running. (Running Object Table).
  18. Stefano Says:
    Hello,

    I'm using Nav 5.0 Italian version and I'm testing your code.
    After the statement
    this._objectDesigner = getObj as IObjectDesigner
    objectDesigner results null.
    Any idea?

    Bye

Leave comment:


(not shown)


(optional - remember http://)