Tuesday, September 27, 2005
Off to Seattle
Packing time again, this time I'm leaving to Seattle to attend the MVP Global Summit. I will try to make some blog entries about any non-NDA material that comes along (if I manage to handle the jet-lag, of course...)
Sunday, September 25, 2005
Supporting ActiveSync 4.0
The technology behind the products I've been developing was initially based on ActiveSync and RAPI (Remote API). Now, some of them support TCP/IP connections to a device server using the same component but, for the most part, ActiveSync is still the connection method most customers choose.
My first approach to consuming RAPI was quite simple: a static link to rapi.lib. This meant that all the products were tied to a particular version of ActiveSync and would not run in another. By linking to a specific version's lib file we are actually precluding the possibility of linking to other versions, both older or newer.
It so happens that util recently all applciations were statically linked to ActiveSync 3.8, meaning that you had to install this particular version in order for the applications to run. Incidentally, I got a request from two different customers, one asking for ActiveSync 4.0 support, and the other was asking for version 3.6 support. Now what?
Publishing different versions of the applications was obviously out of the question. There had to be a better way of supporting mutiple versions of RAPI. Then I recalled something I read in one of Doug Boling's books: use dynamic linking. The idea is to dynamically load the RAPI.DLL file and retrieve the entry points to the functions you need by using GetProcAddress. To make things easier, I wrote a very simple helper class, of which I'm highlighting a few snippets.
First, the class declaration:
I'm only including a few methods to illustrate the technique. You should be able to extend your own version with the calls you need.
Let's look at the constructor and destructor:
As you can see, we dynamically load and unload the RAPI.DLL on the constructor and destructor. You should have no problems in nesting these - you will only be incrementing or decrementing the module load count. Now, let's see how to implement one of the methods:
You now get the idea on how to implement all other methods. The beauty of this is that by using this approach, you can support all versions of ActiveSync (provided that the target version does implement the function call you need...).
My first approach to consuming RAPI was quite simple: a static link to rapi.lib. This meant that all the products were tied to a particular version of ActiveSync and would not run in another. By linking to a specific version's lib file we are actually precluding the possibility of linking to other versions, both older or newer.
It so happens that util recently all applciations were statically linked to ActiveSync 3.8, meaning that you had to install this particular version in order for the applications to run. Incidentally, I got a request from two different customers, one asking for ActiveSync 4.0 support, and the other was asking for version 3.6 support. Now what?
Publishing different versions of the applications was obviously out of the question. There had to be a better way of supporting mutiple versions of RAPI. Then I recalled something I read in one of Doug Boling's books: use dynamic linking. The idea is to dynamically load the RAPI.DLL file and retrieve the entry points to the functions you need by using GetProcAddress. To make things easier, I wrote a very simple helper class, of which I'm highlighting a few snippets.
First, the class declaration:
#pragma once
#include <rapi.h>
class CRemoteAPI
{
public:
CRemoteAPI(void);
~CRemoteAPI(void);
HRESULT InitEx(RAPIINIT* pRapiInit);
HRESULT Uninit();
HRESULT Invoke(LPCWSTR pszDLL,
LPCWSTR pszFUnction,
DWORD cbInput,
BYTE *pInput,
DWORD *pcbOutput,
BYTE **ppOutput,
IRAPIStream **ppIRAPIStream,
DWORD dwReserved);
protected:
HMODULE m_hDll;
};
I'm only including a few methods to illustrate the technique. You should be able to extend your own version with the calls you need.
Let's look at the constructor and destructor:
CRemoteAPI::CRemoteAPI(void)
: m_hDll(NULL)
{
m_hDll = LoadLibrary(_T("RAPI.DLL"));
}
CRemoteAPI::~CRemoteAPI(void)
{
if(m_hDll)
FreeLibrary(m_hDll);
}
As you can see, we dynamically load and unload the RAPI.DLL on the constructor and destructor. You should have no problems in nesting these - you will only be incrementing or decrementing the module load count. Now, let's see how to implement one of the methods:
typedef HRESULT (__stdcall *CERAPIINITEX)(RAPIINIT*);
HRESULT CRemoteAPI::InitEx(RAPIINIT *pRapiInit)
{
HRESULT hr = E_NOTIMPL;
CERAPIINITEX pfnCeRapiInitEx;
if(!m_hDll)
return hr;
pfnCeRapiInitEx =
(CERAPIINITEX)GetProcAddress(m_hDll,
"CeRapiInitEx");
if(pfnCeRapiInitEx)
hr = pfnCeRapiInitEx(pRapiInit);
return hr;
}
You now get the idea on how to implement all other methods. The beauty of this is that by using this approach, you can support all versions of ActiveSync (provided that the target version does implement the function call you need...).
Thursday, September 22, 2005
Data Port products updated
All Data Port products have been updated to tackle two nagging issues: library threading model and a memory leak on the device component. The device component has been corrected to address the GetTableDefinition "leak" (see my previous post) and underwent a major review on memory allocation issues. Memory consumption is now much leaner due to some clever buffer reuse tricks.
I'm still recovering from the shock of realizing that all desktop code was being linked to the sinlge threaded versions of the runtime libraries. At least two threads are used in all products (DPW uses more) so you may now understand why some of my users were getting random memory addressing crashes...
I'm still recovering from the shock of realizing that all desktop code was being linked to the sinlge threaded versions of the runtime libraries. At least two threads are used in all products (DPW uses more) so you may now understand why some of my users were getting random memory addressing crashes...
Wednesday, September 21, 2005
The GetTableDefinition memory leak
It's not really a memory leak - the online documentation of ITableCreation::GetTableDefinition only talks about releasing memory pointed to by one of the parameters. The bottom line is that you need to release all the memory that the method allocates for you and returns in the pointer parameters. The problem is that it's not simple.
This explains why RemSqlCe.dll leaks memory (arrgggh!). Now, I have to roll up my sleeves and produce new releases of all my products...
This explains why RemSqlCe.dll leaks memory (arrgggh!). Now, I have to roll up my sleeves and produce new releases of all my products...
Wednesday, September 14, 2005
New C# sample for Data Port Component
The old CsDataPort sample has been updated to allow for the selection of both desktop and device databases. On the desktop side, the user can specify either an Access database (with file browse) or a SQL Server database (without browsing...). The SQL CE database is fully specified (file name, password, encryption and the temporary file directory on the device). You can find the sample on the latest distribution zip file (see here).
P.S.: The new version is now 1.0.30.
P.S.: The new version is now 1.0.30.
Monday, September 12, 2005
Data Port Component updated to 1.1.29
I have updated Data Port Component to version 1.1.29. There is a new VB .NET sample, the VB6 sample has been corrected and updated and the engine now supports pre-port and post-port custom SQL command execution.
Monday, September 05, 2005
Update CCommand, please!
One of the problems I find with the ATL OLE DB Consumer Templates is that you have to know what you are going to get before you ask for it. Wait - this is normal, right? Looks normal but when you are handling generic SQL commands, you only know what you are going to get after asking for it.
There are two different kinds of SQL commands: the ones that return something and the ones that return nothing. The first class of commands are the classical SELECT commands - they always return something, even if it's only a single row count. On the other hand INSERT, UPDATE and DELETE commands (as well as all the DDL commands) do not return rows for you to process.
Making this distinction is very important when you use CCommand: what accessor and rowset types will you use? All non-SELECT commands typically use a CDynamicAccessor and a CRowset where all others may get away with CNoAccessor and CNoRowset. This means that you need to know what kind of SQL command to execute before instantiating the proper template parameters in CCommand. This is a nuisance when running generic SQL commands. Why?
Let's look at the code in CCommand::Open. Somewhere near the bottom of the method, you can see (ATL 3):
This is bad. By default, bBind is true (default function parameter) but the HasOutputColumns function is defined by the accessor class you provide as a template parameter for CCommand. Gotcha - you need to now if the command returns rows before instantiating CCommand. So how do you go around this? How do you run generic SQL commands without having errors thrown at you, no matter what accessor you use?
Easy. Just change the last code snippet so it reads:
Now all commands will run without having assertions thrown at your face. To see if you actually have an accessor and a rowset, just test GetInterface(). If it's NULL then your command returned nothing.
There are two different kinds of SQL commands: the ones that return something and the ones that return nothing. The first class of commands are the classical SELECT commands - they always return something, even if it's only a single row count. On the other hand INSERT, UPDATE and DELETE commands (as well as all the DDL commands) do not return rows for you to process.
Making this distinction is very important when you use CCommand: what accessor and rowset types will you use? All non-SELECT commands typically use a CDynamicAccessor and a CRowset where all others may get away with CNoAccessor and CNoRowset. This means that you need to know what kind of SQL command to execute before instantiating the proper template parameters in CCommand. This is a nuisance when running generic SQL commands. Why?
Let's look at the code in CCommand::Open. Somewhere near the bottom of the method, you can see (ATL 3):
if( bBind &
_OutputColumnsClass::HasOutputColumns())
return Bind();
else
return hr;
This is bad. By default, bBind is true (default function parameter) but the HasOutputColumns function is defined by the accessor class you provide as a template parameter for CCommand. Gotcha - you need to now if the command returns rows before instantiating CCommand. So how do you go around this? How do you run generic SQL commands without having errors thrown at you, no matter what accessor you use?
Easy. Just change the last code snippet so it reads:
if (bBind && GetInterface() != NULL)
return Bind();
else
return hr;
Now all commands will run without having assertions thrown at your face. To see if you actually have an accessor and a rowset, just test GetInterface(). If it's NULL then your command returned nothing.
Thursday, September 01, 2005
Why? Because I Can!
The stunt both me and César pulled off on August 30th caused some interest in the community. Hits on my site peaked as I never thought would be possible.
One guy, though, kept it cool. In his note on Pocket PC Thoughts, Ed Hansberry said:
I am sure someone will fine a practical application for this, but for now, it falls under the W?BIC category.
Actually we started this stunt with a challenge from César. Why don't we try and connect?
Ed has a good point. The practical implications of this are still a bit limited by the technology. On one hand, the client must know the IP address of the server PDA. This will change very often due to the connection methods available. Not everybody will be able to have a fixed IP address to use on the PDA. So one may wonder if the a half-a-world-away scenario is feasible, but on a WiFi scenario this may actually happen.
On the other hand, while the server is actually busy serving data, the PDA user cannot edit his data. This is obviously a limitation of the current SQL CE 2.0 engine that will be lifted with SQL Mobile. When it becomes mainstream, one may see real-world applications of this technology, where the backoffice accesses the mobile database to update it while the user continues to work.
So that's right, Ed: Why did we do it? Because we could, and was a lot of fun! ;)
One guy, though, kept it cool. In his note on Pocket PC Thoughts, Ed Hansberry said:
I am sure someone will fine a practical application for this, but for now, it falls under the W?BIC category.
Actually we started this stunt with a challenge from César. Why don't we try and connect?
Ed has a good point. The practical implications of this are still a bit limited by the technology. On one hand, the client must know the IP address of the server PDA. This will change very often due to the connection methods available. Not everybody will be able to have a fixed IP address to use on the PDA. So one may wonder if the a half-a-world-away scenario is feasible, but on a WiFi scenario this may actually happen.
On the other hand, while the server is actually busy serving data, the PDA user cannot edit his data. This is obviously a limitation of the current SQL CE 2.0 engine that will be lifted with SQL Mobile. When it becomes mainstream, one may see real-world applications of this technology, where the backoffice accesses the mobile database to update it while the user continues to work.
So that's right, Ed: Why did we do it? Because we could, and was a lot of fun! ;)
Subscribe to:
Posts (Atom)