Monday, March 11, 2013

Starting Fresh

If you did not know before, I have a set of tutorials on how to get up and running on creating CC3 add-ins.  But, they were written for the Visual Studio 2005 Express Editions.  Anyone that can do math knows they were done over 8 years ago, and in the computer age that is almost forever.

So, I will be starting to “sort of” redo them.  They are individually large and complex, so I will be breaking them up into bite size chunks here on this blog.

Lets Start!

The current version of Visual Studio Express is 2012.  Unfortunately, it only works on Windows 8.  Because of this limitation, we will be using the Visual Studio Express 2010 versions.

To download VS 2010 C++ Express go to:

http://www.microsoft.com/visualstudio/eng/downloads#d-2010-express

You will also what to download and install the associated windows SDK:

http://www.microsoft.com/en-us/download/details.aspx?id=8279

Next time, we will create a native exe in C++ to prove that we have everything downloaded and working properly.

Saturday, February 4, 2012

Creating C# objects mapped to CC3 objects

When Simon and I discussed how to fully integrate the city creator, code named “Map Invoker”, the idea of it working like a Wizard surfaced. 

With a wizard like interface, each layer of objects (Waterways, Roads, Walls, etc.) would each be generated by a different page on the wizard.  A “generate” button would draw the objects directly in CC3.  If the results were not what the user wanted, the user could press the “generate” button again and the old objects would be removed and the new ones drawn.  Once the user liked the output, the “next” button would take the user to the next wizard page.

Now this sounded like a great design to me.  But …. I had some technical issues to overcome and some design work to make life a little easier.

The main technical issue was that the built-in dialog system was not made to ever run custom code and call XP functions while the dialog was still visible.  This was solved by using C# to write the Dialog code and communicating with CC3 via a simple callback class and a delegate.  I will go into this solution in depth in a later post.  Right now I want to go over the design work I did to make life easier passing data back and forth between C++ & C#.

Here is the diagram of my classes thus far.  They are simple value classes, with no behavior.  In a previous experiment, I created a REAL object (data & behavior) but because of the fact that a real class crosses native and managed code, it worked great in a all C++/cli environment, but I could not use it in C#.  So I decided to split data and behavior into two separate dlls.  The cc3objects.dll, a C# set of value classes and cc3actions.dll, the C++/cli dll that implements the behavior.

diagram

Now, what this does is let me create an entire object, or set of objects that can be directly converted to native CC3 objects.  So, I actually build my objects in C# then pass them to my C++/cli XP dll.  Plus, once I get done with this project everyone will be able to use these dlls.

Thursday, October 6, 2011

Looking under the hood of the .FCW file format

First a warning – The .FCW file format is BINARY!  If you do not feel comfortable playing with bits and bytes, this may not be for you.  But if you do enjoy this type of challenge, the .FCW file format is one of the best ways to output to CC3.
Imagine you have a random maze generator and you want to output it to CC3.  You could export a script.  It would redraw your maze one line at a time.  
But there are also some problems with scripts:
  • You have to run them.  This may sound obvious but consider that you have to either know the text command to open and run a script file, or you need to know where in the menu system it is.
  • Scripts are slow.  CC3 will have to take your script and run it line-by-line.  Its as if you had set down and typed in the commands directly into CC3.
  • Scripts are not exactly fragile, but they are not very robust either.  And, if your script fails, your users are the ones that are going to get frustrated.
Where as if you exported a .FCW file, simply the action of opening the file is all that is needed. So … if you are still with me, here we go!
One last twist – the .FCW file could be compressed.
The .FCW file format is made up of many different “Blocks” of data.  The first 4 bytes of each block (except for the first block) contains the number of bytes that the following block contains.  It starts with the FileID block.  The FileID block is the only block that is guaranteed to be uncompressed.  This 128 byte block contains quite a bit of general info on the file.  It identifies what type of file it is to other programs, and the version and sub-version number of the file format is was built with.  Last, but not least, it informs the reader that bytes after byte 128 are compressed or not.  (To save an uncompressed file, after you have clicked "Save As ...", you will be presented with the save file dialog.  If you click on the options button, you will be presented with a small dialog box.  Uncheck the "File Compression" option)
For this first blog post on the .FCW file format, I will show you how to read a binary file into a byte array and how to display it, byte-by-byte in a textbox similar to all the binary editors display it.  Being able to look inside a binary file will come in very handy in the future.  Last but not least I will show you FILEID object.
Code Snippet – How to read an entire file into a byte array
  1. public byte[] ConvertFileToByteArray(string fileName)
  2. {
  3.     byte[] buffer;

  4.     using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
  5.     {
  6.         using (var binaryReader = new BinaryReader(fileStream))
  7.         {
  8.             buffer = binaryReader.ReadBytes((int)new FileInfo(fileName).Length);

  9.             binaryReader.Close();
  10.         }

  11.         fileStream.Close();
  12.     }

  13.     return buffer;
  14. }
