News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Passing a VARIANT within a SAFEARRAY

Started by Zen, August 26, 2016, 08:34:16 AM

Previous topic - Next topic

Zen

This is the MOST RIDICULOUS problem ever. It's driving me nuts (and,...it's probably TOO LATE to recover any semblance of sanity). If you can even understand the explanation,...I'll be amazed,...
I have several projects that I'm working on. They are both .NET Framework CLR Hosting applications, similar to what MABDELOUAHAB has done with: Masm32Ref, but, without using his code (I wanted to see just how difficult it is to do this). All of the preliminary stuff works great; I load the .NET Framework Common Language Runtime (CLR), then load a number of .NET assemblies into the Default Domain (an idea I stole from MABDELOUAHAB), and enumerate through the types in each loaded assembly. There are a number of COM interfaces provided by the CLR that allow you to access .NET Framework classes and methods. Anyway, it's very tedious code, but, it's not difficult to conceptualize or write the source code correctly.

So, here is the actual problem. I'm trying to access System.Net.NetworkInformation, and, I'm using a code example (written in C#), called NetStat, which just interrogates the various NetworkInformation objects, retrieves data from them, and writes that data to file and then displays the data on the application window. The concept is not complicated. But, the way you have to write the code is ridiculously complex. Currently, what I'm trying to do (and, failing miserably) is invoke a constructor for the NetworkInformationPermission object. In order to do this, I must retrieve a COM interface pointer from the System.Reflection._Assembly interface to the System._Type COM interface (for the NetworkInformationPermission type). I know, this sounds like something an extraterrestrial would say to torment innocent Earthlings. Anyway, this is all fairly straightforward, and the code that I've written so far works pretty well. So, I'm trying to invoke the constructor, remember ? You won't believe how insanely ridiculous this to do from unmanaged code (a MASM assembly language program) to call into a .NET Framework class object. I want to pass a value of 1 (ULONG) to the constructor. In order to do that I must create a VARIANT that contains the VT_UI4 VARTYPE correctly, and, insert this into a SAFEARRAY.

Here is the prototype of the function that I am trying to call correctly (this is from the OLE/COM Object Viewer, which produces IDL from the mscorlib.dll (version 2.4) Type Library,...another of MABDELOUAHAB's great ideas):   

;     HRESULT _stdcall Invoke_5(
;                     [in] SAFEARRAY(VARIANT) parameters,
;                     [out, retval] VARIANT* pRetVal);


The COM interface pointer for _ConstructorInfo is obtained from the COM pointer for the  NetworkInformationPermission _Type interface, by calling GetConstructors (which returns a SAFEARRAY of COM pointers). This all works OK. So, now, I'm attempting to call _ConstructorInfo.Invoke_5 Method, and providing my VARIANT within my SAFARRAY. Incredibly, the Invoke seems to execute without crashing my application.

Here's a small code snippet of the actual constructor Invoke (SafArrDescrptr is the SAFEARRAY, vFrstConstrctr is the VARIANT return value [out. retval]):   
     mov hresFrstConstctr, 0    ;    This is the HRESULT returned from _ConstructorInfo.Invoke_5   
     mov edx, ptrConstrcts    ;    ptrConstrcts is a pointer to a _ConstructorInfo COM interface returned from GetConstructors. 
     mov ecx, DWORD PTR[edx]    ;    Interface pointer to _ConstructorInfo for the constructor.   
     mov esi, DWORD PTR[ecx]    ;    _ConstructorInfo vtable pointer.   
     invoke [esi]._ConstructorInfo.Invoke_5, ecx, ADDR SafArrDescrptr, ADDR vFrstConstrctr       
     mov hresFrstConstctr, eax   


Of course, I didn't explain the methods that I used to create the VARIANT and the SAFEARRAY. :dazzled:

The HRESULT is: COR_E_SAFEARRAYRANKMISMATCH, which has the value 80131538h. I'd never heard of that one (and, it took me awhile to even find the definition). But, eventually, I found this information description from: ".NET and COM: The Complete Interoperability Guide"

The SafeArrayRankMismatchException Exception
This exception is thrown by the CLR when an unmanaged component attempts to pass a SAFEARRAY parameter or field with a different number of dimensions than the .NET array parameter or field is defined with. For example, a simple, one-dimensional array as a .NET parameter or field is exposed to COM as a SAFEARRAY by default, which has no static rank encoded in its type information. If a COM client attempted to pass a multi-dimensional array, this exception would be thrown.   
There's a second scenario in which this exception is thrown. If a COM client instead passed a SAFEARRAY with the right number of dimensions but the wrong bounds (for instance, a 1-based SAFEARRAY passed to managed code expecting a standard 0-based array), SafeArrayRankMismatchException is thrown. Therefore, this applies to bounds in addition to rank.
SafeArrayRankMismatchException derives from System.SystemException, and should never be thrown by an application. Its protected HResult property (which can be exposed to COM when the exception is thrown) has the value COR_E_SAFEARRAYRANKMISMATCH, defined by the .NET Framework SDK as 0x80131538. 


...So,...at this point (assuming that you're NOT COMPLETELY COMATOSE from reading the preceding),...you're probably thinking: 'that DANG Zen is so deranged that he just did something stupid while creating either his VARIANT or his SAFEARRAY'. And, that's probably it. But, I created the VARIANT with VariantInit, and, then, this little bit of code:
     invoke VariantInit, ADDR vNetPrmsAccess    ;   
     LEA ecx, vNetPrmsAccess    ;    the VARIANT
     ASSUME ECX:PTR VARIANT   
     mov [ecx].vt, VT_UI4    ;    VT_UI4 = 13h   
     mov [ecx].ulVal, 1    ;    NetworkInformationAccess, values: None = 0, Read = 1, Ping = 4  (ULONG)   
     ASSUME ECX:NOTHING    ;   


SIZEOF vNetPrmsAccess (the VARIANT) returns 16 bytes.
...And,...my SAFEARRAY is created in the following code block:

     invoke SafeArrayCreateVector, VT_VARIANT, 0, 1    ;    SafeArrayCreateVector returns a pointer to a safe array descriptor.   
     mov SafArrDescrptr, eax    ;    Save the new array descriptor.   

     mov SafArrIndex, 0    ;    The index for the element in the SAFEARRAY. There is only one element in this SAFEARRAY.       
     invoke SafeArrayPutElement, SafArrDescrptr, ADDR SafArrIndex, ADDR vNetPrmsAccess    ;   

     LEA ebx, vNetPrmsAccess   
     LEA edx, SafArrDescrptr   
     ASSUME edx:PTR SAFEARRAY   
     mov [edx].fFeatures, 0800h    ;    FADF_VARIANT 0800h    FADF_STATIC 0002h
     ASSUME edx:NOTHING   


...I've eliminated some of the code that writes return values to Log File for clarity,...
It's possible that I haven't defined my VARIANT structure correctly. But, I don't think that's it,...

...Still awake ??? I don't believe it,...:bgrin:

HELP ME !!!


mabdelouahab

invoke [esi]._ConstructorInfo.Invoke_5, ecx, ADDR SafArrDescrptr, ADDR vFrstConstrctr

Zen

MABDELOUAHAB,
I almost certainly did not give you enough information.   
I tried this already,...
invoke [esi]._ConstructorInfo.Invoke_5, ecx, SafArrDescrptr, ADDR vFrstConstrctr   
it gives me an HRESULT of E_INVALIDARG.

But, I think I know what the problem is. After I created the SAFEARRAY with SafeArrayCreateVector, I then called: SafeArrayPutElement, which returned S_OK. The code is like this:
     mov SafArrIndex, 0    ;    The index for the element in the SAFEARRAY. There is only one element in this SAFEARRAY.       
     invoke SafeArrayPutElement, SafArrDescrptr, ADDR SafArrIndex, ADDR vNetPrmsAccess    ;   


Now, this morning, I added some code to write out data to my Log File. I called: SafeArrayGetDim, SafeArrayGetElemsize, SafeArrayGetLBound, and SafeArrayGetUBound. This is what I got back:   
The following data is a check of the SAFEARRAY properties:
The number of Dimensions in the SAFEARRAY is: 1
The element size in the SAFEARRAY is: 16
The lower bound in the SAFEARRAY is: 0
The upper bound in the SAFEARRAY is: 0
The size, in bytes, returned from SIZEOF of the VARIANT is: 16
The size of the VARIANT element in SAFEARRAY is: 16


...After a search on the Internet, I found this explanation:
Long index[2]={2,1}; / / rightmost dimension first;
Data long = 3;
SafeArrayPutElements (pSa, index, &data);


In the SafeArrayPutElement function, you need to pay attention to how to specify the required elements. The second variable of SafeArrayPutElement is a pointer, which points to the index vector of each dimension of the array. The one dimension (lowest) is placed in the front of the vector (index[0]); the left one is one of the lowest (lowest) at the end of the vector (index[pSa->cDims-1]). Because of the access to the multidimensional array in C++ is just the opposite, so you need to pay special attention to this. You can imagine a two-dimensional array of network patterns that are expressed in the above code.

mabdelouahab

#3
Hi ZEN,
I always use Invoke_5, and it works well with me
Quote from: Zen on August 27, 2016, 05:07:31 AM
it gives me an HRESULT of E_INVALIDARG.
This means that the problem is in the vNetPrmsAccess  parameter

And NetworkInformationAccess Enumeration Is a static Fields, a VARIANT of  VT_I4

None EQU 00h   ;   VARIANT.vt:VT_I4,  VARIANT.lVal:0
Read EQU 01h   ;   VARIANT.vt:VT_I4,  VARIANT.lVal:1
Ping EQU 04h   ;   VARIANT.vt:VT_I4,  VARIANT.lVal:4

Try with this code:


    invoke SafeArrayCreateVector, VT_VARIANT, 0, 1       
    mov SafArrDescrptr, eax     
    mov eax,[eax].SAFEARRAY.pvData   ;pointer to first VARIANT
    mov [eax].VARIANT.vt,VT_I4
    mov [eax].VARIANT.lVal, 1 ; Read
.......
    invoke [esi]._ConstructorInfo.Invoke_5, ecx, SafArrDescrptr, ADDR vFrstConstrctr


Zen

#4
Thanks, MABDELOUAHAB, for the suggestions,...
You are the only one who REALLY understands this stuff. :dazzled:
I'm fairly certain that _ConstructorInfo.Invoke_5 works perfectly, since it is returning appropriate error codes.
...And, I'm also fairly certain that I'm doing something dumb (and not realizing it) when initializing either the VARIANT or the SAFEARRAY (or, both) that I'm supplying to the constructor Invoke.
I'll just keep trying various things (in desperation),...until, either, I get a different HRESULT error code,...or, I go COMPLETELY INSANE,...

mabdelouahab

If the code did not work with you there is a possibility
you use the first constructor, Try the code with the second constructor

     mov edx, ptrConstrcts    ;    ptrConstrcts is a pointer to a _ConstructorInfo COM interface returned from GetConstructors. 
     mov ecx, DWORD PTR[edx+4]    ;    Interface pointer to _ConstructorInfo for the constructor.   


I also noticed that you used vFrstConstrctr as dword
     mov hresFrstConstctr, 0    ;    This is the HRESULT returned from _ConstructorInfo.Invoke_5   


but the return value of invoke_5 is a ptr to VARIANT not a ptr to dword

Zen

OK,...finally,...PROBLEM SOLVED. :bgrin:
Thanks,...MABDELOUAHAB for all your suggestions,...as it turned out, you were right about everything. I really appreciate it,...
Here is the original constructor invoke that I screwed up so badly:
invoke [esi]._ConstructorInfo.Invoke_5, ecx, ADDR SafArrDescrptr, ADDR vFrstConstrctr   

That initial COR_E_SAFEARRAYRANKMISMATCH HRESULT confused me, but, when I removed the ADDR from the second parameter (the SAFEARRAY, SafArrDescrptr),...I got an HRESULT of E_INVALIDARG, which was because of the THIRD parameter (ADDR vFrstConstrctr). I declared vFrstConstrctr as a VARIANT in my data section, thinking that the .NET Framework would take that pointer and write the interface pointer to the VARIANT that I allocated. DUMB. Last night, it occurred to me that the .NET Framework would return a pointer to a VARIANT that it allocated, instead.

...So, this is the NetworkInformationPermission constructor Invoke that worked correctly:
     invoke [esi]._ConstructorInfo.Invoke_5, ecx, SafArrDescrptr, ADDR PtrFrstContrctr    ;    returns HRESULT of S_OK.       

...where ecx is the Interface pointer to the _ConstructorInfo vtable for the first constructor (there are two for NetworkInformationPermission), and the third parameter, ADDR PtrFrstContrctr is just a DWORD pointer (instead of a pointer to an already initialized VARIANT, in my program's data section).
I called both constructors with the same parameters, and they BOTH returned S_OK.

This is how I set the data in the VARIANT (which is inserted into the SAFEARRAY): 
     invoke VariantInit, ADDR vNetPrmsAccess    ;    vNetPrmsAccess is the VARIANT
     LEA ecx, vNetPrmsAccess   
     ASSUME ECX:PTR VARIANT   
     mov [ecx].vt, VT_I4    ;    VT_I4 = 3h       
     mov [ecx].lVal, 1    ;    NetworkInformationAccess, values: None = 0, Read = 1, Ping = 4  (LONG)       
     ASSUME ECX:NOTHING    ;   


...So, the constructor executed correctly (yes, I checked the returned VARIANT), and I now have a .NET Framework NetworkInformationPermission object with Read Access. :bgrin:
Thanks, again,...MABDELOUAHAB,...