Create Documents using SDK

Hi guys,

I want to, whenever a user click in a option created in PW and select a File from a FileDialog, this file should be created in PW. The first step of it (Create menu option and FileDialog) was acomplished succesfully. Now, i'm having problems to create the selected file as a document in PW. I tried what is described in this link http://communities.bentley.com/products/projectwise/projectwise_programming/f/342995/t/56503.aspx, but with no success. Could someone help me? Here is the code that i'm trying to create the document in PW (It's in C++ using MFC):

LONG docID = lpDocItem->lDocumentId;
LONG projectID = lpDocItem->lProjectId;

LONG *lngDocID;
*lngDocID = 0;

LONG lngAppID = aaApi_GetFExtensionApplication(L"pdf");
LONG lngWorkSpaceID = aaApi_GetWorkspaceProfileId(projectID, 0);

LPWSTR strWorkingDir = L" ";
BOOL status = aaApi_CreateDocument(lngDocID, projectID, 0, AADMS_FTYPE_UNKNOWN ,
AADMS_ITYPE, lngAppID, 1, lngWorkSpaceID, FileDlg.GetPathName(),
FileDlg.GetFileName(), FileDlg.GetFileName(), NULL, NULL,
false, AADMSDOCCREF_DEFAULT, strWorkingDir,
512, 0);
Parents
  • Something like this might work:

    LONG docID = 0L;
    LONG projectID = lpDocItem->lProjectId; // not sure where you are getting this, but as long as it is a valid value...
    LONG lAttributeId = 0;
    LONG lngAppID = aaApi_GetFExtensionApplication(L"pdf");
    LONG lngWorkSpaceID = aaApi_GetWorkspaceProfileId(projectID, 0);
    WCHAR strWorkingDir[_MAX_PATH];   // for checked out file location
    memset (strWorkingDir, '\0', _MAX_PATH);BOOL status = aaApi_CreateDocument(
                      &docID,   // new document's doc id
                      projectID,   // id of folder to create doc in
                      0,    // 0 means default storage
                      0,    // 0 unless you have reason to use something else
                      0,    // 0 unless you have reason to use something else
                      lngAppID,   // 0 means none for application
                      0,    // 0 means none for department
                      lngWorkSpaceID,  // workspace profile
                      FileDlg.GetPathName(), // source file
                      FileDlg.GetFileName(), // what to call the file in PW
                      FileDlg.GetFileName(), // doc name
                      NULL,    // doc description
                      NULL,    // version string - you might want to check DS settings...
                      FALSE,   // TRUE means file is checked out at create time
                      AADMSDOCCREF_DEFAULT,  // check documentation for ulFlags
                      strWorkingDir,  // location of the file if checked out
                      _MAX_PATH - 1,  // make sure the buffer is large enough
                      &lAttributeId   // new attribute id in environment if created - see ulFlags
                      );

     

  • Dan, I tried using something similar to your code to upload a file to ProjectWise, but I keep getting error 50132: "The file does not exist."

    My code is basically the same, except I replaced the FileDlg calls with a function to convert CHAR* to LPCWSTR, because I'm not building a MFC app, this is just a DLL ultimately for use in VBA:

    LPCWSTR convertCharArrayToLPCWSTR(char* charArray)
    {
     std::string charStr=std::string(charArray);
     std::wstring sTemp=std::wstring(charStr.begin(),charStr.end());
     LPCWSTR funcArg=sTemp.c_str();
     return funcArg;
    }

    bool __stdcall FileOperator::uploadDocument(char* filePath,long projectID)
    {
     char* filename="xyz.xlsx";
     char* docname="xyz.xlsx";
     long docID=0L;
     long lAttributeID=0;
     long lngAppID=aaApi_GetFExtensionApplication(L"xlsx");
     long lngWorkSpaceID=aaApi_GetWorkspaceProfileId(projectID,0);
     WCHAR strWorkingDir[_MAX_PATH];

     memset(strWorkingDir,'\0',_MAX_PATH);

     bool status=aaApi_CreateDocument(
    &docID,
    projectID,
    0,
    0,
    0,
    lngAppID,
    0,
    lngWorkSpaceID,
    convertCharArrayToLPCWSTR(filePath),
    convertCharArrayToLPCWSTR(filename),
    convertCharArrayToLPCWSTR(docname),
    NULL,
    NULL,
    FALSE,
    AADMSDOCCREF_DEFAULT,
    strWorkingDir,
    _MAX_PATH-1,
    &lAttributeID
      );
      long errorID=aaApi_GetLastErrorId();
      LPCWSTR errorStr=aaApi_GetLastErrorDetail();
      return status;
    }

    Here's the console app that I used to call the code:

    #include "stdafx.h"
    #include <iostream>
    #include "../myProject/myProject.h"

    using namespace std;

    int main()
    {
     long initResult=myProject::FileOperator::initialize(abc.def.com:PWOPPID_xyz");
     bool uploadResult=myProject::FileOperator::uploadDocument("u:\Software Development\\abc\\def\\ghi\xyz.xlsx",1799);
     return 0;
    }

    I know that the file exists at this path; that's a direct copy/paste of the path given in the file properties.  Why is aaApi_CreateDocument() unable to find it?

    Sorry if this is a thread hijack, I can repost if needed, but it seemed closely related enough.

  • Maybe Dan can provide more input since I don't want to interfere with the current communication.

    If you have not, you may want to consider using the Microsoft Visual Studio debugger to closely examine all function parameters as being 100% correct to a "failing function call" prior to the call of that function.  Microsoft makes it very easy to place break points and "fly over" a parameter to see what the value is when it is frozen in time before a call that is failing.

    If the sample code you posted was copied and pasted directly (encouraged) and was not a simply typo in the posting process, then the line in main() for the file name is not fully escaping all backslash characters with double backslashes as required for any string literals.  Make sure to double backslash any single backslashes like: "u:\" and "\xyz.xlsx".

    HTH,

    Bob



  • Joshua,

    A few things come to mind.

    1) I think Bob is correct in that your hard coded path isn't correct; it has some single backslash characters.

    2) I'm not convinced that you are accounting for the differences between ANSCII and UNICODE in your code.

    3) I'm suspicious of your convertCharArrayToLPCWSTR() function. You allocate a variable for a new string that you return a pointer to, but won' that go away after you return from the function? Shouldn't it be static? It may work because of how quickly you use it, but it seems to me that you are returning a pointer to something that isn't guaranteed to be there after you exit the function.

    Here's what I suggest:

    1) Create your DLL with C/C++ and the PW SDK.

    2) Create a test program to fully test your DLL, but create that test program with C/C++.

    3) Once you are convinced that your DLL works as designed with C/C++, then try to make it work with VBA.

     

