Join The Works program to have access to the most current content, and to be able to ask questions and get answers from Revelation staff and the Revelation community

At 01 FEB 2021 12:02:47PM Brad Bishop wrote:

I'm converting a function that prints a word document from Ole to DOTNET. I can't get the DOTNET functionality to work. So I'm either interpreting the documentation incorrectly or DOTNET isn't running as I expect.

To break it down, I start a Word instance, open a document, print it and close the document.

I have it running in C# in visual studio so I know the steps it looks like this

using Word = Microsoft.Office.Interop.Word;

.

.

.

var Word = new Word.Application() { Visible = true };

Word.Documents.Open(adoc);

Word.PrintOut();

Word.Quit();

In DOTNET I believe it should look like this:

dotNetHandle = StartDotNet('')

lpath = 'C:\Program Files (x86)\Microsoft Visual Studio\Shared\Visual Studio Tools for Office\PIA\Office15\Microsoft.Office.Interop.Word.dll'

rslt = Set_Property.net(dotNetHandle, 'AssemblyName', lpath)

wordObj = create_class.net(dotNetHandle, appClass, true$)

ReturnValue = send_Message.net(wordObj, 'Documents','Open',adoc)

ReturnValue = send_Message.net(wordObj, 'Printout')

ReturnValue = send_message.net(wordObj, 'Quit')

Free_class.net()

The DOTNET successfully Starts WORD but then nothing. Can't even close the document. The second issue is the that Creates generates the following errors:


At 01 FEB 2021 12:34PM bshumsky wrote:

Hi, Brad. Hope you don't mind, I deleted all the redundant copies of this post…

Can you tell me what you have specified for appClass? You reference it in your code snippet but don't show how it's defined.

Is your C# built to generate 32 or 64bit output, or is it "AnyCPU"?

By the way, if you want, you might be able to turn your C# routine into a COM object that you could call from OI without needing to use RevDotNet…?

- Bryan Shumsky

Revelation Software, Inc.


At 01 FEB 2021 01:26PM Brad Bishop wrote:

No problem on the duplicates I didn't intentionally create them.

appClass = 'Microsoft.Office.Interop.Word.ApplicationClass'

AnyCPU.

I'm not really a Visual Studio programmer. I know just enough about it to be able to look up and follow somebody else's examples. So I prefer to keep it all in Open Insight as it makes it cleaner and easier to support across six different applications and multiple locations.


At 01 FEB 2021 03:07PM bshumsky wrote:

No problem on the duplicates I didn't intentionally create them.

appClass = 'Microsoft.Office.Interop.Word.ApplicationClass'

AnyCPU.

I'm not really a Visual Studio programmer. I know just enough about it to be able to look up and follow somebody else's examples. So I prefer to keep it all in Open Insight as it makes it cleaner and easier to support across six different applications and multiple locations.

