|
(originally posted on July 17, 2009)
I thought that this may be useful to somebody…
My Visual Studio 2005 was getting slower and slower. Today it was freezing for 1-2 minutes every time I saved. So I searched the internet and eventually ran FileMon.
It turns out that each time I save, the IDE queries WebsiteCache folder in C:\Documents and Settings\iguigova\Local Settings\Application Data\Microsoft\WebsiteCache.
Then I spent at least an hour to delete its contents of about 80 000 folders being accumulated since December (I even took a snapshot of it and had to use the DOS Prompt to get rid of all the files).
The issue has been raised with Microsoft: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=347228[^]
Because I could not find a setting on the IDE that controls this behavior, I created a batch file to run every night to clean WebsiteCache:
rmdir /s /q "C:\Documents and Settings\<your username>\Local Settings\Application Data\Microsoft\WebsiteCache"
mkdir "C:\Documents and Settings\<your username>\Local Settings\Application Data\Microsoft\WebsiteCache"
I also cleaned some temp files and the RecycleBin and everything runs smoother now.
|
|
|
|
|
MSSQL Server 2005 provides native support for the XML data type, and new methods to directly parse and read the data.
The following two articles discuss the MSSQL Server 2005 XML capabilities:
http://www.setfocus.com/TechnicalArticles/Articles/sql-server-2005-xml.aspx[^]http://www.15seconds.com/Issue/050803.htm[^]
And here is a basic example:
DECLARE @XMLText XML
SET @XMLText = '
<Customers>
<Customer>
<FirstName>Kevin</FirstName>
</Customer>
<Customer>
<FirstName>Steve</FirstName>
</Customer>
</Customers>'
SELECT @XMLText.query('/Customers/Customer/FirstName')
Now let's assume that we have a table (#MAP ) that contains a xml mapping to another table (#NODE ). In order to validate the mappings, we need to check if a node referenced in a xml map exists in the #NODE table.
A solution is to use a cursor.
And here is a basic example of a cursor:
DECLARE @tablename sysname
DECLARE tables_cursor CURSOR FOR
SELECT name
FROM sys.objects
WHERE type = 'U' AND UPPER(NAME) LIKE UPPER('%%')
OPEN tables_cursor
FETCH NEXT FROM tables_cursor INTO @tablename
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC ('SELECT Top 1 * FROM ' + @tablename)
FETCH NEXT FROM tables_cursor INTO @tablename
END
CLOSE tables_cursor
DEALLOCATE tables_cursor
However, since cursors are the slowest way to access data inside MSSQL Server (http://www.sqlteam.com/article/cursors-an-overview[^]), the following snippet includes both the cursor and the set-based way to scroll through and validate the mappings:
CREATE TABLE #NODE
(
ID INT IDENTITY,
NAME NVARCHAR(MAX)
)
INSERT INTO #NODE VALUES ('TESTNODE')
CREATE TABLE #MAP
(
ID INT IDENTITY,
XML_TEXT NVARCHAR(MAX),
IS_ENABLED BIT
)
INSERT INTO #MAP VALUES ('<MAP><INPUT><NODE><ID>1</ID></NODE></INPUT><OUTPUT>1234</OUTPUT></MAP>', 1)
INSERT INTO #MAP VALUES ('<MAP><INPUT><NODE><ID>11</ID></NODE></INPUT><OUTPUT>1234</OUTPUT></MAP>', 1)
UPDATE #MAP SET IS_ENABLED = 0
FROM #MAP M
LEFT JOIN #NODE N ON N.ID = ISNULL((CAST(M.XML_TEXT AS XML)).value('(//NODE/ID)[1]', 'INT'), 0)
WHERE N.ID IS NULL
DROP TABLE #MAP
DROP TABLE #NODE
|
|
|
|
|
On Update
http://haacked.com/archive/2004/02/28/sql-auto-increment.aspx[^]
On Insert
http://social.msdn.microsoft.com/forums/en-US/transactsql/thread/e021ead3-5dd4-4f2b-a79e-a9258384f313[^]
CREATE TABLE #NODE
(
ID INT IDENTITY,
DOMAIN_ID INT,
NAME VARCHAR(32)
)
INSERT INTO #NODE VALUES (100, 'Bloggers')
INSERT INTO #NODE VALUES (100, 'Vloggers')
INSERT INTO #NODE VALUES (500, 'Joe')
INSERT INTO #NODE VALUES (500, 'Jane')
INSERT INTO #NODE VALUES (500, 'Bob')
CREATE TABLE #NODE_LINKS
(
ID INT IDENTITY,
RANK INT,
LEFT_NODE_ID INT,
RIGHT_NODE_ID INT
)
DECLARE @RANK_OFFSET INT
SELECT @RANK_OFFSET = COUNT(ID) FROM #NODE WHERE DOMAIN_ID = 500
INSERT INTO #NODE_LINKS (RANK, LEFT_NODE_ID, RIGHT_NODE_ID)
SELECT (ROW_NUMBER() OVER (ORDER BY Jobs.ID) - 1) % @RANK_OFFSET, Jobs.ID, Personnel.ID
FROM #NODE Jobs
JOIN #NODE Personnel ON Personnel.DOMAIN_ID = 500
WHERE Jobs.DOMAIN_ID = 100
SELECT ROW_NUMBER() OVER (ORDER BY ID) AS COUNTER, * FROM #NODE_LINKS ORDER BY LEFT_NODE_ID
DROP TABLE #NODE_LINKS
DROP TABLE #NODE
Good Luck!
|
|
|
|
|
Setup:
A data from one module is passed into another so that Module 1 takes a (flat) file as input and feeds an xml structure into Module 2. While Module 2 is internal, Module 1 is designed to handle different customer inputs into the system. However, it is not always possible to generate the expected Module 2 xml structure from the flat file in one step.
|Customer| -> file -> |Module 1| -> xml(products-product-detail) -> |Module 2|
There are at least two ways to accomodate the (multi-step) translation process:
- Add re-parsing logic directly in the code - not smart enough as we do not know what input formats will need to be supported by Module 1 in the future
- Apply customizable xsl transformations - providing decoupled solution with a reliable techology
For example:
Given a list of details, we can group them into product(s) in order to conform to the expected Module 2 hierarchy.
List of details:
='1.0'="iso-8859-1"
<PRODUCTS>
<DETAIL>
<DESC>D1</DESC>
<PRODUCT_NAME>APL</PRODUCT_NAME>
</DETAIL>
<DETAIL>
<DESC>D2</DESC>
<PRODUCT_NAME>ANL</PRODUCT_NAME>
</DETAIL>
<DETAIL>
<DESC>D3</DESC>
<PRODUCT_NAME>APL</PRODUCT_NAME>
</DETAIL>
</PRODUCTS>
List of details grouped by product:
='1.0'="iso-8859-1"
<PRODUCTS>
<PRODUCT>
<PRODUCT_NAME>ANL</PRODUCT_NAME>
<DETAIL>
<DESC>D2</DESC>
<PRODUCT_NAME>ANL</PRODUCT_NAME>
</DETAIL>
</PRODUCT>
<PRODUCT>
<PRODUCT_NAME>APL</PRODUCT_NAME>
<DETAIL>
<DESC>D1</DESC>
<PRODUCT_NAME>APL</PRODUCT_NAME>
</DETAIL>
<DETAIL>
<DESC>D3</DESC>
<PRODUCT_NAME>APL</PRODUCT_NAME>
</DETAIL>
</PRODUCT>
</PRODUCTS>
In order to achieve this grouping, we can use the Muenchian method as described here:
http://www.jenitennison.com/xslt/grouping/muenchian.html[^]
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="iso-8859-1"/>
<xsl:key name="details-by-product" match="DETAIL" use="PRODUCT_NAME" />
<xsl:template match="PRODUCTS">
<PRODUCTS>
<xsl:for-each select="DETAIL[count(. | key('details-by-product', PRODUCT_NAME)[1]) = 1]">
<xsl:sort select="PRODUCT_NAME" />
<PRODUCT>
<PRODUCT_NAME><xsl:value-of select="PRODUCT_NAME"/></PRODUCT_NAME>
<xsl:for-each select="key('details-by-product', PRODUCT_NAME)">
<DETAIL>
<xsl:apply-templates/>
</DETAIL>
</xsl:for-each>
</PRODUCT>
</xsl:for-each>
</PRODUCTS>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See also:
In order to test the transformation, we can initiate the xslt in a script as described here:
http://msdn.microsoft.com/en-us/library/ms762796(VS.85).aspx[^]
var oArgs = WScript.Arguments;
if (oArgs.length == 0)
{
WScript.Echo ("Usage : cscript xslt.js xml xsl");
WScript.Quit();
}
xmlFile = oArgs(0) + ".xml";
xslFile = oArgs(1) + ".xsl";
var xsl = new ActiveXObject("MSXML2.DOMDOCUMENT.6.0");
var xml = new ActiveXObject("MSXML2.DOMDocument.6.0");
xml.validateOnParse = false;
xml.async = false;
xml.load(xmlFile);
if (xml.parseError.errorCode != 0)
WScript.Echo ("XML Parse Error : " + xml.parseError.reason);
xsl.async = false;
xsl.load(xslFile);
if (xsl.parseError.errorCode != 0)
WScript.Echo ("XSL Parse Error : " + xsl.parseError.reason);
try
{
WScript.Echo (xml.transformNode(xsl.documentElement));
}
catch(err)
{
WScript.Echo ("Transformation Error : " + err.number + "*" + err.description);
}
In order to apply the transformation at run-time, we can use the XslCompiledTransform as described here:
http://msdn.microsoft.com/en-us/library/system.xml.xsl.xslcompiledtransform_members.aspx[^]
private string ApplyStylesheet(string xml, string stylesheet)
{
if (!string.IsNullOrEmpty(stylesheet))
{
XmlReaderSettings settingsReader = new XmlReaderSettings();
settingsReader.ValidationType = ValidationType.None;
settingsReader.ProhibitDtd = false;
settingsReader.XmlResolver = null;
StringReader strReader = new StringReader(xml);
XmlReader xmlReader = XmlReader.Create(strReader, settingsReader);
StringWriter stringWriter = new StringWriter();
XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(stylesheet);
xslt.Transform(xmlReader, xmlWriter);
xmlWriter.Close();
xmlReader.Close();
return stringWriter.ToString();
}
return xml;
}
Good luck!
|
|
|
|
|
|
In dealing with durations in MSSQL server, I have found the following three resources helpful:
If you need to report a duration in a certain granularity such as "Task runs for ## minutes ## seconds" , then refer to this[^] post:
SELECT *,
(TOTAL_SS) / 31536000 AS YY,
DATEDIFF(MONTH, 0, END_TIME - START_TIME) % 12 AS MM,
(TOTAL_SS % 31536000) / 604800 AS WW,
(TOTAL_SS % 31536000) / 86400 AS DD,
(TOTAL_SS % 86400) / 3600 AS HH,
(TOTAL_SS % 3600)/60 AS MI,
(TOTAL_SS % 60) AS SS
FROM (
SELECT START_TIME,
END_TIME,
END_TIME - START_TIME AS TOTAL,
DATEDIFF(ss, 0, END_TIME - START_TIME) AS TOTAL_SS
FROM Tasks
WHERE END_TIME >= START_TIME
) AS Q1
If you need to report a duration in a certain format such as "## hours 00 minutes 00 seconds", then you may need to pad the number of minutes and seconds to ensure that, for example, 3 minutes is represented as 03. Padding for integers is explained here[^]:
DECLARE @CH CHAR(1) = '0',
@LEN INT = 2,
@N INT = 1
SELECT CASE WHEN @LEN > LEN(@N) THEN REPLICATE(@CH, @LEN - LEN(@N)) ELSE '' END + CAST(@N AS VARCHAR) AS PADDED_NUMBER
If you need to extract the date part of a DATETIME timestamp, you can use the fact that internally dates are treated as FLOAT as described in more detail here[^]:
SELECT CAST (FLOOR(CAST (GETDATE() AS FLOAT)) AS DATETIME) AS DATE_ONLY
|
|
|
|
|
Excellent article, exactly what I need ! Greetings from Varna!
|
|
|
|
|
Hello and thank you for the message! Varna rocks!
|
|
|
|
|
(Originally posted: 2007-09-26)
The __arglist keyword can be very useful. It returns an argument list handle for the current method. By calling __arglist a pointer to the argument list is pushed on the stack.
Application:
In the new OLM Inventory application, the backend interactions are abstracted to a few methods:
. public static void BuildQuery(out string sql_cmd, out OleDbParameter[] sql_params, int mode, string tbl, string input, __arglist)
. public static int ExecNonQuery(string sql_cmd, OleDbParameter[] sql_params)
. public static List<Object> ExecQuery(Type rcrd_type, string sql_cmd, OleDbParameter[] sql_params)
The BuildQuery method has to be flexible enough to work with unknown number of arguments representing the query parameters. Thus we use the __arglist keyword as shown in this excerpt:
public static void BuildQuery(out string sql_cmd, out OleDbParameter[] sql_params, int mode, string tbl, string input, __arglist)
{
sql_params = null;
sql_cmd = String.Empty;
try
{
string[] rcrd_params = input.Split(new Char[] { ',' });
ArgIterator param_iter = new ArgIterator(__arglist);
int param_count = param_iter.GetRemainingCount();
if (param_count != rcrd_params.Length)
{
ErrorLogDataSource.LogError(String.Format(
"Failed to build query with {1}/{2} parameters",
param_count, rcrd_params.Length));
return;
}
sql_params = new OleDbParameter[param_count];
sql_cmd = String.Empty;
for (int i = 0; i < param_count; i++)
{
Object param = TypedReference.ToObject(param_iter.GetNextArg());
sql_params[i] = new OleDbParameter(rcrd_params[i], param);
}
} catch (Exception ex)
{
sql_cmd = String.Empty;
sql_params = null;
ErrorLogDataSource.LogException(new Exception(
String.Format("Failed to build query for {1} with {2} in {3} mode - {0}",
ex.Message, tbl, input, mode),
ex));
}
}
Here is a sample call to BuildQuery:
public static List<Object> GetStatusTypes(int ID, string Name, string Description, bool Reload, int UserID)
{
if ((!isCached) || (Reload))
{
DataSourceAdapter.BuildQuery(out sql_cmd, out sql_params, 3, tbl_name, "Name,Description,ID,ID", __arglist("%", "%", -1, -1));
cache = DataSourceAdapter.ExecQuery(typeof(StatusType), sql_cmd, sql_params);
isCached = true;
listFilter = null;
}
}
Reference:
. Hidden Keywords[^]
. Variable argument list
|
|
|
|
|
(Originally posted: 2007-10-24)
Services running on your machine are defined in the registry at:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
You can manually edit the registry to change service settings or you can use the SC windows command on the command prompt.
DESCRIPTION:
SC is a command line program used for communicating with the
NT Service Controller and services.
USAGE:
sc <server> [command] [service name] <option1> <option2>...
The option <server> has the form "\\ServerName"
Further help on commands can be obtained by typing: "sc [command]"
Commands:
query-----------Queries the status for a service, or
enumerates the status for types of services.
queryex---------Queries the extended status for a service, or
enumerates the status for types of services.
start-----------Starts a service.
pause-----------Sends a PAUSE control request to a service.
interrogate-----Sends an INTERROGATE control request to a service.
continue--------Sends a CONTINUE control request to a service.
stop------------Sends a STOP request to a service.
config----------Changes the configuration of a service (persistant).
description-----Changes the description of a service.
failure---------Changes the actions taken by a service upon failure.
qc--------------Queries the configuration information for a service.
qdescription----Queries the description for a service.
qfailure--------Queries the actions taken by a service upon failure.
delete----------Deletes a service (from the registry).
create----------Creates a service. (adds it to the registry).
control---------Sends a control to a service.
sdshow----------Displays a service's security descriptor.
sdset-----------Sets a service's security descriptor.
GetDisplayName--Gets the DisplayName for a service.
GetKeyName------Gets the ServiceKeyName for a service.
EnumDepend------Enumerates Service Dependencies.
The following commands do not require a service name:
sc <server> <command> <option>
boot------------(ok | bad) Indicates whether the last boot should
be saved as the last-known-good boot configuration
Lock------------Locks the Service Database
QueryLock-------Queries the LockStatus for the SCManager Database
EXAMPLE:
sc start MyService
|
|
|
|
|
(Originally posted: 2008-02-15)
qwinsta
Display information about Terminal Sessions.
QUERY SESSION [sessionname | username | sessionid]
[/SERVER:servername] [/MODE] [/FLOW] [/CONNECT] [/COUNTER]
sessionname Identifies the session named sessionname.
username Identifies the session with user username.
sessionid Identifies the session with ID sessionid.
/SERVER:servername The server to be queried (default is current).
/MODE Display current line settings.
/FLOW Display current flow control settings.
/CONNECT Display current connect settings.
/COUNTER Display current Terminal Services counters information.
rwinsta
Reset the session subsytem hardware and software to known initial values.
RESET SESSION {sessionname | sessionid} [/SERVER:servername] [/V]
sessionname Identifies the session with name sessionname.
sessionid Identifies the session with ID sessionid.
/SERVER:servername The server containing the session (default is current).
/V Display additional information.
. Managing Terminal Services Sessions Remotely[^]
|
|
|
|
|
(Originally posted: 2007-10-24)
Tip: When you disconnect the adaptor (or most serial port devices like a modem bluetooth IrDA device) the COM port remains allocated but does not show up in Device Manager, however you can re-use that COM port by manually selecting it if you know that it is really free. As far as I know the only way to completely free that COM port so that it is not reported as “In Use” is to plug the device in so that it shows up in device manager, then right click it and choose uninstall (or to go into the registry and manually remove the assignment).
Tools:
. Free Serial Port Monitor[^]
Reference:
. USB to serial adapter review[^]
. A diagnostic procedure[^]
|
|
|
|
|
(Originally posted: 2007-08-24)
A Solution
<style type='text/css'>
.TableContainer div {
width:900px;
padding-bottom: 20px;
overflow-x:auto;
overflow-y:hidden;
}
.locked {
position: relative;
}
</style>
This solution provides only a horizontal scroll bar. In IE, it also freezes the first column, so that the Edit buttons are always visible. The behaviour in Firfox differs.
Note: A bug in IE6.0 places Dropdown lists on top of all other elements independent of their z-index style property. There are few available hacks (using IFrame) to address the problem but they apply better in dealing with page menus rather than controls in the same div element. For more details:
. CSS Positioning[^]
. IE6, SELECT and Z-index Problem Resolution[^]
. Select Element Z-index ignored by IE6 - Workaround[^]
Horizontal And Vertical Scrolling
Ideally, the header, the footer and the first column of the grid view control can be frozen. One clever solution is to apply scrolling at two different levels - the div element containing the table and the tbody element of the table.
<style type='text/css'>
.TableContainer div{
width:800px;
padding-bottom: 20px;
overflow-x:auto;
overflow-y:hidden;
}
.locked {
position: relative;
}
.TableContainer tbody{
height:300px;
overflow-x:hidden;
overflow-y:auto;
padding-right: 20px;
}
.TableContainer tbody tr {
height: auto;
white-space: nowrap;
}
</style>
When we scroll the tbody vertically, the header and the footer of the table are not affected by design (fails not in IE6.0).
When we scroll the div element horizontally, the first column is not affected if its style position is set to relative (fails in Firefox).
. CSS position property[^]
. CSS positioning properties[^]
Note: In order to have access to the thead, tbody, and tfooter sections of the grid view table representation, they have to be programatically added after the grid is data-bound. For example, call the following funciton during the grid's OnPreRender event.
private void MakeAccessible(GridView grid)
{
if (grid.Rows.Count > 0)
{
grid.UseAccessibleHeader = true;
grid.HeaderRow.TableSection = TableRowSection.TableHeader;
grid.FooterRow.TableSection = TableRowSection.TableFooter;
if (grid.TopPagerRow != null)
{
grid.TopPagerRow.TableSection = TableRowSection.TableHeader;
}
if (grid.BottomPagerRow != null)
{
grid.BottomPagerRow.TableSection = TableRowSection.TableFooter;
}
}
}
. Make the GridView control accessible[^]
. Avoid out of order Pager rows: The table must contain row sections in order of header, body, then footer[^]
Explicit Position Calculation
The following javascript functions help setup a listener for the scrolling event and recalculate the positions of the elements on the fly. This approach allows to explicitely set the position style values and avoids the pitfalls of css behaviour interpretations by the different browsers. However, it requires more research and testing before the implementation can be successfully completed...
<script type="text/javascript">
function getElementsByClassName(oElm, strTagName, oClassNames){
var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
var arrReturnElements = new Array();
var arrRegExpClassNames = new Array();
if(typeof oClassNames == "object"){
for(var i=0; i<oClassNames.length; i++){
arrRegExpClassNames.push(new RegExp("(^|\s)" + oClassNames[i].replace(/-/g, "\-") + "(\s|$)"));
}
}
else{
arrRegExpClassNames.push(new RegExp("(^|\s)" + oClassNames.replace(/-/g, "\-") + "(\s|$)"));
}
var oElement;
var bMatchesAll;
for(var j=0; j<arrElements.length; j++){
oElement = arrElements[j];
bMatchesAll = true;
for(var k=0; k<arrRegExpClassNames.length; k++){
if(!arrRegExpClassNames[k].test(oElement.className)){
bMatchesAll = false;
break;
}
}
if(bMatchesAll){
arrReturnElements.push(oElement);
}
}
return (arrReturnElements)
}
function OnScroll(){
var cells = getElementsByClassName(document.getElementByID("div_OLMGridView"), "td", "locked");
for (var i = 0; i < cells.length; i++){
cells[i].style.left = cells[i].offsetParent.scrollLeft;
}
}
function GetScroller(){
var scroller = null;
var divs=document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
if (divs[i].parentNode.getAttribute('class') == "TableContainer"){
scroller = divs[i];
break;
}
}
return (scroller);
}
function AddScrollListener(){
var scroller = GetScroller();
if (scroller != null){
if (scroller.addEventListener){
scroller.addEventListener('scroll', OnScroll, false);
} else if (scroller.attachEvent){
scroller.attachEvent('scroll', OnScroll);
}
}
}
window.onload = function(){AddScrollListener();}
</script>
In IE, exression() can be used to dynamically set style properties.
. IE Dynamic Properties[^]
For example,
<DIV STYLE="background-color: #CFCFCF; position: absolute;
left:expression(document.body.clientWidth/2-oDiv.offsetWidth/2);
top:expression(document.body.clientHeight/2-oDiv.offsetHeight/2)"></DIV>
However, its use is not recommended:
. CSS expressions in browsers other then IE[^]
. Avoid CSS expressions[^]
References
. ScrollingGrid: A cross-browser freeze-header two-way scrolling DataGrid by Ashley van Gerven[^]
. An ASP.NET DataGrid Custom Control to Freeze Header, Rows, Columns by Tittle Joseph[^]
. Real World GridView: Excel-like Frozen Headers for ASP.NET 2.0[^]
. Lock or Freeze Table Columns plus Non-Scroll Headers - Internet Explorer CSS solution[^]
. Freeze table header in Mozilla[^]
. Implementing a Fixed GridView Header in ASP.NET[^]
|
|
|
|
|
(Originally posted: 2007-09-26)
There are two Delphi functions defined in Classes that are employed in order to allow for a class to be instantiated given its class name:
. function GetClass(const ClassName: string): TPersistentClass;
. procedure RegisterClass(AClass: TPersistentClass);
The Class must be registered before GetClass can find it. Form classes and component classes that are referenced in a form declaration (instance variables) are automatically registered when the form is loaded. Other classes can be registered by calling RegisterClass or RegisterClasses.
Details:
Delphi help files
Application:
The UPOS Specification[^] is followed in an attempt to standardize the implementation of Vivonet POS Devices. Thus each POS device is derived from the TUPOSDeviceControl and TUPOSDeviceService family. A generic interface is provided to create the POS device instances (such as MSR and PinPad) based on their names (supplimented by an ini configuration file that provides the class names for the device in question):
unit uUPOSFactory;
interface
uses
uUPOSDeviceControl,
uUPOSDeviceService;
const
POSDEVICESINIFILE = 'C:\Program Files\Vivonet\iPOS\Client\PosDevices.ini';
function CreateDeviceService(DeviceName: string): TUPOSDeviceService;
function CreateDeviceControl(DeviceName: string): TUPOSDeviceControl;
implementation
uses
IniFiles,
Classes;
function GetClassName(DeviceName: string; DeviceObject: string):string;
var
iniFile: TIniFile;
begin
iniFile := TIniFile.Create(POSDEVICESINIFILE);
Result := iniFile.ReadString(DeviceName, DeviceObject, 'T' + DeviceName + DeviceObject);
iniFile.Free;
end;
function CreateDeviceService(DeviceName: string): TUPOSDeviceService;
var
DeviceClass: TPersistentClass;
begin
try
DeviceClass := GetClass(GetClassName(DeviceName, 'Service'));
Result := TUPOSDeviceService(DeviceClass.Create);
except
Result := nil;
end;
end;
function CreateDeviceControl(DeviceName: string): TUPOSDeviceControl;
var
DeviceClass: TPersistentClass;
begin
try
DeviceClass := GetClass(GetClassName(DeviceName, 'Control'));
Result := TUPOSDeviceControl(DeviceClass.Create);
except
Result := nil;
end;
end;
end.
Inspiration:
Factory Method design pattern
See Also:
Abstract Factory and Builder design patterns
|
|
|
|
|
(Originally posted: 2007-10-03)
. Set Project -> Properties -> Configuration Properties -> Linker -> Debugging -> Generate Debug Info
. Set Tools -> Options -> Debugging -> Native -> Enable RPC Debugging
. Ensure that the user account under which the application will run has enough privileges (e.g., it is part of Debugger Users). Alternatively, set the Component application identity as follows: <Component> Properties -> Identity -> System Account -> Interactive User
References:
. How to: Debug COM+ 1.0 Components[^]
. Debugging an W32 App[^]
|
|
|
|
|
|
(Originally posted: 2008-02-01)
These are the steps for setting up COM+ debugging in Windows 2000. Note that there are known issues with this and it may not work correctly, even if you follow all the steps. It is even more unlikely that it will work correctly under Windows XP.
Setting up COM+
In AdmistrativeTools -> Component Services :
. Ensure that the COM dlls registered in Component Services point to the dlls that the Delphi project will create when it compiles. Usually, this means deleting the original COM applications in Component Services, and readding the DLLs that are generated by the Delphi project compilation.
. Right click on the COM Application you are debugging (not the COM object itself), and go to Properties->Identify and ensure that 'Interactive User - the current logged on user' is selected. If you don't see the Identity tab, you may be trying to access the properties of the COM object instead of the COM application it resides in.
. Get the Application identifier for the COM object itself: Right click on the COM object and go to Properties. Under CLSID is the Application ID (a GUID). Copy this as it will be used later when setting up the Delphi Project.
Setting up the Delphi Project
In the Delphi Project you wish to debug:
. Go to Project->Options
Under the 'Linker' tab, ensure that the options 'Include TD32 debug info' and 'Include remote debug symbols' are checked.
Click OK.
. Go to Run->Parameters .
Under 'Host Application' find 'dllhost.exe' in the WINNT\system32 folder.
Under 'Parameters', paste the Application identifier you got from earlier (see Setting up COM+). Entering in '/ProcessID:{GUID}', where GUID is the Application identifier, may work better.
Click OK.
The application should now be set up to debug. Set the breakpoints you want the debugger to stop at, and click Run. If all goes well, the project will be shown in run mode and will wait for the break point to hit.
Troubleshooting
If your break points are not being hit:
. It is recommended that dllhost.exe is copied to the directory where the COM+ dll file is and it is referred to in Delphi 'Host Application' step above. This is a required step on a Windows XP machine.
. You may need to set the Include remote debug symbols option under Project | Options | Linker. Make sure to shutdown the COM+ application that contains the DLL first. This can be done fom the Component Services Admin utility.
. Change the COM+ application settings to Activation -> Library and use Shift+F7 to step into the com+ code
. The pathfilename of the dll containing the COM is not in 8.3 format. There are 2 solutions:
1: Put de DLL in a directory in 8.3 format like C:\Test\MyCom.dll and NOT C:\My Test\My Com.dll
2: Put your DLL anywhere, register it, search the registration key with RegEdit and change the pathfilename in the registry by the long pathfilename format like C:\My Test\My Com.dll
Resources
. CodeGear from Borland[^]
. Debugging COM+ services written in Delphi 7 (D7)[^]
. Why can't I get breakpoints to work when debugging a COM+ or MTS app in Delphi?[^]
. On Windows XP, it is very difficult to set breakpoints in a COM, Delphi do not stops[^]
|
|
|
|
|
(Originally posted: 2007-10-11)
Check the EventViewer Application log:
. Go to Start -> Control Panel -> Administrative Tools -> Event Viewer
. Look for Error with Source: COM+ and Event ID: 4786.
|
|
|
|
|
(originally posted: 2007-10-12)
There are many representations of text in C++. Usage and conversion should be done with great care...
. (Visual C++) How to: Convert Between Various String Types[^]
This article only touches on the issue of CURRENCY data type.
CURRENCY is implemented as an 8-byte two's-complement integer value scaled by 10,000. This gives a fixed-point number with 15 digits to the left of the decimal point and 4 digits to the right. The CURRENCY data type is very useful for calculations involving money, or for any fixed-point calculations where accuracy is important.
Here is the code used in Mercury.cpp of VivoClientApi.dll that converts CURRENCY to text and vice versa for the purposes of the Mercury project (text is used loosely )
CURRENCY2Text:
CURRENCY Currency_Amount;
CString Currency_Text = "";
Currency_Text.Format("%.2f", (float)(Currency_Amount.int64) / 10000);
Text2CURRENCY (reading from an XML doc):
CURRENCY Currency_Amount;
COleCurrency COleCurr_Amount;
MSXML::IXMLDOMNodePtr pNode;
pNode = pDOMDoc->selectSingleNode("//Amount/Authorize");
if(pNode)
{
COleCurr_Amount.ParseCurrency(W2T(pNode->Gettext()));
Currency_Amount = COleCurr_Amount.m_cur;
}
See Also:
. MFC Simple Data Types[^]
. MFC COleCurrency[^]
. ATL CComCurrency[^]
. Component Automation: CURRENCY[^]
|
|
|
|
|
(Originally posted: 2007-09-20)
The call to Application.ProcessMessages requires the inclusion of Forms unit.
If the size of the dll is of a concern, consider replacing the call with:
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
For more details: http://www.delphipages.com/forum/showthread.php?t=181620[^]
Here is the Application.ProcessMessage() extracted from the Forms.pas:
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;
Note: This function requires Windows.pas and Messages.pas.
|
|
|
|
|