Last post we looked at how to write a device side agent. I am uploading the source for file viewer written using CoreCon API's.
How to use
- You need to have visual studio 2008 installed in your machine.
- Extract the zip file. It has 2 solutions and 2 xsl files.
- Take a back up of your global datastore directory
- In vista this is "C:\Users\All Users\Microsoft\corecon\1.0"
- In non-vista this is "c:\documents and settings\All users\Application Data\Microsoft\corecon\1.0"
- Copy the xsl files to "addons" directory inside the Global datastore directory.
- Build the solutions. Copy the RemoteAgent.exe to d:\temp\Armv4i directory. If you are keeping the file in different directory then you need to modify the RemoteAgent.xsl file accordingly (modify the RootPath property)
- Execute the FileViewer.exe - you have the fileviewer ready.
I have created the RemoteAgent.xsl package for Armv4i architecture only. If you want to work in any other architecture then you need to build the RemoteAgen.sln in the required architecture. Add a Packagetype entry in the Remoteagent.xsl for that architecture. In case you want to add support for X86, then add the following inside the Packagetypecontainer (in RemoteAgent.xsl)
<PACKAGETYPE Name="x86" ID="x86" Protected="True">
<PROPERTYCONTAINER>
<PROPERTY ID="RemotePath" Protected="True">%CSIDL_WINDOWS%</PROPERTY>
<PROPERTY ID="RootPath" Protected="True">D:\temp\x86</PROPERTY>
<PROPERTY ID="CommandLine" Protected="True"></PROPERTY>
<PROPERTY ID="CPU" Protected="True">x86</PROPERTY>
<PROPERTY ID="Host" Protected="True">RemoteAgent.exe</PROPERTY>
</PROPERTYCONTAINER>
<FILECONTAINER>
<FILE ID="RemoteAgent.exe" />
</FILECONTAINER>
</PACKAGETYPE>
Place the remoteagent.exe built for X86 in D:\temp\X86 directory.
--
Anand R
Attachment(s): fileviewer.zip
In the last post we saw how to create a package and use that for downloading set of files.
Writing Device Agent
What do I get if I write an my own agent?
- You can reliably start your agent. When the start call is completed you are guaranteed that your agent has started running in device.
- You can create stream independent of the underlying transport mechanism. You need not worry about whether DMA or TCP is the underlying transport.
- You can use the streams for your device-desktop interaction
- You need not worry about serializing and de-serializing the data you send.
- We provide a packet class which could be used to send and receive most of the basic data types.
Let’s see how we can write a device agent.
When an agent is started in device, it should call ‘AcknowledgeLaunch’.
Calling AcknowledgeLaunch signifies 2 things.
- Agent conveys that it has done with all its initialization and ready to accept connections from desktop
- It also registers the set of Ids that will be used to create streams.
Here is the code snippet to do AcknowledgeLaunch
HRESULT (*pfnGetDeviceService)(IDeviceAgentTransport**);
hMod = ::LoadLibrary(L "DeviceAgentTransport.dll");
if(hMod == NULL)
{
//Error Handle…
}
pfnGetDeviceService = (GetDeviceAgentTransportFunction) ::GetProcAddress(hMod,L"GetDeviceAgentTransport");
if( NULL == pfnGetDeviceService)
{
//Error Handle…
}
hr = pfnGetDeviceService(out_pTransport);
if(FAILED(hr))
{
//Error Handle…
}
//We got the DeviceAgentTransport. Call acknowledgelaunch to indicate all initializations are done.
hr = out_pService->AcknowledgeLaunch(count,lpServiceIds);
if(FAILED(hr))
{
//Error Handle…
}
Now, the Device agent is ready to accept connections…
IDevicePacketStream* out_pStream;
out_pService ->AcceptConnectionEx(lpServiceId ,&out_pStream);
AcceptConnectionEx is analogous to TCP accept call. This is a blocking call and will wait till a Connect (CreatePackaetStream) call is made from desktop with the same serviced. Note that this serviced has to be one among the registered service Ids (in AcknowledgeLaunch).
Send receive Packets
Let’s send some packets to desktop.
We need to create a packet, write the info into the packet and send the packet in stream.
GetNewPacketFunc packFunc;
HINSTANCE hMod = ::LoadLibrary(L "DeviceAgentTransport.dll");
packFunc = (GetNewPacketFunc) ::GetProcAddress(hMod, L"GetNewPacket");
if( NULL == packFunc)
{
//Error Handle…
}
IPacket *writePacket;
packFunc(&writePacket);
writePacket->WriteString(szSendString); //Some string to be sent to desktop
out_pStream->write(writePacket);
Just like in TCP, Read is a blocking call. Below call will wait till some data is sent from the desktop.
IPacket *packet;
packFunc (&packet);
hr =out_pStream->Read(&packet);
Desktop Side
So far we are discussing about how the device agent will look like. We will look at the desktop counterpart for this.
//PackageId is the Guid corresponding to the package we want to deploy
RemoteAgent mRemoteAgent = Device.GetRemoteAgent(new ObjectId(packageID));
//This will start the agent. This call will be blocked till AcknowledgeLaunch is called in device side.
mRemoteAgent.Start(“”);
//This is the connect call.
//This will unblock the AcceptConnectionExe in device
DevicePAcketStream mStream = mRemoteAgent.CreatePacketStream(streamId);
Packet pkt = new Packet();
Pkt.WriteString(“hai”);
mStream.write(pkt);
pkt = mStream.Read();
string msg = pkt.ReadString();
Below is the sequence diagram for the desktop and device interaction
In next post we will look at actual File Viewer written using CoreCon API's.
--
Anand R
Package
More than often we would like to deploy multiple files as part of our application. Also these files might vary depending on the architecture of the device. To handle these CoreCon provides something called packages. Packages files are present in the datastore (e.g Microsoft.RemoteTools.Packages.xsl).
Let’s Look at a sample package(FileListPackage.xsl) which we can use for our FileList utility.
<?xml version="1.0" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<ADDONCONTAINER>
<ADDON>
<PACKAGECONTAINER>
<PACKAGE ID="0695ABA4-6A85-484d-8C07-5E67FE44D6BC" NAME="FileList">
<PACKAGETYPECONTAINER>
<PACKAGETYPE Name="ARMV4" ID="ARMV4" Protected="true">
<PROPERTYCONTAINER>
<PROPERTY ID="RemotePath" Protected="true">%CSIDL_PROGRAM_FILES%</PROPERTY>
<PROPERTY ID="RootPath" Protected="true">%CSIDL_PROGRAM_FILES%\MyUtility\ARMV4</PROPERTY>
<PROPERTY ID="CommandLine" Protected="true"/>
<PROPERTY ID="CPU" Protected="true">ARMV4</PROPERTY>
</PROPERTYCONTAINER>
<FILECONTAINER>
<FILE ID="FileList.exe"/>
</FILECONTAINER>
</PACKAGETYPE>
<PACKAGETYPE Name="ARMV4I" ID="ARMV4I" Protected="true">
<PROPERTYCONTAINER>
<PROPERTY ID="RemotePath" Protected="true">%CSIDL_PROGRAM_FILES%</PROPERTY>
<PROPERTY ID="RootPath" Protected="true">%CSIDL_PROGRAM_FILES%\MyUtility\ARMV4I</PROPERTY>
<PROPERTY ID="CommandLine" Protected="true"/>
<PROPERTY ID="CPU" Protected="true">ARMV4I</PROPERTY>
</PROPERTYCONTAINER>
<FILECONTAINER>
<FILE ID="FileList.exe"/>
</FILECONTAINER>
</PACKAGETYPE>
<PACKAGETYPE Name="X86" ID="X86" Protected="true">
<PROPERTYCONTAINER>
<PROPERTY ID="RemotePath" Protected="true">%CSIDL_PROGRAM_FILES%</PROPERTY>
<PROPERTY ID="RootPath" Protected="true">%CSIDL_PROGRAM_FILES%\MyUtility\X86</PROPERTY>
<PROPERTY ID="CommandLine" Protected="true"/>
<PROPERTY ID="CPU" Protected="true">X86</PROPERTY>
</PROPERTYCONTAINER>
<FILECONTAINER>
<FILE ID="FileList.exe"/>
</FILECONTAINER>
</PACKAGETYPE>
</PACKAGETYPECONTAINER>
<PROPERTYCONTAINER/>
</PACKAGE>
</PACKAGECONTAINER>
</ADDON>
</ADDONCONTAINER>
</xsl:template>
</xsl:stylesheet>
Using Packages
Build the FileList.cpp for all the required Platforms and place the binaries under ‘Program Files\MyUtility\<Arch>’ (e.g Program Files \MyUtility\Armv4I\FileList.exe).
Let’s modify the utility to make use of the package. We need to do 2 things for that
- Place the FileListPackage.xsl file in Global datastore directory (Documents and Settings\All users\Application Data\Microsoft\CoreCon\1.0\Addons).
- Replace the FeilDeployer.sendfile with FileDeployer.DownloadPackage
Replace the line
fileDeployer.SendFile("FileList.exe", @"%CSIDL_PROGRAM_FILES%\FileList.exe");
With the below 2 lines
ObjectId packageObjID = new ObjectId("0695ABA4-6A85-484d-8C07-5E67FE44D6BC");
fileDeployer.DownloadPackage(packageObjID);
Now, we have made the FileList Utility architecture agnostic. Won’t it be good if there is way to know whether my utility has started properly in device. Won't it be good if I could interact with my utility, send/receive inputs and make it more interactive. Yes all are good and possible using CoreCon APIs. We will see how to write a device agent and build a FileViewer of our own.
Let’s see some ways to do send/receive files to device and some process related functions. CoreCon provides a class called FileDeployer using which we can do file transfers across device and desktop. You should have established connection (Device.Connect()) to use the FileDeployer methods.
Device myDevice = GetPPC05Emulator();
myDevice.Connect();
FileDeployer fileDeployer = myDevice.GetFileDeployer();
fileDeployer.SendFile("foo.txt",@"%CSIDL_PROGRAM_FILES%\foo.txt");
This deploys the foo.txt (present in current directory) to “\Program files” folder in device. Note that you can use CSIDL paths in the both source and destination file names. CSIDL values provide a unique system-independent way to identify special folders used frequently by applications, but which may not have the same name or location on any given system. Similarly there are APIs to do receive file and deploy bunch of files (using package) at a time. I will be discussing about the packages in detail in following blogs.
RemoteProcess class gives us a way to start/stop process. Below code snippet starts a new process. Waits for some time (5 seconds), List all the process running and kills the started process.
Device myDevice = GetPPC05Emulator();
myDevice.Connect();
//Start the calculator process in device
RemoteProcess remoteProc = myDevice.GetRemoteProcess();
bool bStarted = remoteProc.Start(@"%CSIDL_WINDOWS%\calc.exe", " ");
//List all running process
Collection<RemoteProcess> remoteProcCollection = myDevice.GetRunningProcesses();
int num = 0;
foreach (RemoteProcess rproc in remoteProcCollection)
{
num++;
System.Console.WriteLine(num.ToString() + ". " + rproc.FileName);
}
//Kill the process
if (remoteProc.HasExited() == false)
{
remoteProc.Kill();
}
Let’s put all these together and write a small utility to do file listing in device. We will have a native app(FileList.exe) in device which will iterate the specified directory and print file names in a log. Our app will deploy this filelist.exe, run it with various parameters, get the log file and print in the console.
Create a Smartdevice console app targeting PPC 05. Copy paste the below code.
Source for FileList.cpp
/*
* Gets the list of files/directories present int the path specified in cmd line argument.
*/
int _tmain(int argc, _TCHAR* argv[])
{
int iRetVal = 0;
if(argc <=1)
{
//No args passed
return -1;
}
DeleteFile(L"\\foo.txt");
HANDLE hLog = CreateFile(L"\\foo.txt",GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hLog == INVALID_HANDLE_VALUE)
{
//Error in creating log file
return -1;
}
//Search the Directory
TCHAR* szPath = new TCHAR[_tcslen(argv[1]) + 4];
_tcscpy(szPath, argv[1]);
_tcscat(szPath, L"\\*");
WIN32_FIND_DATA findData;
HANDLE hSearchFiles = INVALID_HANDLE_VALUE;
hSearchFiles = FindFirstFile(szPath, &findData);
if(hSearchFiles != INVALID_HANDLE_VALUE)
{
do
{
TCHAR* szBuffer = new TCHAR[_tcslen(findData.cFileName) + 4];
_tcscpy(szBuffer, findData.cFileName );
_tcscat(szBuffer,L"\r\n");
DWORD numBytes;
WriteFile(hLog, szBuffer, _tcslen(szBuffer)*sizeof(TCHAR), &numBytes, NULL);
delete[] szBuffer;
}while(FindNextFile(hSearchFiles, &findData)) ;
}
else
{
iRetVal = -1;
}
//Clean up
if(szPath != NULL)
{
delete[] szPath;
szPath = NULL;
}
if(hLog != INVALID_HANDLE_VALUE)
CloseHandle(hLog);
if(hSearchFiles != INVALID_HANDLE_VALUE)
CloseHandle(hSearchFiles);
return iRetVal;
}
Create a C# console application. Copy paste the below code in program.cs.
Place the FileList.exe in the same directory as output directory.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SmartDevice.Connectivity;
using System.Collections.ObjectModel;
namespace sample1
{
class Program
{
static void Main(string[] args)
{
Device myDevice = GetPPC05Emulator();
//Connect to the device.
myDevice.Connect();
FileDeployer fileDeployer = myDevice.GetFileDeployer();
fileDeployer.SendFile("FileList.exe", @"%CSIDL_PROGRAM_FILES%\FileList.exe");
//Start FileList process in device
RemoteProcess remoteProc = myDevice.GetRemoteProcess();
bool bStarted = remoteProc.Start(@"%CSIDL_PROGRAM_FILES%\FileList.exe", @"\Program files");
if (bStarted == true)
{
while (remoteProc.HasExited() == false)
{
System.Threading.Thread.Sleep(2000);
}
}
int exitCode = remoteProc.GetExitCode();
if (exitCode == 0)
{
fileDeployer.ReceiveFile(@"\foo.txt", "Filelist.txt");
}
}
static Device GetPPC05Emulator()
{
DatastoreManager dsmgrObj = new DatastoreManager(1033);
ObjectId ppc05PlatObjID = new ObjectId("4118C335-430C-497f-BE48-11C3316B135E");
ObjectId PPC05EmuID = new ObjectId("25D984D9-0DFE-4DB1-A5A0-9A4F660BF2CE");
Platform ppc05Plat = dsmgrObj.GetPlatform(ppc05PlatObjID);
Device ppc05Emu = ppc05Plat.GetDevice(PPC05EmuID);
return ppc05Emu;
}
}
}
Now, this utility could be used to do file listing of device.
--
Anand R
VSD is shipping Set of connectivity API which could be used to write remote tools (Remote file viewer etc) kind of Apps. This series of blog is aimed at giving an introduction about the connectivity APIs.
Datastore is a set of XML files on your desktop computer that contains information about the platforms, devices, emulators, and packages that are installed on the computer. The contents of the Datastore are modified whenever you install an SDK that is based on Windows CE such as Windows Mobile. Let’s start with an app which looks at the datastore and enumerate list of SDKs and devices installed in the machine.
[Create a new C# Desktop console application. Add reference to Microsoft.Smartdevice.Connectivity.dll which can be found at <systemdrive>:\Program files\Common Files\Microsoft Shared\CoreCon\1.0\Bin ]
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SmartDevice.Connectivity;
using System.Collections.ObjectModel;
namespace sample1
{
class Program
{
static void Main(string[] args)
{
//Instead of 1033 use the locale ID of your app.
DatastoreManager dsmgrObj = new DatastoreManager(1033);
//Get all the platform entries present in Data store
//Iterate through Collection of platforms
Collection<Platform> platformCollection = dsmgrObj.GetPlatforms();
foreach (Platform objplatform in platformCollection)
{
System.Console.WriteLine(objplatform.Name);
Collection<Device> deviceCollection = objplatform.GetDevices();
//List all the devices in the platform
foreach (Device objdevice in deviceCollection)
{
System.Console.WriteLine("\t" + objdevice.Name);
}
}
}
}
}
Let’s look at some options to connect to a device and get info regarding the device.
In the below sample I am connecting to PPC 05 emulator and getting info regarding virtual and physical memory. IDs of devices/SDK can be obtained from the datastore files which can be found in
<sysDrive>:\Documents and Settings\<username> \Local Settings\Application Data\Microsoft\CoreCon\1.0 (Non Vista OS) or <sysDrive>:\users\<username>\Application data\Microsoft\CoreCon\1.0 (Vista)
Open the conman_ds_platform.xsl file in visual studio. This file contains the information about all the installed SDKs.
DatastoreManager dsmgrObj = new DatastoreManager(1033);
ObjectId ppc05PlatObjID = new ObjectId("4118C335-430C-497f-BE48-11C3316B135E");
ObjectId PPC05EmuID = new ObjectId("25D984D9-0DFE-4DB1-A5A0-9A4F660BF2CE");
Platform ppc05Plat = dsmgrObj.GetPlatform(ppc05PlatObjID);
Device ppc05Emu = ppc05Plat.GetDevice(PPC05EmuID);
//Connect to the device.
ppc05Emu.Connect();
SystemInfo pp05EmuInfo = ppc05Emu.GetSystemInfo();
System.Console.WriteLine("Available Physical Memoory\t:\t"+pp05EmuInfo.AvailPhys);
System.Console.WriteLine("Total Physical Memoory \t:\t" + pp05EmuInfo.TotalPhys);
System.Console.WriteLine("Available Virtual Memoory \t:\t" + pp05EmuInfo.AvailVirtual);
System.