I am suspicious that the the Office Primary Interop Assemblies (PIA) are 32bit (since they're located in the (x86) Program Files subtree), but I don't know for sure. Can you rebuild your C# program as x86? Does it still work? What about if you build it as x64 - does it still work?

In C# you can run as "AnyCPU" and it "sorts things out", but in OI 10 you can ONLY use 64bit assemblies. So I think that's the first place to look.

Thanks,

- Bryan Shumsky

Revelation Software, Inc.


At 01 FEB 2021 03:43PM Brad Bishop wrote:

I had not thought of the 32/64 bit issue. That might be a problem as most users (including all of mine) still run 32bit office as running 64 bit requires upgrades to all 3rd party add-ins. Even Microsoft recommends staying with 32bit unless there is a specific need for 64bit (and 32 is still the default install).


At 01 FEB 2021 05:14PM Carl Pates wrote:

Hi Brad,

I'm curious as to why you're moving from using the Ole interface to a .NET one? The OLE interface has been a tried and tested way of working with Office from OI for a long time now?

Regards

Carl Pates


At 03 FEB 2021 09:33AM Brad Bishop wrote:

I have to update every form and stored procedure in all my applications because I'm upgrading from v7.3. So as part of the process I'm trying to future proof the code by using the latest technology whenever possible. For example, I am converting all of the "Utility" references to the new model so I don't have to go through every application in detail at some later date to address issues I already know.


At 03 FEB 2021 09:52AM Carl Pates wrote:

Hi Brad,

I honestly don't think you would need update MS Office from OLE →.NET, because I'm fairly sure that MS will never retire that API - they would just break too many applications. I'm also fairly sure the .NET API will actually use the OLE API internally anyway as the core office products are native code and not .NET based.

… but then I'm not MS so this is just my opinion :)

Regards

Carl Pates


At 03 FEB 2021 11:34AM Brad Bishop wrote:

Carl, that makes perfect sense to me. But unfortunately I'm having trouble with the OLE calls in V10 (they are not working the same as V7.3). The only way I can get the open method to work is to remove all arguements except the first one. If I leave the arguements in, Word simply closes. This is very similar to the issue I was having with Methods DetNet. I need to be able to set options during the Open to prevent the user from being prompted (this all runs in the background). Here is the code without any errorhandling.

wdConfirmConverions = FALSE$

wdReadOnly = TRUE$

wdAddToRecentFiles = FALSE$

wdSaveChanges = FALSE$

OleCreateInstance('Word.Application')

olePutProperty(ObjWord,"Visible",0)

olePutProperty(ObjWord,"Visible",1) ;* Used only during testing

objWordOptions = OleGetProperty(objWord,"Options")

olePutProperty(objWordOptions,"EnableSound",0)

olePutProperty(objWordOptions,"ConfirmConversions",0)

olePutProperty(objWordOptions,"AlertLevel",0

objDocs = OleGetProperty(objWord, 'Documents')

objDoc = OleCallMethod(objDocs,'Open', attachment, wdConfirmConverions, wdReadOnly, wdAddToRecentFiles)

oleRetVal = OleCallMethod(objDoc,'PrintOut')

oleRetVal = OleCallMethod(objDoc,'Close',wdSaveChanges)

retVal = OleCallMethod(ObjWord,"Quit"


At 04 FEB 2021 11:04AM Carl Pates wrote:

Hi Brad,

This is a strange one - I made sure my copy of Office was 64-bit and then did a simple test like so:

   objWord = oleCreateInstance( "Word.Application" )

   objWord->visible = TRUE$

   

   objDocs = objWord->Documents

   

   file = "c:\temp\testDoc.docx"

   objDoc = objDocs->open( file, FALSE$, TRUE$, FALSE$ )



   statX  = oleStatus()

   if statX then

      errorText = rti_ErrorText( "OLE", statX, TRUE$ );

      debug

   end

Looking at the error it returns "Remote Procedure Call Failed". Hmmm … not massively helpful.

If I just changed the Open() call to simply pass the file name, with no extra arguments, then the document opens. It appears there is some issue with passing more than one of these optional parameters.

Checking the code in the engine for the CallOleMethod opcode (which hasn't changed in the many years since it was written) it seems like it _might_ (and this is a _might_) not be following the rules for optional arguments properly - this is something I have to investigate further. If this is the case, then two questions I have are are:

1) Why did it work for you with earlier versions of OI?

2) Why does it work with just the file name?

Anyway, this doesn't help you right now, so another option you have is to invoke Word through the OLE script interface, whereby you create some functions in JScript or VBScript and call them from Basic+ - I tested it here and it worked fine, and, TBH, if you have a lot of OLE automation work to do it's a better solution than using the Basic+ calls, because it's a more natural fit in terms of syntax.

In 32-bit versions of OI we used to be able to use the MS ScriptControl OCX, but MS didn't port a 64-bit version of this, so for v10 we wrote our own script DLL call RevAXSH.DLL (ActiveX Scripting Host), and a stored procedure called rti_AXSH to interface to it. Using it follows these steps:

1) Create an instance of the scripting host

2) Load your JS/VB functions into it

3) Call them

4) Destroy the scripting host

Here's a simple example:

compile subroutine cp_test_word_open_ole( fileName )



   $insert rti_AXSH_Equates

   $insert rti_SSP_Equates

   $insert logical

   

   call set_Status( SETSTAT_OK$ )



   f     =  'function testWord( docName )'

   f<-1> = '{'

   f<-1> = '   var objWord = new ActiveXObject( "Word.Application" );'

   f<-1> = '   objWord.Visible = true;'

   f<-1> = '   var objDocs = objWord.Documents;'

   f<-1> = '   objDocs.Open( docName, false, true, false );'

   f<-1> = '   return;'

   f<-1> = '};'

   

   swap @fm with CRLF$ in f

   

   createParams = ""

   createParams<REVAXSH_CREATE_POS_LANGUAGE$> = "JScript"

   

   hAXSH = rti_AXSH( REVAXSH_MTD_CREATE$, createParams )

   if get_Status() then return

   

   call rti_AXSH( REVAXSH_MTD_ADDCODE$, hAXSH, f )

   if get_Status() else

   

      call rti_AXSH( REVAXSH_MTD_RUN$,       |

                     hAXSH,                  |

                     "testWord",             |

                     fileName )

                     

   end

   

   if ( hAXSH ) then

      call rti_AXSH( REVAXSH_MTD_DESTROY$, hAXSH )

   end

   