Once the file is read into a byte array, I pull the first 128 bytes out of the file byte array, using an extension method to Arrays to create sub-arrays.
Code Snippet – SubArray extension method
  1. public static class Extensions
  2. {
  3.     public static T[] SubArray<T>(this T[] data, int index, int length)
  4.     {
  5.         var result = new T[length];

  6.         Array.Copy(data, index, result, 0, length);

  7.         return result;
  8.     }
  9. }
Then I feed the sub-array to my FILEID object.  This object converts the bytes into the fields of the FILEID object.  We can then check the Compressed property.  If the file is compressed, the rest of the data is meaningless, so if the file is compressed, I set the background color of the textbox to a Rose color.
Code Snippet
  1. using System;
  2. using System.Text;

  3. namespace DisplayBinaryFile
  4. {
  5.     public class FILEID
  6.     {
  7.         #region Fields
  8.         public char[] ProgId = new char[26];
  9.         public char[] VerText = new char[4];
  10.         public char[] VerTextS = new char[14];
  11.         public byte[] DosChars = new byte[3];
  12.         public byte DBVer;
  13.         public bool Compressed;
  14.         public byte[] Filler = new byte[78];
  15.         public byte EndFileID;
  16.         #endregion

  17.         public int Length { get; set; }

  18.         public FILEID(byte[] buffer)
  19.         {
  20.             ProgId = Encoding.ASCII.GetChars(buffer.SubArray(0, 26));
  21.             VerText = Encoding.ASCII.GetChars(buffer.SubArray(26, 4));
  22.             VerTextS = Encoding.ASCII.GetChars(buffer.SubArray(30, 14));
  23.             DosChars = buffer.SubArray(44, 3);
  24.             DBVer = buffer.SubArray(47, 1)[0];
  25.             Compressed = BitConverter.ToBoolean(buffer.SubArray(48, 1), 0);
  26.             Filler = buffer.SubArray(49, 78);
  27.             EndFileID = buffer.SubArray(127, 1)[0];

  28.             Length = 128;
  29.         }

  30.         public byte[] GetBytes()
  31.         {
  32.             var returnValue = new byte[Length];

  33.             Array.Copy(Encoding.ASCII.GetBytes(ProgId), 0, returnValue, 0, 26);
  34.             Array.Copy(Encoding.ASCII.GetBytes(VerText), 0, returnValue, 26, 4);
  35.             Array.Copy(Encoding.ASCII.GetBytes(VerTextS), 0, returnValue, 30, 14);
  36.             Array.Copy(DosChars, 0, returnValue, 44, 3);
  37.             Array.Copy(BitConverter.GetBytes(DBVer), 0, returnValue, 47, 1);
  38.             Array.Copy(BitConverter.GetBytes(Compressed), 0, returnValue, 48, 1);
  39.             Array.Copy(Filler, 0, returnValue, 49, 78);
  40.             Array.Copy(BitConverter.GetBytes(EndFileID), 0, returnValue, 127, 1);

  41.             return returnValue;
  42.         }
  43.     }
  44. }
Here is a link to the entire Visual Studio 2010 project

Sunday, May 1, 2011

A more modern example of Intercom

In the last post, I introduced Intercom with an old example I wrote using VB6.  This post will be a much more modern example using C#.

I’ve also included a “Round Trip” example where the command is initiated via CC3.  By adding this small macro, you now have a command that draws a diamond on the screen.

Code Snippet
  1. MACRO DIAMOND
  2. GP TEMP ^DCenter:
  3. SENDM 2 TEMP
  4. ENDM

What this does is as the user to get a point (GP) and then send it via Intercom to the c# application.

Once it gets to the c# code, it takes the string from CC3 (all data sent from CC3 via Intercom is in strings), splits it on the comma and converts the two substrings into doubles.  Then it creates a command string and passes it back to CC3.