Reply
  • Joshua,

    A few things come to mind.

    1) I think Bob is correct in that your hard coded path isn't correct; it has some single backslash characters.

    2) I'm not convinced that you are accounting for the differences between ANSCII and UNICODE in your code.

    3) I'm suspicious of your convertCharArrayToLPCWSTR() function. You allocate a variable for a new string that you return a pointer to, but won' that go away after you return from the function? Shouldn't it be static? It may work because of how quickly you use it, but it seems to me that you are returning a pointer to something that isn't guaranteed to be there after you exit the function.

    Here's what I suggest:

    1) Create your DLL with C/C++ and the PW SDK.

    2) Create a test program to fully test your DLL, but create that test program with C/C++.

    3) Once you are convinced that your DLL works as designed with C/C++, then try to make it work with VBA.

     

Children
  • Thanks for catching the non-escaped slash. I changed the file path so that all the slashes are escaped as double backslashes.  I also changed the file path to a UNC path.

    Yes, it looks like the problem might be with convertCharArrayToLPCWSTR().  After this line is executed:

    LPCWSTR funcArg=sTemp.c_str();

    then funcArg has the correct value.  But then, after this line is executed:

    return funcArg;

    then funcArg has become garbage data.

    I'm having a lot of trouble figuring out how to convert a string to an LPCWSTR, which is used throughout the ProjectWise API.  It seems that if I want to cast it using, e.g.

    LPCWSTR xyz=(LPCWSTR)myInputString

    then I have to switch from Unicode to multi-byte, but if I do that the API throws an error because:

    #if !defined(UNICODE) && !defined(NOUNICODE)
    #error UNICODE is not defined. UNICODE must be defined for correct API arguments.
    #endif

    What's the best way to convert from char* to LPCWSTR?



  • I got it working.

    Here's the conversion to LPCWSTR I'm using:

    wchar_t *convertCharArrayToLPCWSTR(const char* charArray)
    {
    wchar_t* wString=new wchar_t[4096];
    MultiByteToWideChar(CP_ACP, 0, charArray, -1, wString, 4096);
    return wString;
    }

  • Glad to hear you have your code working now.

    Microsoft provides plenty of character types, objects, and functions to work with those different character types.  With so many choices there is really no one size fits all.  For ProjectWise programming your projects should define and use _UNICODE since ProjectWise fully supports unicode multibyte character arrays.  For string lterals (where needed - like your test/sample file name used) Microsoft provides: "L" to promote ANSII character arrays to unicode, and a better/preferred "_T" macro to safely implement either ANSII or unicode conversions.

    Although using wstring is perfectly fine, I have come to prefer using wostringstream since it has a .str() method to provide you an ANSII character array reference when needed and will cast/format other well known data types into that output stream (string).  There are a lot of choices and in the end it will come down to a.) what works properly, and b.) personal preference.

    In case you are interested, here is a code snip I use showing use of: wostringstream, _T, stream casting, and output wstring for use elsewhere:

    wstring PWDatasourceTypeString (LONG lDatasourceType)
    {
     wostringstream woStream;
     switch (lDatasourceType)
     {
      case AAAPIDB_UNKNOWN: woStream << _T("AAAPIDB_UNKNOWN, AAAPIDB_OPTINET (") << lDatasourceType << ")"; break;
      case AAAPIDB_ORACLE: woStream << _T("AAAPIDB_ORACLE, AAAPIDB_ODBC (") << lDatasourceType << ")"; break;
      case AAAPIDB_SQLSERVER: woStream << _T("AAAPIDB_SQLSERVER (") << lDatasourceType << ")"; break;
      default: woStream << _T("UNKNOWN TYPE: ") << lDatasourceType << ")"; break;
     }
     return woStream.str();
    }

    HTH,
    Bob



  • Just a few extra comments:

    Bob Hook is right,  you should always compile any ProjectWise compatible module with _UNICODE defined.

    Unless you have a very real need to compile your code for Unicode and ANSI build targets, stay away from the _T, _TEXT and such; they are a backward compatibility crutch, and they make it easy to accidentally compile a source file with Unicode turned off.  Stick with the "L" prefix (e.g., L"my text string") as this will fail to compile in a non-Unicode build.

    Your original problem in your conversion function is that your local std::wstring object was going out of scope (and thus being destructed) on the return of your function. This obviously would invalidate your pointer to the wchar_t* object inside the string object.  Your subsequent refactor needs some work as well, as you are now leaking memory (wString is not freed).  Better to supply two arguments to your conversion, one for input and one for output, rather than trying to return the result of the conversion.

    Mike



  • Michael,

    Thanks for the advice about memory management; I'm freeing the memory out of scope by using delete() on the return value of convertCharArrayToLPCWSTR(), but I realize that this was not apparent from my code, and I can see that it would be cleaner code if I free the memory in scope.