The ProjectWise SDK is a set of header files and link files that allow a C++ program to interface with the DLL files in the bin directory of ProjectWise. You can interact with those DLL files with a C# program but in a different way. The ProjectWise DLL files are standard C++ and do not have any COM or assembly interface to interact with. In C# you have to use what is called Platform Invoke (p/Invoke). This involves declaring each function you wish to call in a specific way to determine how the data is going to be passed from your program to the DLL and from the DLL to your program. There are many web sites that talk about marshaling and p/Invoke. There seem to be many approaches to accomplishing the same thing. I am going to relate the methods that worked for me.
You need System.Runtime.InteropServices in order to create the DLLImport definitions. This can be added with a using statement at the top of the file that has your API function definitions. When I have an API function I want to use, I copy the definition from the help and then apply the tag and change the data types.
Example from the API help:
LONG aaApi_SelectProject(LONG lProjectID)
Declaration for C# :
[DllImport("c:\\Program Files\\Bentley\\ProjectWise\\bin\\dmscli.dll")] public static extern int aaApi_SelectProject(int lProjectId);
With this declaration in place you can use the function normally in your code. It is best practice to create a library of functions in their own class and namespace to share among the programs you write for consistency. However you may find that your declarations and how your code interacts with them vary some between 32-bit and 64-bit functions.
Here is a list of mappings I use from API help to C# declaration.
LONG int ULONG uint LPLONG ref int BOOL bool HAADMSBUFFER IntPtr LPCTSTR string (as argument), IntPtr (as return value) LPWSTR StringBuilder
Strings are handled differently than int and IntPtr for marshaling. Here are the rules I follow.
If string is in the argument then the tag looks like this: [DllImport("dmscli.dll",CharSet = CharSet.Unicode)]
if LPCTSTR is in the return value in the help then you use IntPtr as the return value and use Marshal.PtrToStringUni() around the function to convert the return to a C# string.
if you need to pass NULL to a string then use the keyword null. If that causes an error, then change the type to IntPtr and pass IntPtr.Zero for the null value.
When you use StringBuilder to pass a pointer to a string (LPWSTR), you have to define the MaxCapacity and use that value for your buffer length.
Occasionally you will need to pass or get a structure. You need to define the structure in your class with your declarations and it needs to be defined with the [StructLayout(LayoutKind.Sequential)] tag.
Example:
[StructLayout(LayoutKind.Sequential)] public struct AADOC_ITEM { public int lProjectId; public int lDocumentId; }
You can not create hooks and menus in pure C# yet, you will need to create a C# COM object and a C++ stub that is called by ProjectWise.