Code Snippet
  1. var strNumbers = System.Text.Encoding.ASCII.GetString(bytMsg).Split(',');
  2. var x = double.Parse(strNumbers[0]);
  3. var y = double.Parse(strNumbers[1]);
  4.  
  5. var strDiamond = "LINE \n" +
  6.                     (x - 100) + "," + y + " \n" +
  7.                     x + "," + (y - 100) + " \n" +
  8.                     (x + 100) + "," + y + " \n" +
  9.                     x + "," + (y + 100) + " \n" +
  10.                     (x - 100) + "," + y + " \n\n" +
  11.                     "ENDM";
  12.  
  13. icSendMsg(20, strDiamond);

Here is a link to the C# portion

Monday, April 11, 2011

An introduction to Intercom

Intercom was originally written in C code for inclusion in C programs. For C programmers this was quite acceptable. Unfortunately the vast majority of non-professional programmers do not work in C, so something needed to be done.

I've taken the original C code and placed it into a plain C DLL. Most higher level languages like C#, VB.Net, VB6 and Delphi can utilize the subroutines in a C DLL. When I originally wrote about Intercom (way back in 1999 – You can tell, because the title of the example application reads: “Intercom with CC2), I was a professional Visual Basic programmer I used VB6 as my language of choice in the following example.

First, what is intercom? Intercom is code that creates a memory-mapped file that both CC3 and your program can use. What this means is that in memory a chuck of space is set aside, half for you and half for CC3. You can only read your half and you can only write to CC3's half. CC3 on the other hand can do just the opposite.

Whenever a new message is written, a 'New mail flag' is raised. CC3 looks for a raised flag automatically. Your program needs to check periodically.

Second, what is it that is sent back and forth between CC3 and a program? Intercom sends an integer and a string back and forth. To CC3 these have very strict meanings. The integer is part of the name of the macro it will now run and the string is used as input data.

If you sent CC3 via intercom the number 123 then the macro CC3 will attempt to run in named RCV123 and the string will now reside inside the variable RCVDATA.

CC3 ships with an important macro: RCV20.  If you run the exe in the VBIntercom zip file (Intercom.exe) and enter 20 and LINE 0,0 1000,1000 you will see.

image

See? The macro RCV20 contains nothing but RCVDATA in it. This macro will run whatever text is sent to it, so this means that the calling program can dynamically build and run scripts/control CC3.

To connect to the DDL, you need to use Declare Statements. See the code in the VB6 form for the exact syntax, later l will post C# examples.

Here is a link to the original C code for Intercom.

Here is a link to the Intercom code wrapped into a plain C DLL plus the old VB6 example.

Thursday, December 9, 2010

Creating a Path element without adding it to the Drawing List

Wow, its been a while since I’ve written a post here.  The holidays are always a busy family blur.  Well, here is the latest installment …

The path/poly element is the most used element in CC3.  You really cannot get much done with it.  Try building anything in CC3 without it (Sure you can just use more primitive elements and use multipoly & group for joining and filling, but really you are just re-creating the path/poly element).  So if you want to get something done in an XP, odds are you are going to be working with paths/polys.

If you have been following this blog or the cc2-dev-list on groups.yahoo.com, you have seen code that creates path.  The code always creates a empty drawing list element then works on this record entity to “change” it into a path/poly.  I’ve always had a bit of a bad feeling about that.  It seems that it would be more “correct” to only add completed elements to the drawing list.  So, after quite a bit of frustration, I’ve successfully  worked out the code to create a path/poly element independent of the drawing list.

The code below creates a closed path (poly) with with four points:

