I have a custom action dll, written in C++, which is invoked using a button during install. The purpose of the custom action is to capture the clipboard contents and evaluate whether or not the contents is in the format of a valid product key. If it is, then the 'PRODUCTKEY' property is updated, as is another property which lets me know that we have been successful.
<Control Id="PasteButton" Type="PushButton" X="25" Y="176" Width="25" Height="16" Default="yes" Text="Paste" >
<Publish Event="DoAction" Value="MsiCheckClipboardForKey" Order="1">1</Publish>
<Publish Property="PRODUCTKEY" Value="[PRODUCTKEY]" Order="2">ClipboardSuccess = 1</Publish>
</Control>
Unfortunately, the install is failing after this custom action has been called. However, from looking at the install log my properties have been changed which tends to suggest that my custom action code is running successfully.
MSI (c) (20:4C) [12:04:55:281]: Invoking remote custom action. DLL:
C:\Users\xxxxx\AppData\Local\Temp\MSI5652.tmp, Entrypoint:
msiCheckClipboardForKey
MSI (c) (20!C4) [12:04:57:746]: PROPERTY CHANGE: Modifying
ClipboardSuccess property. Its current value is '0'. Its new value:
'1'.
MSI (c) (20!C4) [12:04:57:746]: PROPERTY CHANGE: Adding PRODUCTKEY
property. Its value is 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx'.
Action ended 12:04:57: MsiCheckClipboardForKey. Return value 3.
DEBUG: Error 2896: Executing action MsiCheckClipboardForKey failed.
This is the custom action code:
#include "StdAfx.h"
#include "Debug.h"
#pragma comment(linker, "/EXPORT:msiCheckClipboardForKey=_msiCheckClipboardForKey#4")
BOOL GetClipboardText ( IN OUT CString& strClipBoardText)
{
strClipBoardText = _T("");
BOOL bOK = FALSE;
UINT uFormat = 0;
// We need to explicitly query the clipboard for UNICODE text
// if we have a UNICODE application
#ifdef _UNICODE
uFormat = CF_UNICODETEXT;
#else
uFormat = CF_TEXT;
#endif
if ( ::IsClipboardFormatAvailable ( uFormat ) )
{
if ( ::OpenClipboard ( NULL ) )
{
HANDLE hClipBrdData = NULL;
if ( HANDLE hClipBrdData = ::GetClipboardData ( uFormat ) )
{
if ( LPTSTR lpClipBrdText = ( LPTSTR ) ::GlobalLock ( hClipBrdData ) )
{
MessageBox("Clipboard Text",lpClipBrdText,NULL,NULL);
strClipBoardText = lpClipBrdText;
::GlobalUnlock ( hClipBrdData );
bOK = TRUE;
}
}
::CloseClipboard();
}
}
return bOK;
}
extern "C" UINT __stdcall msiCheckClipboardForKey(MSIHANDLE hMSI)
{
CString strClipboardText ( _T("") );
if ( GetClipboardText ( strClipboardText ) )
{
DebugMsg ( hMSI, _T("Found clipboard text") );
strClipboardText.Trim();
// Look at the length. Is it 25 (wih no dashes/slashes) or 29 (with dashes/slashes)?
BOOL bValidLength = strClipboardText.Find ( '-' ) != -1 || strClipboardText.Find ( '/' ) != -1 ? strClipboardText.GetLength() == 29 : strClipboardText.GetLength() == 25;
DebugMsg ( hMSI, _T("Is it a product key? %b",bValidLength) );
if ( bValidLength )
{
//strClipboardText.Remove ( '-' );
//strClipboardText.Remove ( '/' );
MessageBox("Formatted Clipboard Text",strClipboardText,NULL,NULL);
MsiSetProperty(hMSI, "ClipboardSuccess", "1");
MsiSetProperty(hMSI, "PRODUCTKEY", strClipboardText);
return 0;
}
}
return 1; // None-zero is error state
}
Not sure what the problem could be, even more so as the custom action does seem to be executed as the properties are set correctly.
After tidying the code to add to this question and rebuilding I found that I was only getting the error when I had a string on the clipboard which wasn't evaluated as a product key format. Therefore the custom action was returning 1. I believe this to be the reason that my install failed. A return value of 1 obviously causing the install error.
Please correct me if I am wrong but this has fixed the issue.
Related
I'm using C++ for Microsoft Word 2010 automation. When the user closes the application and my programm wants to use the previously obtained IDispatch interface, the programm crashes (unhandled exception). Simular VBA code in Excel gives an "Error 462: The remote server does not exist" error. How can I detect that the application has been closed by the user in a way Excel does.
#ifdef __NO_PRECOMPILED_HEADERS__
#include "generic/platformdefs.h"
#endif
#include "test_word.h"
/*
* Include the right atlbase.h (depends on the compiler)
*/
#include "compat/which_atlbase.h"
static OLECHAR FAR *VISIBLE =
{
OLESTR( "Visible" )
} ;
static OLECHAR FAR *QUIT =
{
OLESTR( "quit" )
} ;
static void
VarSetBool( VARIANT *v , BOOL value )
{
V_VT( v ) = VT_BOOL ;
V_BOOL( v ) = value ? VARIANT_TRUE : VARIANT_FALSE ;
}
static void
DispatchPropertyPut
(
CComPtr<IDispatch> dispatch ,
OLECHAR FAR *property ,
VARIANT *value
)
{
HRESULT status ;
DISPID dispid ,
propertyput ;
DISPPARAMS parameters ;
UINT n_argument_error;
VARIANT result ;
/*
* Get the dispatch id of the method and arguments to invoke
*/
status = dispatch->GetIDsOfNames( IID_NULL ,
&property ,
1 ,
LOCALE_USER_DEFAULT ,
&dispid ) ;
if( !SUCCEEDED( status ) )
{
throw( 1 ) ;
}
/*
* Initialize result
*/
VariantInit( &result ) ;
/*
* need to be able to take the address of this
*/
propertyput = DISPID_PROPERTYPUT ;
/*
* Setup the parameters
*/
parameters.cNamedArgs = 1 ;
parameters.rgdispidNamedArgs = &propertyput ;
parameters.cArgs = 1 ;
parameters.rgvarg = value ;
/*
* Get the object
*/
status = dispatch->Invoke( dispid ,
IID_NULL,
LOCALE_USER_DEFAULT,
DISPATCH_PROPERTYPUT ,
¶meters ,
&result ,
0,
&n_argument_error ) ;
/*
* Cleanup result if any
*/
VariantClear( &result ) ;
/*
* Success ?
*/
if( !SUCCEEDED( status ) )
{
throw( 2 ) ;
}
}
static void
DispatchInvoke
(
CComPtr<IDispatch> dispatch ,
OLECHAR FAR *method
)
{
DISPID dispid ;
HRESULT status ;
DISPPARAMS parameters ;
status = dispatch->GetIDsOfNames( IID_NULL ,
&method ,
1 ,
LOCALE_USER_DEFAULT ,
&dispid ) ;
if( !SUCCEEDED( status ) )
{
throw( 3 ) ;
}
parameters.cNamedArgs = 0 ;
parameters.rgdispidNamedArgs = 0 ;
parameters.cArgs = 0 ;
parameters.rgvarg = 0 ;
status = dispatch->Invoke( dispid ,
IID_NULL,
LOCALE_USER_DEFAULT,
DISPATCH_METHOD ,
¶meters ,
0 ,
0 ,
0 ) ;
if( !SUCCEEDED( status ) )
{
throw( 4 ) ;
}
}
void
test_word( int argc , char *argv[] , void *data )
{
CComPtr<IDispatch> word ;
VARIANT v ;
HRESULT hr ;
OleInitialize( NULL ) ;
try
{
/*
* The metrowerks compiler doesn't handle __uuidof()
*/
# ifdef __MWERKS__
hr = word.CoCreateInstance( OLESTR( "Word.Application" ) ,
IID_IDispatch ,
0 ,
CLSCTX_SERVER ) ;
# else
hr = word.CoCreateInstance( OLESTR( "Word.Application" ) ,
0 ,
CLSCTX_SERVER ) ;
# endif
if( !SUCCEEDED( hr ) )
{
throw( 6 ) ;
}
VariantInit( &v ) ;
VarSetBool( &v , TRUE ) ;
DispatchPropertyPut( word , VISIBLE , &v ) ;
DispatchInvoke( word , QUIT ) ;
VarSetBool( &v , FALSE ) ;
/*
* This will crash the application
*/
DispatchPropertyPut( word , VISIBLE , &v ) ;
}
catch( int where )
{
fprintf( stderr , "Exception caught %d\n" , where ) ;
}
word.Release() ;
OleUninitialize() ;
}
The excel vba macro looks like this:
Sub test_word()
Dim word As Object
Set word = CreateObject("word.application")
word.Visible = True
word.quit()
'
' Quit the word application before the next statement
' and you will get Error 462: The remote server does not exist
'
word.Visible = False
Set word = Nothing
End Sub
This is more of a comment, but comments are limited...it might work as a solution...
Well, generally you wouldn't do what you're doing. You must be setting a breakpoint before the second property put and at that point manually closing Word. Generally, you would capture events from Word and then when Word closed, you would receive notification and then know not to use your interface pointer any more. To protect against not catching events, I might suggest first creating the application to get an IUnknown pointer first. Then when you want to make a call to IDispatch, query for the IDispatch and then make the call, and then release the IDispatch.
When you first create with an IUnknown, it will create an in process handler IUnknown for you. On the first call to query for IDispatch, it will at that point actually start up Word. On subsequent calls to QI for IDispatch and then make the call, the handler may be smart enough to just gracefully fail if Word has been shut down--or it may not. But, I would start there...if you don't want to catch events.
The real proper way to get events from Word and look for the Closing or Closed events.
Wow... just looked at Word events. Looks like there is a Quit event but not a Closing or BeforeClosing event or anything like that. So, you'd want to catch the Quit event. After that, set a flag or release your IDispatch interface and never use it again.
Interesting. Is Word 2010 all patched up? I ran the following against Word 2016 (what I have installed). It's also using Visual Studio 2017 but should compile on any Visual C++ in the last 10 years:
#include <comdef.h>
#include <atlbase.h>
class COleInitialize
{
public:
COleInitialize()
{
OleInitialize(NULL);
}
~COleInitialize()
{
OleUninitialize();
}
};
int main(int argc, char* argv[])
{
COleInitialize _oleinit;
IUnknownPtr lpUnk;
lpUnk.CreateInstance(L"Word.Application");
CComDispatchDriver disp(lpUnk);
disp.PutPropertyByName(L"Visible", &_variant_t(VARIANT_TRUE));
MessageBox(NULL, "Close Word", "Prompt", MB_OK);
return 0;
}
I am using a wxProgressDialog on Windows. Whenever I change the text in the dialog the dialog box re-sizes to best accommodate the text, this leads to the dialog frequently re-sizing which looks terrible.
I tried SetMinSize and SetSizeHints but these seemed to have no effect. SetSize also seems not to work.
(For info, the dialog is show progress of file transfer. As each file is transferred its full path is displayed. These vary greatly leading the continual re-sizing.)
My code is based on this code from the samples:
static const int max = 100;
wxProgressDialog dialog("Progress dialog example",
// "Reserve" enough space for the multiline
// messages below, we'll change it anyhow
// immediately in the loop below
wxString(' ', 100) + "\n\n\n\n",
max, // range
this, // parent
wxPD_CAN_ABORT |
wxPD_CAN_SKIP |
wxPD_APP_MODAL |
//wxPD_AUTO_HIDE | // -- try this as well
wxPD_ELAPSED_TIME |
wxPD_ESTIMATED_TIME |
wxPD_REMAINING_TIME |
wxPD_SMOOTH // - makes indeterminate mode bar on WinXP very small
);
bool cont = true;
for ( int i = 0; i <= max; i++ )
{
wxString msg;
// test both modes of wxProgressDialog behaviour: start in
// indeterminate mode but switch to the determinate one later
const bool determinate = i > max/2;
if ( i == max )
{
msg = "That's all, folks!\n"
"\n"
"Nothing to see here any more.";
}
else if ( !determinate )
{
msg = "Testing indeterminate mode\n"
"\n"
"This mode allows you to show to the user\n"
"that something is going on even if you don't know\n"
"when exactly will you finish.";
}
else if ( determinate )
{
msg = "Now in standard determinate mode\n"
"\n"
"This is the standard usage mode in which you\n"
"update the dialog after performing each new step of work.\n"
"It requires knowing the total number of steps in advance.";
}
// will be set to true if "Skip" button was pressed
bool skip = false;
if ( determinate )
{
cont = dialog.Update(i, msg, &skip);
}
else
{
cont = dialog.Pulse(msg, &skip);
}
// each skip will move progress about quarter forward
if ( skip )
{
i += max/4;
if ( i >= 100 )
i = 99;
}
if ( !cont )
{
if ( wxMessageBox(wxT("Do you really want to cancel?"),
wxT("Progress dialog question"), // caption
wxYES_NO | wxICON_QUESTION) == wxYES )
break;
cont = true;
dialog.Resume();
}
wxMilliSleep(200);
}
if ( !cont )
{
wxLogStatus(wxT("Progress dialog aborted!"));
}
else
{
wxLogStatus(wxT("Countdown from %d finished"), max);
}
}
Select a size for the file path display. If the path is shorter, add blanks, if longer replace with ellipses ( ... )
wxControl::Ellipsize is useful for this ( http://docs.wxwidgets.org/3.1/classwx_control.html#a0bb834cae2a8986aceddb89f84ef4ed1 )
If you want a fixed size, you need to use wxGenericProgressDialog, there doesn't seem to be any way of preventing the native dialog, used by default under the systems that support it (Vista and later), of adapting its size to its contents.
My code,
LPSTR Internal::Gz_GetSystemKey( BOOL SHOW_ERROR, BOOL SHOW_KEY ) {
HW_PROFILE_INFO HwProfInfo;
if (!GetCurrentHwProfile(&HwProfInfo))
{
if(SHOW_ERROR)
Message::Error( "An Internal Error Has Occurred", "Gizmo Message", TRUE );
return NULL;
}
std::string __clean( (char*)HwProfInfo.szHwProfileGuid );
__clean.append( std::string( (char*)HwProfInfo.szHwProfileName ) );
LPSTR neet_key = Crypt::CRC32( Crypt::MD5( (char*)__clean.c_str() ) );
if (SHOW_KEY)
Message::Info( neet_key ); // shows expected result
return neet_key; // returns strange ascii result
};
Gz BOOL Gz_CreateContext( BOOL SHOW_ERROR, BOOL SHOW_KEY ) {
HKEY CHECK; // key result container
BOOL RESULT;
std::wstring neet_key_uni; // must use unicode string in RegSetValueExW
if ( RegOpenKey(HKEY_CURRENT_USER, TEXT("Software\\NEET\\Gizmo\\"), &CHECK) != ERROR_SUCCESS )
goto CREATE_REG_CONTEXT;
else
goto STORE_NEET_KEY;
CREATE_REG_CONTEXT:
if ( RegCreateKeyA( HKEY_CURRENT_USER, "Software\\NEET\\Gizmo\\", &CHECK ) != ERROR_SUCCESS ) {
if( SHOW_ERROR )
Message::Error( "Context Could Not Be Created" );
RESULT = FALSE;
goto END_MACRO;
}
STORE_NEET_KEY:
LPSTR neet_key = Internal::Gz_GetSystemKey( SHOW_ERROR, SHOW_KEY ); // GetSystemKey generates good key, returns weird ascii
Message::Notify( neet_key );
neet_key_uni = std::wstring(neet_key, neet_key+strlen(neet_key));
if ( RegSetValueEx( CHECK, TEXT("Key"), 0, REG_SZ, (const BYTE*)neet_key_uni.c_str(), ( neet_key_uni.size() + 1 ) * sizeof( wchar_t ) ) != ERROR_SUCCESS ) {
if( SHOW_ERROR )
Message::Error( "Context Could Not Be Reached" );
RESULT = FALSE;
goto END_MACRO;
}
RESULT = TRUE;
END_MACRO:
RegCloseKey(CHECK); // safely close registry key
return RESULT;
};
I'm creating a simple PC identification lib for practice, not for commercial use.
Message::Info( neet_key );
Shows
but the actual return value is
Any ideas why? The 'Message' namespace/functions are just message boxes. As for the 'Crypt' namespace/functions, they aren't the issue at hand.
From the comments: Who owns the memory for the 'neet_key'? My guess would be that the 'Message::Info' shows a valid value because whatever memory structure its from is still in memory but when you return its no longer in memory. Therefore the returned value prints rubbish.
This is a common issue for the C++ language. I would highly recommend that you avoid using raw pointers where possible (especially when returning from functions/methods). For strings you could obviously use 'std::string'.
New to windows programming, there are several examples all over the internet of what i am about to ask however none of them show the comparison which i think is failing.
I'm using several windows api calls throughout my C++ Program and just need some guidence on how to use them correctly.
For example below i have GetFileAttributes() which returns anything from File Attribute Constants.
DWORD dwAttributes = GetFileAttributes(strPathOfFile.c_str());
if ( dwAttributes != 0xffffffff )
{
if ( dwAttributes == FILE_ATTRIBUTE_NORMAL )
{
pkFileInfoList->Add( strPathOfFile + "\t" +"FILE_ATTRIBUTE_NORMAL");
}
else if ( dwAttributes == FILE_ATTRIBUTE_ARCHIVE )
{
pkFileInfoList->Add( strPathOfFile + "\t" + "FILE_ATTRIBUTE_ARCHIVE");
}
}
[/CODE]
The if/else statement continues with everything from File Attribute Constants.
Am i using this correctly, i have a directory with over 2500 files which i am feeding the path recusivly. It is always returning FILE_ATTRIBUTE_ARCHIVE.
Thanks,
GetFileAttributes returns a set of attributes, not a single attribute, so to test correctly you should do:
DWORD dwAttributes = GetFileAttributes(strPathOfFile.c_str());
if ( dwAttributes != 0xffffffff )
{
if ( dwAttributes & FILE_ATTRIBUTE_NORMAL )
{
pkFileInfoList->Add( strPathOfFile + "\t" +"FILE_ATTRIBUTE_NORMAL");
}
else if ( dwAttributes & FILE_ATTRIBUTE_ARCHIVE )
{
pkFileInfoList->Add( strPathOfFile + "\t" + "FILE_ATTRIBUTE_ARCHIVE");
}
}
I.e. use bitwise & instead of ==
In my NPAPI plugin, some of the objects have an "onEvent" property that is readable and writeable, and which is called on certain events.
What I have in my Javascript code will look like this:
myObject.onEvent = function( event ) {
console.log("Event: " + event );
}
// if I put this next line, the next call to the 'onEvent' handler will SIGBUS
// when there's no RetainObject() in the getter.
console.log("Event handler : " + myObject.onEvent);
And on the C++ side of the plugin, I have that kind of code:
bool MyPluginObject::getOnEvent(NPIdentifier id, NPVariant *result)
{
if( _onEvent )
{
OBJECT_TO_NPVARIANT( _onEvent, *result);
NPN_RetainObject( _onEvent ); // needed ???? why??
}
else
VOID_TO_NPVARIANT(*result);
return true;
}
bool MyPluginObject::setOnEvent( NPIdentifier id, const NPVariant *value )
{
if ( value && NPVARIANT_IS_OBJECT( *value ) )
{
if( _onEvent != NULL )
{
// release any previous function retained
NPN_ReleaseObject( _onEvent );
}
_onEvent = NPVARIANT_TO_OBJECT( *value );
NPN_RetainObject( _onEvent ); // normal retain
return true;
}
return false;
}
void MyPluginObject::onEvent(void)
{
NPVariant event = [...];
if ( _onEvent!= NULL )
{
NPVariant retVal;
bool success = NPN_InvokeDefault( _Npp, _onEvent, &event, 1, &retVal );
if( success )
{
NPN_ReleaseVariantValue(&retVal);
}
}
}
What's strange is that I've been struggling with a SIGBUS problem for a while, and once I added the NPN_RetainObject() in the getter, as you can see above, everything went fine.
I didn't find in the statement that it is needed in the Mozilla doc, neither in Taxilian's awesome doc about NPAPI.
I don't get it: when the browser requests a property that I've retained, why do I have to retain it a second time?
Should I maybe retain the function when calling InvokeDefault() on it instead? But then, why?? I already stated that I wanted to retain it.
Does getProperty() or InvokeDefault() actually does an NPN_ReleaseObject() without telling me?
You always have to retain object out-parameters with NPAPI, this is not specific to property getters.
In your specific case the object may stay alive anyway, but not in the general case:
Consider returning an object to the caller that you don't plan on keeping alive from your plugin. You have to transfer ownership to the caller and you can't return objects with a retain count of 0.