return

Of course you don't have to create you JS/VB in a string in the program - you could simple store it in a record and xlate it when you need it.

Hope that helps, but in the meantime I'll continue looking at why we're having problems with Word, (doubtful this is an easy fix!)

Carl Pates


At 05 FEB 2021 02:54PM Carl Pates wrote:

Hi Brad,

This is a tough one :(

I've rewritten the OleCallMethod interface to deal with Optional Parameters properly, but I still can't get it work.

So, digging deeper it reports that it is having a problem with the second argument, which we are passing as FALSE$ (i.e the number 0).

Internally this value is being translated to an OLE type of VT_I8, which is a 64-bit integer with a value of 0 - (Basic+ has no concept of an actual boolean type - we get by with simple numerics quite well thanks). If I change this "by hand" in the VS debugger to a VT_BOOL type the method works. So, it seems that Word is not happy to coerce a 64-bit integer to a bool.

Next I used the "by hand" method to change the type to a VT_I4 type, which is a 32-bit integer and this time Word coerced this to a bool properly and the Open method worked (and in 32-bit OI this is why it would work). So the bottom line here is that the functions Word is using for coercing OLE variant data types are not handling the 64-bit integer type correctly (I say this because looking at the WINE source code for the equivalent MS function it handles 64-bit→bool as expected, and I would expect Word to do this).

The next approach was to use the OLE type information for the method to see if the parameter was marked as a bool - it is not - it's marked as a "variant" which means that I can't tell from the method interface what type it really wants and therefore I can't take the Basic+ numeric value and change it to a VT_BOOL.

This doesn't leave us with any good options. We have:

1) Have an explicit Bool type in Basic+ - introducing new data types into the engine is a big deal.

2) Have some sort of flag/switch that says convert numerics to 32-bit ints, but this is not great if you had a method that needed a 64-bit integer argument and a bool argument together.

3) Have some way of "decorating" the argument possibly with a @fm-delimited suffix of some sort to indicate the desired type (e.g. FALSE$ : @fm : "B" or something)

Implementing (3) is more likely. In the meantime you can always use the scripting example I gave you earlier.

Regards

Carl Pates


At 05 FEB 2021 02:59PM Donald Bakke wrote:

Implementing (3) is more likely.

For what it is worth, I think (3) is the most sensiible and OI friendly solution.

Don Bakke

SRP Computer Solutions, Inc.


At 05 FEB 2021 03:24PM Carl Pates wrote:

Yes, it's close to what we do already with OLE Control properties in the PS.

Carl Pates


At 05 FEB 2021 08:40PM Carl Pates wrote:

Quick update - I've taken a slightly different route due to the fact that we already have a precedent for "decoration" with the OLE parameters due to a change that was made in a previous for using OLE SafeArray types as arguments with OleCallMethod (the OleEnsureByteArray opcode).

In this case we have a new function opcode called OleEnsureBool, which you use to simply wrap your boolean variable before you pass to OleCallMethod, e.g.

   bReadOnly = TRUE$

   retVal = OleCallMethod( ObjDocs, "Open", FileName, |

                           OleEnsureBool( FALSE$ ),   |

                           OleEnsureBool( bReadOnly ) ) 

And so on. This ensures that the variable is marked as a boolean when it is translated to an OLE variant data type. With this addition Word now behaves as expected.

There's also another OLE function call added, which is OleGetErrorArgIndex - when used after a failed call to OleCallMethod() it returns the index of the argument that was not correct (if appropriate), so it can help in debugging. In the example above, if the bool arguments were not marked the "Open" call will fail (always check OleStatus()!) and OleGetErrorArgIndex() would return "2" because the second argument could not be translated.

You will see these changes in the engine and compiler in the next release.

Carl Pates

View this thread on the Works forum...

  • third_party_content/community/commentary/forums_works/368405425822924a963ddfcc5baac2ca.txt
  • Last modified: 2024/01/04 20:57
  • by 127.0.0.1