Code Snippet – Build a PATH2 Element
  1. PATH2 *pathPtr = (PATH2*)malloc(sizeof(PATH2));
  2.  
  3. pathPtr->CStuff.ERLen = sizeof(PATH2);    // entity record length
  4. pathPtr->CStuff.EType = ET_PATH2;         // entity type code
  5. pathPtr->CStuff.EFlags = 0;               // erase/select bits
  6. pathPtr->CStuff.EFlags2 = 0;              // extra flags
  7. pathPtr->CStuff.EColor = 1;               // entity color
  8. pathPtr->CStuff.EColor2 = 1;              // fill (2nd) color
  9. pathPtr->CStuff.EThick = 0;               // pen thickness 0..25.4 mm
  10. pathPtr->CStuff.WPlane = 0;               // workplane (0 = XY plane)
  11. pathPtr->CStuff.ELayer = 1;               // layer
  12. pathPtr->CStuff.ELStyle = 0;              // line style (0=solid)
  13. pathPtr->CStuff.GroupID = 0;              // group id (0 = not grouped)
  14. pathPtr->CStuff.EFStyle = 0;              // fill style (0=hollow)
  15. pathPtr->CStuff.LWidth = 0.0;             // line width
  16. pathPtr->CStuff.Tag = 0;                  // entity tag id
  17. pathPtr->Path.SmType = 0;                 //SmType
  18. pathPtr->Path.SRes = 8;                   //SRes
  19. pathPtr->Path.SParm = 0.0;                //SParm
  20. pathPtr->Path.EParm = 0.0;                //EParm
  21. pathPtr->Path.Count = 0;                  //Count
  22. pathPtr->Path.Flags = 0;                  //Flags
  23. pathPtr->Path.unused = 0;                 //unused
  24.  
  25. GetCStuff((pENTREC)pathPtr);
  26.  
  27. pathPtr = (PATH2*)realloc(pathPtr, sizeof(PATH2) + sizeof(GPOINT2) * 4);
  28.  
  29. pathPtr->CStuff.ERLen = sizeof(PATH2) + sizeof(GPOINT2) * 4;
  30.  
  31. pathPtr->Path.Nodes[0].x = 0.0;            //Nodes[0]
  32. pathPtr->Path.Nodes[0].y = 0.0;            //Nodes[0]
  33. pathPtr->Path.Nodes[1].x = 0.0;            //Nodes[1]
  34. pathPtr->Path.Nodes[1].y = 1000.0;         //Nodes[1]
  35. pathPtr->Path.Nodes[2].x = 1000.0;         //Nodes[2]
  36. pathPtr->Path.Nodes[2].y = 1000.0;         //Nodes[2]
  37. pathPtr->Path.Nodes[3].x = 1000.0;         //Nodes[3]
  38. pathPtr->Path.Nodes[3].y = 0.0;            //Nodes[3]
  39.  
  40. pathPtr->Path.Count = 4;
  41. pathPtr->Path.EParm = 4.0;
  42. pathPtr->Path.Flags = NL_CLS;
  43.  
  44. pENTREC pEntRec = DLApnd(NULL, (pENTREC)pathPtr);
  45.  
  46. EDraw(pEntRec);
  47.  
  48. free(pathPtr);

Thursday, November 4, 2010

Quick development tip

For those of us that are not super c/c++ gurus, like me, debugging an XP add-on can be quite challenging. Over the years I've tried numerous different ways to see into the inner workings of the code.
  • I first tried placing lots of InfoBox() calls to report on the code state.  This worked to a point, but when I needed to see what way going on in a dynamic cursor, or anywhere that the user needed to interact with CC3, the InfoBox() tended to interfere.  Plus building a string, that usually included numbers, just to output it to a message box seemed like a lot of work.
  • Then I added a bunch of code to write to a text file.  This worked great but is was quite a bit of code and if I forgot to remove it, the end users got debug files that the shouldn't have.  Plus you still needed to do all that code for creating your output string.  And, if you wanted the file readable, you needed to include newlines, tabs etc.
Well, when we are first all taught c/c++ it is via the command line.  The command printf/cout is king.  We learn to output data to the console window in very simple ways.  As we start to develop more and more complex applications, we could, for development purposes, interject tons of these little commands to see exactly what was happening.

Unfortunately, with the development of windowing software we no longer have the console window.  Or do we?

If you are developing an XP add-on, (or any windows application for that matter) adding a console window is quite easy.  Simply slip into your code two commands: AllocConsole & FreeConsole.

Simply change your code to match the code bellow and start some searious debugging!

Code Snippet - Display Console Window
  1. BOOL WINAPI DllMain (HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved)
  2. {
  3.     switch (dwReason)
  4.     {
  5.         case DLL_PROCESS_ATTACH:
  6.         {
  7.             AllocConsole();
  8.             MyXP.ModHdl=hDLL;
  9.             XPRegCmd(&MyXP);
  10.             break;
  11.         }
  12.         case DLL_PROCESS_DETACH:
  13.         {
  14.             FreeConsole();
  15.             XPUnregCmd(&MyXP);
  16.             break;
  17.         }
  18.     }
  19.  
  20.     return TRUE;