|
ms
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
modifying a string array via MFC COM InteropI'm working with an MFC based COM object. From C# I'd like to be able to call a method on the COM object that takes a string array and modifies the contents. Is there any way to do this with a variable length array? I've only been able to get it to work with a fixed size array. The relevant code snippets are below. Suggestions are greatly appreciated (e.g., there's a better way to do it, the code will cause memory leaks, etc...). MFC DISPATCH MAP ENTRY: DISP_FUNCTION_ID(CMyClass, "StrArrayFunc", dispidStrArrayFunc, StrArrayFunc, VT_I4, VTS_VARIANT) ..H PROTOTYPE: HRESULT StrArrayFunc(VARIANT &vArray); ..CPP: HRESULT CMyClass::StrArrayFunc(VARIANT &vArray) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); SAFEARRAY **ppsa = V_ARRAYREF(&vArray); // ... } MANAGED/C# CLIENT: static int Main(string[] args) { String[] ar = new string[30]; for (int i=0; i < ar.Length; i++) ar[i] = "str"; Array a = (Array) ar; MyCOMServer.MyClassClass mc = new MyClassClass(); mc.StrArrayFunc(ref a); // INTEROP call string[] nAr = (string[]) a; // ... remaining code accesses modified array through nAr } "Arnshea" <arns***@gmail.com> ha scritto nel messaggio I don't know about C# side.news:d105694e-b83f-463c-8a6d-4192d79652fb@p69g2000hsa.googlegroups.com... > I'm working with an MFC based COM object. From C# I'd like to be able > to call a method on the COM object that takes a string array and > modifies the contents. > > Is there any way to do this with a variable length array? > > I've only been able to get it to work with a fixed size array. But I believe that - at least for COM and MFC side - if you use a SAFEARRAY containing BSTRs, you should have no problem at all. Have you tried to use your MFC COM object e.g. with Visual Basic 6? VB6 understands COM very well. You could also try to test it from a MFC client, as well. My suggestion is to first make it work correctly with native COM (so you may test your COM object from a VB6 or MFC client), only after then, you may try to fix the C# Interop side. BTW: COM programming is *full* of lots of *details* (see other posts in this MFC newsgroup, for some example... e.g. there was a bug with a VT_UI8 flag used instead of correct VT_UI1, immersed in other well-working code), so posting the C++/MFC code snippet you gave us is useless. Just my 2 cents. Giovanni
Show quote
On Nov 28, 1:31 pm, "Giovanni Dicanio" <giovanni.dica***@invalid.it> Thanks, I appreciate it.wrote: > "Arnshea" <arns***@gmail.com> ha scritto nel messaggionews:d105694e-b83f-463c-8a6d-4192d7965***@p69g2000hsa.googlegroups.com... > > > I'm working with an MFC based COM object. From C# I'd like to be able > > to call a method on the COM object that takes a string array and > > modifies the contents. > > > Is there any way to do this with a variable length array? > > > I've only been able to get it to work with a fixed size array. > > I don't know about C# side. > > But I believe that - at least for COM and MFC side - if you use a SAFEARRAY > containing BSTRs, you should have no problem at all. > > Have you tried to use your MFC COM object e.g. with Visual Basic 6? VB6 > understands COM very well. > You could also try to test it from a MFC client, as well. > > My suggestion is to first make it work correctly with native COM (so you may > test your COM object from a VB6 or MFC client), only after then, you may try > to fix the C# Interop side. > > BTW: COM programming is *full* of lots of *details* (see other posts in this > MFC newsgroup, for some example... e.g. there was a bug with a VT_UI8 flag > used instead of correct VT_UI1, immersed in other well-working code), so > posting the C++/MFC code snippet you gave us is useless. > > Just my 2 cents. > > Giovanni The full C++/MFC method is below. The method works (the input array is modified and the modifications are visible from C#). I'm mainly looking for ideas for improving this, e.g., alternative implementation, style, potential pitfalls and the like. HRESULT CMyClass::StrArrayFunc(VARIANT &vArray) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // make sure it's a string array if (V_VT(&vArray) != (VT_BYREF | VT_ARRAY | VT_BSTR)) AfxThrowOleDispatchException(1001, "Type Mismatch in Parameter. Pass a string array by reference"); // get the safearray from the variant SAFEARRAY **ppsa = V_ARRAYREF(&vArray); // get lower and upper bounds long lLBound, lUBound; SafeArrayGetLBound(*ppsa, 1, &lLBound); SafeArrayGetUBound(*ppsa, 1, &lUBound); // access each element BSTR bstrCurrent; BSTR bstrNew; CString curStr; for (long i=lLBound; i <= lUBound; i++) { SafeArrayGetElement(*ppsa, &i, &bstrCurrent); SysFreeString(bstrCurrent); bstrNew = SysAllocString(L"replaced"); HRESULT hr = SafeArrayPutElement(*ppsa, &i, bstrNew); if (FAILED(hr)) goto error; } return S_OK; error: AfxThrowOleDispatchException(1003, "Unexpected Failure in StrArrayFunc method"); return 0; } "Arnshea" <arns***@gmail.com> ha scritto nel messaggio OK. So my understanding was wrong (I thought you had problems and bugs, e.g. news:4b6908e9-df68-4c50-825b-48e296245edb@s8g2000prg.googlegroups.com... > The full C++/MFC method is below. The method works (the input array > is modified and the modifications are visible from C#). about array resizing...). I apologize. > I'm mainly OK. I put some comments in code below:> looking for ideas for improving this, e.g., alternative > implementation, style, potential pitfalls and the like. > HRESULT CMyClass::StrArrayFunc(VARIANT &vArray) I would put an HRESULT variable, local to the method, e.g.> { > AFX_MANAGE_STATE(AfxGetStaticModuleState()); > HRESULT hr; So, you can assign it as the result of every function call you do in the method, and if you get an error (FAILED(hr)), then you can return this 'hr' value as function return value. So, the caller (client) can have better information about the cause of the error condition. e.g. hr = SomeCOMCall(...); if ( FAILED(hr) ) { return hr; } > // make sure it's a string array When you use AfxThrowOleDispatchException, I would prefer not using a string > if (V_VT(&vArray) != (VT_BYREF | VT_ARRAY | VT_BSTR)) > AfxThrowOleDispatchException(1001, "Type Mismatch in Parameter. > Pass a string array by reference"); literal, but instead an integer reference into a string-table. AfxThrowOleDispatchException (MFC): http://msdn2.microsoft.com/en-us/library/hf2kbh8z(VS.80).aspx You could use the following overload, which takes an integer instead of a string: void AFXAPI AfxThrowOleDispatchException( WORD wCode, UINT nDescriptionID, UINT nHelpID = -1 ); Using an integer into a string table has several advantages, like making localization easier (you can just change the string error description in the string table, without modifying string literals in source code). In general, I prefer storing strings in string table resources, and reference the string via an integer resource ID, and not embedd string literals in code. > // get the safearray from the variant You may want to check the pointer value here.> SAFEARRAY **ppsa = V_ARRAYREF(&vArray); if ( ppsa == NULL ) ... error: pointer not valid (you may return an E_POINTER error as HRESULT). Moreover, considering that you use "SAFEARRAY *" parameters, I would get rid of double level of indirection (SAFEARRAY **), and just use one level of indirection, i.e.: SAFEARRAY * psa = *ppsa; ASSERT( psa != NULL ); // ASSERT in debug builds only if ( psa == NULL ) // Check for release builds { // signal errors in release builds return E_POINTER; // or something similar } So, you can use 'psa' instead of '*ppsa' in following lines, e.g.: > // get lower and upper bounds becomes:> long lLBound, lUBound; > SafeArrayGetLBound(*ppsa, 1, &lLBound); > SafeArrayGetUBound(*ppsa, 1, &lUBound); SafeArrayGetLBound( psa, 1, ... ); SafeArrayGetUBound( psa, ... ); > // access each element In general, I would use a BSTR class wrapper (like CComBSTR or _bstr_t) > BSTR bstrCurrent; > BSTR bstrNew; > CString curStr; > for (long i=lLBound; i <= lUBound; i++) > { > SafeArrayGetElement(*ppsa, &i, &bstrCurrent); > > SysFreeString(bstrCurrent); > bstrNew = SysAllocString(L"replaced"); instead of "raw" BSTR. > HRESULT hr = SafeArrayPutElement(*ppsa, &i, bstrNew); 'hr' should be defined at the top of the method, so you can assign it from whatever function call you make. For example, also SafeArrayGetLBound and SafeArrayGetUBound return an HRESULT. For robust code, I think you should check hr also in these cases. So 'HRESULT hr' should be local to the whole method. HRESULT hr; // <-- in the top of method ... hr = SafeArrayGetLBound( psa, ... ); if ( FAILED(hr) ) { ... return hr; } ... hr = ... if ( FAILED(hr) ) ... > If you have an error, you should not return 0. You should return at least an > error: .... > return 0; error code (generic) like E_FAIL. But you should return a more specific error code, if possible. Please consider all above as IMHO. I like freely expressing opinions (and elaborate them), but I don't like when personal opinions become "religious" wars or flames :) Giovanni
Show quote
On Nov 28, 2:07 pm, "Giovanni Dicanio" <giovanni.dica***@invalid.it> Ahh, beautiful. Thanks a lot, I appreciate it!wrote: > "Arnshea" <arns***@gmail.com> ha scritto nel messaggionews:4b6908e9-df68-4c50-825b-48e296245***@s8g2000prg.googlegroups.com... > > > The full C++/MFC method is below. The method works (the input array > > is modified and the modifications are visible from C#). > > OK. So my understanding was wrong (I thought you had problems and bugs, e.g. > about array resizing...). > I apologize. > > > I'm mainly > > looking for ideas for improving this, e.g., alternative > > implementation, style, potential pitfalls and the like. > > OK. I put some comments in code below: > > > HRESULT CMyClass::StrArrayFunc(VARIANT &vArray) > > { > > AFX_MANAGE_STATE(AfxGetStaticModuleState()); > > I would put an HRESULT variable, local to the method, e.g. > > HRESULT hr; > > So, you can assign it as the result of every function call you do in the > method, and if you get an error (FAILED(hr)), then you can return this 'hr' > value as function return value. So, the caller (client) can have better > information about the cause of the error condition. > e.g. > > hr = SomeCOMCall(...); > if ( FAILED(hr) ) > { > return hr; > } > > > // make sure it's a string array > > if (V_VT(&vArray) != (VT_BYREF | VT_ARRAY | VT_BSTR)) > > AfxThrowOleDispatchException(1001, "Type Mismatch in Parameter. > > Pass a string array by reference"); > > When you use AfxThrowOleDispatchException, I would prefer not using a string > literal, but instead an integer reference into a string-table. > > AfxThrowOleDispatchException (MFC):http://msdn2.microsoft.com/en-us/library/hf2kbh8z(VS.80).aspx > > You could use the following overload, which takes an integer instead of a > string: > > void AFXAPI AfxThrowOleDispatchException( > WORD wCode, > UINT nDescriptionID, > UINT nHelpID = -1 > ); > > Using an integer into a string table has several advantages, like making > localization easier (you can just change the string error description in the > string table, without modifying string literals in source code). > In general, I prefer storing strings in string table resources, and > reference the string via an integer resource ID, and not embedd string > literals in code. > > > // get the safearray from the variant > > SAFEARRAY **ppsa = V_ARRAYREF(&vArray); > > You may want to check the pointer value here. > > if ( ppsa == NULL ) > ... error: pointer not valid > > (you may return an E_POINTER error as HRESULT). > > Moreover, considering that you use "SAFEARRAY *" parameters, I would get rid > of double level of indirection (SAFEARRAY **), and just use one level of > indirection, i.e.: > > SAFEARRAY * psa = *ppsa; > ASSERT( psa != NULL ); // ASSERT in debug builds only > if ( psa == NULL ) // Check for release builds > { > // signal errors in release builds > return E_POINTER; // or something similar > } > > So, you can use 'psa' instead of '*ppsa' in following lines, e.g.: > > > // get lower and upper bounds > > long lLBound, lUBound; > > SafeArrayGetLBound(*ppsa, 1, &lLBound); > > SafeArrayGetUBound(*ppsa, 1, &lUBound); > > becomes: > > SafeArrayGetLBound( psa, 1, ... ); > SafeArrayGetUBound( psa, ... ); > > > // access each element > > BSTR bstrCurrent; > > BSTR bstrNew; > > CString curStr; > > for (long i=lLBound; i <= lUBound; i++) > > { > > SafeArrayGetElement(*ppsa, &i, &bstrCurrent); > > > SysFreeString(bstrCurrent); > > bstrNew = SysAllocString(L"replaced"); > > In general, I would use a BSTR class wrapper (like CComBSTR or _bstr_t) > instead of "raw" BSTR. > > > HRESULT hr = SafeArrayPutElement(*ppsa, &i, bstrNew); > > 'hr' should be defined at the top of the method, so you can assign it from > whatever function call you make. > > For example, also SafeArrayGetLBound and SafeArrayGetUBound return an > HRESULT. For robust code, I think you should check hr also in these cases. > So 'HRESULT hr' should be local to the whole method. > > HRESULT hr; // <-- in the top of method > > ... > > hr = SafeArrayGetLBound( psa, ... ); > if ( FAILED(hr) ) > { > ... > return hr; > } > > ... > > hr = ... > if ( FAILED(hr) ) > ... > > > > > > > error: > ... > > return 0; > > If you have an error, you should not return 0. You should return at least an > error code (generic) like E_FAIL. But you should return a more specific > error code, if possible. > > Please consider all above as IMHO. I like freely expressing opinions (and > elaborate them), but I don't like when personal opinions become "religious" > wars or flames :) > > Giovanni "Arnshea" <arns***@gmail.com> ha scritto nel messaggio You're welcome.news:49702a5a-93ce-45be-9451-8d26e5eaa3cc@w28g2000hsf.googlegroups.com... > Ahh, beautiful. Thanks a lot, I appreciate it! And I'm glad you appreciated. Just a final note: This could not be the case of the particular method you posted, but in other contexts you may have some needs to do proper cleanup in case of error (FAILED(hr)), and you may need to check error conditions several times, e.g. HRESULT hr; hr = CallSomething(...); if ( FAILED(hr) ) { ...cleanup1 return hr; } ... hr = CallSomething2(...); if ( FAILED(hr) ) { ...cleanup2 return hr; } etc. This C++ code could be kind of boilerplate (lots of duplicated cleanup - neither beautiful nor quality code, IMHO). You may choose to jump using a "goto" statement at the end of the method, to do a "centralized" cleanup there, e.g. <code> hr = CallSomething(); if ( FAILED(hr) ) goto Error; ... hr = CallSomething2(); if ( FAILED(hr) ) goto Error; ... return S_OK; // All right // // Cleanup already allocated resources, and return error code // Error: // Centralized cleanup here if ( resource1 is allocated ) release resource1; if ( resource2 is allocated ) release resource2; ... if ( resourceN is allocated ) release resourceN; return hr; </code> To avoid that, I would prefer using what is called RAII (Resource Acquisition Is Initialization) in C++, i.e. you can wrap your "dynamic" resources in C++ classes, providing a destructor that does the cleanup job. In this way, when the wrapper class goes out of scope (e.g. at the end of the method body), before the "}", the C++ compiler calls the destructors, so allocated resources are safely and elegantly cleaned up, because their own destructors are called (the wrapper class instances are on the stack). In this case, a "return hr;" could be just fine, because the compiler will automatically call the destructors, and you need neither the duplicated cleanup, nor the goto to "Error" label. Giovanni
Show quote
"Arnshea" <arns***@gmail.com> wrote in message Not sure what you mean exactly with *variable* length array, but following news:d105694e-b83f-463c-8a6d-4192d79652fb@p69g2000hsa.googlegroups.com... > (apologies for the crosspost) > > I'm working with an MFC based COM object. From C# I'd like to be able > to call a method on the COM object that takes a string array and > modifies the contents. > > Is there any way to do this with a variable length array? > > I've only been able to get it to work with a fixed size array. The > relevant code snippets are below. Suggestions are greatly appreciated > (e.g., there's a better way to do it, the code will cause memory > leaks, etc...). > > MFC DISPATCH MAP ENTRY: > > DISP_FUNCTION_ID(CMyClass, "StrArrayFunc", dispidStrArrayFunc, > StrArrayFunc, VT_I4, VTS_VARIANT) > > > .H PROTOTYPE: > > HRESULT StrArrayFunc(VARIANT &vArray); > > > .CPP: > > HRESULT CMyClass::StrArrayFunc(VARIANT &vArray) > { > AFX_MANAGE_STATE(AfxGetStaticModuleState()); > > SAFEARRAY **ppsa = V_ARRAYREF(&vArray); > > // ... > } > > > MANAGED/C# CLIENT: > > static int Main(string[] args) > { > String[] ar = new string[30]; > > for (int i=0; i < ar.Length; i++) > ar[i] = "str"; > > Array a = (Array) ar; > > MyCOMServer.MyClassClass mc = new MyClassClass(); > > mc.StrArrayFunc(ref a); // INTEROP call > > string[] nAr = (string[]) a; > > // ... remaining code accesses modified array through nAr > } fills a List with a number of strings and passes it by ref to the COM method. Of course what gets passed is a fixed length array (precise bounds). List<string> stringList = new List<string>(); for(int i = 0; i < 5; i++) stringList .Add("str" ); Array sar = stringList .ToArray(); MyCOMServer.MyClassClass mc = new MyClassClass(); mc.StrArrayFunc(sar); .... IDL ..... HRESULT StrArrayFunc [in,out] SAFEARRAY(BSTR) vArray); C++ implementation ..... StrArrayFunc(SAFEARRAY* vArray) { ... } Willy. |
|||||||||||||||||||||||