Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
I recently was assigned the task of doing a POC on Modern App Automation, specifically that are to do with application state management. For instance things like,
- Activating the App.
- Suspending/Resuming/Terminating an app.
- App Bar activation.
- Snapping the app.
- Activate Sharing etc.
I started off with the unmanaged code presented in the blog post https://blogs.msdn.com/b/windowsappdev/archive/2012/09/04/automating-the-testing-of-windows-8-apps.aspx
--------------------------------------------------------------------------------------------------------------------------------
HRESULT LaunchApp(const std::wstring& strAppUserModelId, PDWORD pdwProcessId)
{
CComPtr<IApplicationActivationManager> spAppActivationManager;
HRESULT hrResult = E_INVALIDARG;
if (!strAppUserModelId.empty())
{
// Instantiate IApplicationActivationManager
hrResult = CoCreateInstance(CLSID_ApplicationActivationManager,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IApplicationActivationManager,
(LPVOID*)&spAppActivationManager);
if (SUCCEEDED(hrResult))
{
// This call ensures that the app is launched as the foreground window
hrResult = CoAllowSetForegroundWindow(spAppActivationManager, NULL);
// Launch the app
if (SUCCEEDED(hrResult))
{
hrResult = spAppActivationManager->ActivateApplication(strAppUserModelId.c_str(),
NULL,
AO_NONE,
pdwProcessId);
}
}
}
return hrResult;
}
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hrResult = S_OK;
if (SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
{
if (argc == 2)
{
DWORD dwProcessId = 0;
++argv;
hrResult = LaunchApp(*argv, &dwProcessId);
}
else
{
hrResult = E_INVALIDARG;
}
CoUninitialize();
}
return hrResult;
}
---------------------------------------------------------------------------------------------------------------------------------
One of the requirements was to be able to execute this from managed code. I did some reading on how to call Unmanaged COM Code from managed code and decided to try the below options.
- PINVOKE
- COM Class Wrappers. (https://msdn.microsoft.com/en-us/library/aa645736%28v=VS.71%29.aspx)
This article documents my efforts in getting the above piece of code executed from managed code using PINVOKE. I am not familiar with COM & Unmanaged Code (C++). I had to do a lot of learning before I attempted this.Some of the things that I write here can be very basic (as I mentioned this is my first try at Unmanaged COM code).
- I started by creating a C++ Dynamic Linked Library project called MAF32.
- Refactored the above code,
-
- Moved everything into a single method (Moved the logic with the main method into the Launch app method.)
- Updated the first parameter type from constant pointer to std::wstring to LPWSTR. This was done to facilitate marshaling of managed types while using PINVOKE.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HRESULT LaunchApp(LPWSTR strAppUserModelId, PDWORD pdwProcessId)
{
HRESULT hrResult =
S_OK;
if (SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
{
CComPtr<IApplicationActivationManager> spAppActivationManager;
HRESULT hrResult = E_INVALIDARG;
size_t strlength = wcslen(strAppUserModelId);
if (strlength != 0)
{
// Instantiate IApplicationActivationManager
hrResult = CoCreateInstance(CLSID_ApplicationActivationManager,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IApplicationActivationManager,
(LPVOID*)&spAppActivationManager);
if (SUCCEEDED(hrResult))
{
// This call ensures that the app is launched as the foreground window
hrResult = CoAllowSetForegroundWindow(spAppActivationManager,
NULL);
// Launch the app
if (SUCCEEDED(hrResult))
{
hrResult = spAppActivationManager->ActivateApplication(strAppUserModelId,
NULL,
AO_NONE,
pdwProcessId);
}
}
}
CoUninitialize();
}
return hrResult;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------
In short, the above COM code does the following.
- Initializes a COM instance (COM must be initialized for every thread that executes COM code).
- Creates an instance of the COM interface IApplicationActivationManager.
- Calls the activate application method.
- Un-Initializes the COM instance
3. Created a C# Console application project and added the following code which allows us to call the LaunchApp method from the Unmanaged dll (MAF32.dll).
[DllImport("MAF32.dll")] //Note: Name of the C++ dll that we created in Step 1.
public static extern int LaunchApp( //Note: Name of the method must be exactly same as the one defined in C++ code.
string processIdentifier,
out int processId); //The C++ code takes a pointer to DWORD as parameter. In C#, I am passing reference (note the out keyword) to a integer as a parameter.
4. Now in the Console application's Main method, I called the method as follows.
uint processId;
PlatformInvokeTest.LaunchApp(@"Microsoft.BingTravel_8wekyb3d8bbwe!AppexTravel", out processId);
Console.WriteLine(processId);
Note: For this to work, the unmanaged dll created in step1 (MAF32.dll) must be present in the same directory as that of the C# application.
5. On executing the managed code, I got "EntryPointNotFoundException".
6. I downloaded the Depends.exe (SysInternals tool) to see the methods exposed by MAF32.dll) .
Even though "MAF.dll" contains a method call LaunchApp, it is not visible to the clients. In order to make this method visible to the clients we must do one of the following
- Explicitly define the dll's interface using the dll export attribute. https://msdn.microsoft.com/en-us/library/3y1sfaz2(v=VS.80).aspx
- Use Module definition (.def) files to specify the methods that are exposed by the dll. https://msdn.microsoft.com/en-us/library/d91k01sh.aspx
I chose to follow the first method. So I made the below changes.
- Added the code "#define DllExport extern "C" __declspec( dllexport )" to the code.
- Decorated the LanuchApp method with the DllExport keyword.
Now on running the depends tool once again, you can see the LaunchApp method.
7. I re-executed the C# Console application. I no longer got the EntryPointNotFound exception. However the method doesn't launch the app as expected. On debugging I found that the call to the method
if(SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
always returns a failure and the rest of the code is not being executed. The CoInitializeEx method returns the error code "RPC_E_CHANGED_MODE" which means the COM has been already initialized on that thread in a different mode. From .Net 2.0 onwards CLR initializes COM on every Dot net thread in Multi Threaded Apartment state by default. The C++ code is trying to initialize it in Single threaded apartment state, Hence the error.
I have removed the call to CoInitializeEx and CoUninitialize since this is already handle by CLR.
8. On further execution, the call to the ActivateApplication method return the error code "-2144927148" (0x80270254) which is "E_APPLICATION_NOT_REGISTERED" . After debugging for quiet sometime and trial and error, I figured out that this was because of marshaling issues. I changed the below marshaling code as follows,
[DllImport("MAF32.dll")]
public static extern int LaunchApp(
[In, MarshalAs(UnmanagedType.LPWStr)]string processIdentifier,
[Out, MarshalAs(UnmanagedType.U4)] out int processId);
For more info on marshaling see
https://msdn.microsoft.com/en-us/library/aa288468(v=VS.71).aspx#pinvoke_defaultmarshaling
https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype(v=vs.71).aspx
After this I was able to launch the application from managed code.
Final Code for launching a modern app from Managed code is below.
*************Unmanaged code in MAF32.cpp************************
#include "stdafx.h"
#include "stdafx.h"
#include <shlobj.h>
#include <stdio.h>
#include <shobjidl.h>
#include <objbase.h>
#include <atlbase.h>
#include <string>
#define DllExport extern "C" __declspec( dllexport )
DllExport HRESULT LaunchApp(LPWSTRstrAppUserModelId, PDWORDpdwProcessId)
{
HRESULT hrResult = E_INVALIDARG;
CComPtr<IApplicationActivationManager> spAppActivationManager;
size_t strlength = wcslen(strAppUserModelId);
if (strlength != 0)
{
hrResult = CoCreateInstance(
CLSID_ApplicationActivationManager,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IApplicationActivationManager,
(LPVOID*)&spAppActivationManager);
if (SUCCEEDED(hrResult))
{
hrResult = CoAllowSetForegroundWindow(
spAppActivationManager,
NULL);
if (SUCCEEDED(hrResult))
{
hrResult = spAppActivationManager->ActivateApplication(
strAppUserModelId,
NULL,
AO_NONE,
pdwProcessId);
}
}
}
return hrResult;
}
**********************************************************************************************************************************
***************Managed Code ********************************************************************
class Program
{
static void Main(string[] args)
{
int processId;
Console.WriteLine(PlatformInvokeTest.LaunchApp(@"Microsoft.BingTravel_8wekyb3d8bbwe!AppexTravel", out processId));
Console.WriteLine(processId);
}
}
public class PlatformInvokeTest
{
[DllImport("MAF32.dll")]
public static extern int LaunchApp(
[In, MarshalAs(UnmanagedType.LPWStr)]string processIdentifier,
[Out, MarshalAs(UnmanagedType.U4)] outint processId);
}
Similar code can then be used to perform other application level activities from managed code.
Comments
- Anonymous
November 26, 2012
Great post - could you share a compiled DLL (I am NOT a C++ developer at all) ?