|
Reply from "blueturtle" :
Hi,
Thanks for your help.
The information about VB's implementation using the TLS was important
for me.
I had to verify in which cases I can avoid the marshalling (none left
in my case...).
Regarding your question:
The information is not actually passed between the threads.
In the case where I had to use the marshalling, it is a kind of an
application server
which accept requests from web browsers, and each request is executed
in a new thread ( to minimize amount of running threads )
So when a session starts, the web browser can ask to load object X,
in return I send back an identifier that represents the marshalled
object.
In the next request, a new thread will start to execute it, unmarshal,
IDispatch::Invoke, marshal, etc.
What I like about it, is that it should work with any COM object (
there is no set of predefined of objects ),
so it is very interesting to make it work with all the scenarios,
parameter types, etc.
What I dont like about it, is that it should work with _every_ COM
object...
So I have to solve many internal problems of customers,
E.g: TLS (yes again, but nothing to do with marshalling this time )
I encountered a COM object that was using TlsAlloc (inside ACE
library),
and that collided (a nice random crash) with some other code which used
TlsAlloc in that thread,
because the ACE library was compiled with the wrong value for maximum
indices for that OS.
( from MSDN:
Windows 2000/XP: There is a limit of 1088 TLS indexes per process.
Windows 98/Me: There is a limit of 80 TLS indexes per process. )
Thanks again for your help,
Dave.
|
|
|
|
|
It's now 2:15 pm 31st Dec 2004 in Singapore.
Happy New Year to all CodeProject Members !
- Bio.
|
|
|
|
|
A coclass is COM's way of defining a class (class in the object-oriented sense) which is meant to be language independent.
An IDL file is what COM provides that allows developers to define such language independent object-oriented classes.
An IDL file is compiled by the MIDL compiler into a Type Library (.TLB file) which is a binary form of an IDL file meant to be processed by various language compilers (e.g. VB, VC++, Delphi, etc).
The end result of such .TLB processing is that the specific language compiler produces the language-specific constructs (VB classes for VB, C++ classes, various structs, macros and typedefs for VC++) that represent the coclass defined in the .TLB (and ultimately that which was defined in the originating .IDL).
When you look at an example coclass definition in an IDL :
coclass MyObject
{
[default] interface IMyObject;
[default, source] dispinterface _IMyObjectEvents;
};
it is declaring a COM class named MyObject which must implement an interface named IMyObject and which supports (not implement) the event interface _IMyObjectEvents.
This is conceptually equivalent to defining a C++ class like this :
class CSomeObject : public ISomeInterface
{
...
...
...
};
where ISomeInterface is a C++ virtual class.
It is up to the individual language compiler to produce whatever code (in the specific compiler's language) is necessary for its user to implement and ultimately produce a binary which can be deemed by COM to be of coclass MyObject.
Now, in languages like C++, we can use CoCreateInstance() in which we can specify the CLSID of the coclass and select the interface from that coclass that we want to use to interact with that coclass. Calling CoCreateInstance() like this :
STDAPI CoCreateInstance(
CLSID_MyObject,
NULL,
CLSCTX_INPROC_SERVER,
IID_IMyObject,
(void**)&m_pIMyObject
);
is conceptually equivalent to :
ISomeInterface* pISomeInterface = NULL;
pISomeInterface = new CSomeObject();
In the first case, we are saying to the COM sub-system that we want to obtain a pointer to an object that implements the IMyObject interface and we want coclass CLSID_MyObject's particular implementation of this interface.
In the second case, we are saying that we want to create an instance of a C++ class that implements the interface ISomeInterface and we are using CSomeObject as that C++ class.
Do you see the equivalence ? A coclass, then, is an object-oriented class in COM's own language.
|
|
|
|
|
Note that if a thread (in a COM Exe Server) is initialized with ::CoInitializeEx(NULL, COINIT_MULTITHREADED) and this thread is used to register a class object :
DWORD WINAPI ThreadFunc(LPVOID lpvParameter)
{
LPDWORD lpdwCookieReceiver = (LPDWORD)lpvParameter;
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
RegisterClassObject_CTestServer02(lpdwCookieReceiver);
::CoUninitialize();
return 0;
}
void RegisterClassObject_CTestServer02(LPDWORD lpdwCookieReceiver)
{
HRESULT hr = S_OK;
CTestServer02_Factory* pCTestServer02_Factory = new CTestServer02_Factory();
if (pCTestServer02_Factory)
{
IUnknown* pIUnknown = NULL;
pCTestServer02_Factory -> QueryInterface(IID_IUnknown, (void**)&pIUnknown);
hr = ::CoRegisterClassObject
(
CLSID_TestServer02,
pIUnknown,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
lpdwCookieReceiver
);
pIUnknown -> Release();
pIUnknown = NULL;
pCTestServer02_Factory -> Release();
pCTestServer02_Factory = NULL;
}
}
This thread need not stay alive for the registered class object. In fact, as you can see, the thread simply exits after performing the registration.
The pointer to the class factory remains alive inside the COM engine somewhere.
However, this will not work if the thread is initialized with ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED).
|
|
|
|
|
1. After some time of researching, I found that a COM Object in a COM EXE Server cannot decide on its own Apartment Model (Apartment, Multithreaded, etc).
2. This is so despite indicating the model you want when creating an ATL COM object inside an EXE Server using the ATL Wizard.
3. The way to indicate the Apartment Model for your COM Object is to place your call to CoRegisterClassObject() on your COM Object Class Factory in a thread that has been initialized with CoInitializeEx(NULL, COINIT_MULTITHREADED) or CoInitializeEx(NULL, COINIT_APARTMENTTHREADED).
|
|
|
|
|
In his book, "Essential COM", Don Box wrote :
From a programming perspective, apartment membership is an interface pointer attribute, not an object attribute. When an interface pointer is returned from a COM API call or from a method invokation, the thread that invoked the API call or method determines which apartment the resultant interface pointer belongs to.
If the call returns a pointer to the actual object, then the object itself resides in the calling thread's apartment. Often the object cannot reside in the caller's apartment, ether because the object already exists in a different process or host machine or because the concurrency requirements of the object are incompatible with the client's apartment. In these cases, the client receives a pointer to a proxy.
|
|
|
|
|
In his book, "Essential COM", Don Box wrote :
In COM, a proxy is an object that is semantically identical to an object in another apartment.
|
|
|
|
|
The DispatchMessage function dispatches a message to a window procedure. It is typically used to dispatch a message retrieved by the GetMessage function.
|
|
|
|
|
The PostThreadMessage function posts a message to the message queue of the specified thread.
The thread to which the message is posted must have created a message queue, or else the call to PostThreadMessage fails. Use one of the following methods to handle this situation:
* Call PostThreadMessage. If it fails, call the Sleep function and call PostThreadMessage again. Repeat until PostThreadMessage succeeds.
* Create an event object, then create the thread. Use the WaitForSingleObject function to wait for the event to be set to the signaled state before calling PostThreadMessage. In the thread to which the message will be posted, call PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE) to force the system to create the message queue. Set the event, to indicate that the thread is ready to receive posted messages.
The thread to which the message is posted retrieves the message by calling the GetMessage or PeekMessage function. The hwnd member of the returned MSG structure is NULL.
Messages sent by PostThreadMessage are not associated with a window. Messages that are not associated with a window cannot be dispatched by the DispatchMessage function. Therefore, if the recipient thread is in a modal loop (as used by MessageBox or DialogBox), the messages will be lost. To intercept thread messages while in a modal loop, use a thread-specific hook.
|
|
|
|
|
In COM, event handling is achieved via Connection Points. The COM object which fires events is a Connection Point Container. It recognizes several Connection Points.
Hence,
COM Object That Fires Events
- implements IConnectionPointContainer.
- contains one or more IConnectionPoint implementations.
- calls event interface methods implemented by event clients.
COM Client that listens to events
- implements Event Interface methods.
- searches for a suitable connection point in target COM object (that fires events) via IConnectionPointContainer.FindConnectionPoint().
- connects with event firing object via IConnectionPoint.Advise().
- disconnects with event firing obejct via IConnectionPoint.Unadvise().
|
|
|
|
|
I try your TEventHandler template in the activex component that I design. I hope to use this to receive the events of flash activex I create in my activex component.
But I came across some compile errors:
--------------------Configuration: SmartFlash - Win32 Debug--------------------
Compiling...
StdAfx.cpp
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(25) : error C2872: 'DISPPARAMS' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(27) : error C2872: 'EXCEPINFO' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(110) : error C2872: 'DISPPARAMS' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(111) : error C2872: 'EXCEPINFO' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
Error executing cl.exe.
SmartFlash.ocx - 4 error(s), 0 warning(s)
It's about the following snippet
typedef HRESULT (event_handler_class::*parent_on_invoke)
(
TEventHandler<event_handler_class, device_interface,="" device_event_interface="">* pthis,
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pdispparams,
VARIANT* pvarResult,
EXCEPINFO* pexcepinfo,
UINT* puArgErr
);
I don't have any hints to solve this problem, can you give me some suggestion?
|
|
|
|
|
For those of you who have tried to develop ASP.NET Web Applications using Visual Studio .NET under the Windows 2000 platform and faced a message indicating that ASP.NET is not installed on your Web Server (i.e. IIS), you need to perform the following :
1. Install ASP.NET on your system.
1.1. To do this, start up the Visual Studio .NET 2003 Command Prompt and type the following :
aspnet_regiis /i
1.2 You will see the following line output on your console window :
Start installing ASP.NET (1.1.4322.0).
After a pause of some time, you will see the following line output :
Finished installing ASP.NET (1.1.4322.0).
2. Of course, in order to be able to install ASP.NET, you must first have Microsoft .NET Framework installed on your target web server system.
2.1 If you are unsure whether ASP.NET has actually been installed on your system even after installing the .NET framework, go to the ASP.NET home page to download the Microsoft .NET Framework Version 1.1 Redistribution Package :
http://asp.net/Default.aspx?tabindex=0&tabid=1
|
|
|
|
|
Just like to share with all a note about one instance where the Type Library GUID and its version number can play a very significant role.
1. Background
----------------------
1.1 I was developing a wrapper ATL COM Server for a COM Server written in Visual Basic.
1.2 The original COM Server written in Visual Basic has a set of events that it fires on certain occassions.
1.3 What my wrapper COM Server needed to do was to receive these events from the VB COM Server and then perform some work before firing the same event up to my client.
1.4 Firing events to my client was the simple part. The part that presented some bumps along the road was that of receiving events from my VB COM Server.
2. Some Info on implementing Event Listening
--------------------------------------------------------------------
2.1To receive events from an external COM Server inside your own ATL COM Server, you need to derive your CComObjectRootEx object from the IDispEventImpl class : e.g. :
class ATL_NO_VTABLE CIrisACUWrapper :
public CComObjectRootEx<CComSingleThreadModel>,
...
...
...
public IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0> // <--- publicly derive from IDispEventImpl.
2.2 You also need to declare an Event Sink Map in your ATL COM Server class : e.g. :
BEGIN_SINK_MAP(CIrisACUWrapper)
SINK_ENTRY_EX(1, __uuidof(__ClassIrisACU), 1, OnCaptureComplete)
END_SINK_MAP()
2.3 For more info on Event Sinks, refer to MSDN documentation for topics :
IDispEventImpl
and
Supporting IDispEventImpl
2.4 You also need to connect to the COM Server that fires the events you are interested in receiving. This is related to Connection Points. A sample implementation is presented below :
long SetupWrappedObjectEventConnections()
{
IUnknown* pIUnknown_WrappedObject = NULL;
long lRet = 0;
m_spICaptureDevice -> QueryInterface (IID_IUnknown, (void**)&pIUnknown_WrappedObject);
if (pIUnknown_WrappedObject)
{
IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0>::DispEventAdvise(pIUnknown_WrappedObject);
pIUnknown_WrappedObject -> Release();
pIUnknown_WrappedObject = NULL;
}
return lRet;
}
2.5 At the end of your application, you will also want to tell your COM Server to stop sending you events. A sample implementation is presented below :
long ShutdownWrappedObjectEventConnections()
{
IUnknown* pIUnknown_WrappedObject = NULL;
long lRet = 0;
m_spICaptureDevice -> QueryInterface (IID_IUnknown, (void**)&pIUnknown_WrappedObject);
if (pIUnknown_WrappedObject)
{
IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0>::DispEventUnadvise(pIUnknown_WrappedObject);
pIUnknown_WrappedObject -> Release();
pIUnknown_WrappedObject = NULL;
}
return lRet;
}
|
|
|
|
|
3. The Importance of the (event firing) COM Server's Type Lib GUID and its Version Number
-----------------------------------------------------------------------------------------------------------------------------------------
3.1 Note the way that you should declare the derivation of your CComObjectRootEx object from the IDispEventImpl class :
class ATL_NO_VTABLE CIrisACUWrapper :
public CComObjectRootEx<ccomsinglethreadmodel>,
...
...
...
public IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0> // <--- publicly derive from IDispEventImpl.
3.2 The 4th parameter, the Type Library GUID, is the GUID of the Type Library of the COM Server that fires the set of events (for which we, as its client, wants to receive).
3.3 The 5th and 6th parameters, i.e. the last 2 numbers, i.e. 4 and 0, are very important too.
3.4 They form the version number of the Type Library whose GUID is specified.
3.5 You can find out the Type Library GUID and the version number from the "library" statement in the IDL file of the COM Server :
e.g. :
//
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: IrisACU.dll
[
uuid(EF2DFFCB-D5CA-4AE0-9A53-1C4E04EC3F80), // <-- This is the GUID of the type library.
version(4.0) // <-- this is the version we want to know.
]
library IrisACU
{
...
}
3.6 Or you can find out this info from the Registry :
Lookup the HKEY_CLASSES_ROOT\TypeLib\ key and search for the sub key that is named by the GUID of the Type Lib : e.g. :
HKEY_CLASSES_ROOT\TypeLib\{EF2DFFCB-D5CA-4AE0-9A53-1C4E04EC3F80}
Immediately after this key is the version number subkey : e.g. :
HKEY_CLASSES_ROOT\TypeLib\{EF2DFFCB-D5CA-4AE0-9A53-1C4E04EC3F80}\4.0
3.7 The purpose of getting the GUID and version numbers of the type library is simply for the ATL Engine to locate the correct Type Library of the COM Server that fires the events.
3.8 The client ATL COM Server engine (i.e. the side that RECEIVES the event) needs to determine the INDEX (v-table index) and the DESCRIPTION (call type, number of parameters, parameter types, etc) of the EVENT function that is to be invoked (i.e. fired) from the (event firing) COM Server.
3.9 In other words, the ATL Engine needs to know the signature of the event function. This is part of ATL's architecture for sending/receiving events.
3.10 The correct Type Library GUID and Version Info must be given in order for the ATL Engine to load the appropriate Type Lib.
3.11 If this fails, the event will still be fired from the (event firing) COM Server but this will not be received by the event-receiving client.
|
|
|
|
|
The Problem
----------------------
I was just compiling some C++ COM code just now on a machine (not my own).
The same code compiled well on my machine but could not succeed on this other machine.
The compilation error is printed below :
D:\limbl\Develope\SMPT_FrameWorks\Devt\Wrappers\COM\WebServiceServers\FacialRecognitionWebService\FRWebService.cpp(304) : error C2668: 'InlineIsEqualGUID' : ambiguous call to overloaded function
STDMETHODIMP CFRWebService::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IFRWebService
};
for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
if (InlineIsEqualGUID(*arr[i],riid)) // <-- problem is reported here.
return S_OK;
}
return S_FALSE;
}
How to Resolve The Problem
-----------------------------------------------
The MSDN documentation could not help me much and I experimented with some Visual C++ settings. I eventually resolved the problem by resetting the Tools|Options|Directories settings for "Include files".
I found that in the Tools|Options|Directories settings for "Include files", if the Microsoft SDK include folder (e.g. C:\PROGRAM FILES\MICROSOFT SDK\INCLUDE) is set to the top of the list or is above the Visual C++ include folder (e.g. C:\PROGRAM FILES\MICROSOFT VISUAL STUDIO\VC98\INCLUDE), the problem will occur.
I now set the Microsoft SDK include folder to below the Visual C++ include folder. The compilation problem is now resolved.
I currently haven't the time to find out the root cause of the problem. If anyone finds out, please share with all of us.
|
|
|
|
|
Lim Bio Liong wrote:
if (InlineIsEqualGUID(*arr[i],riid)) // <-- problem is reported here.
To resolve problem we can call global InlineIsEqualGUID like this way
(::InlineIsEqualGUID(*arr[i],riid))
this will compile code without problem!
"Opinions are neither right nor wrong. I cannot change your opinion. I can, however, change what influences your opinion." - David Crow
cheers,
Alok Gupta
|
|
|
|
|
Hello Alok,
Thanks for the tip, Alok. I'll give it a try.
Best Regards,
Bio.
|
|
|
|
|
Hello Alok,
I had an occassion to use your solution to this problem today. It worked great
Thanks v much, buddy !
Best Regards,
Bio.
|
|
|
|
|
Lim Bio Liong wrote: Thanks v much, buddy !
Mention not, Sir!
"Opinions are neither right nor wrong. I cannot change your opinion. I can, however, change what influences your opinion." - David Crow
cheers,
Alok Gupta
VC Forum Q&A :- I/ IV
|
|
|
|
|
Some recommended reading on a subject close to our hearts :
Debugging Release Mode Problems
Web link :
http://www.codeproject.com/debug/releasemode.asp
|
|
|
|
|
For every SOAP call to a Web Service method, SOAP and IIS will create the COM Object that implements the method, invoke the appropriate Object Method, and then destroy the COM Object.
|
|
|
|
|
Check out this web site :
http://www.ddevel.com/DrCOM/DrCOMMonitor.asp
This site features a cool COM Spying utility called DrCOM. It looks potentially cool and useful. Try downloading their trial DrCOM software.
|
|
|
|
|
Recently, Wee Fong and I had the pleasure of working with C# and lots of COM Interopt stuff. We also had the chance to delve quite deeply into the area of COM Interface Marshalling. The following are some useful pointers that we learnt from our forays into the internal guts of COM Marshalling :
1. COM objects that are Apartment-Threaded-Model based can only have its interface methods invoked directly from the same thread that created the COM Object. The thread that created the COM Object is known as the owning thread.
2. To use the interface methods of an ATM COM Object in a thread different from the owning thread, i.e. the one in which the object was created, the interface pointer of the object must be marshalled to the target thread.
3. The owning thread that created the COM Object must be also be a User-Interface thread. That is, it must contain a message loop like the following :
MSG msg;
// Dispatch all windows messages in queue.
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage (&msg);
DispatchMessage(&msg);
}
4. This is because when an ATM COM Interface method is invoked from a non-owning thread, COM serializes the method call by inserting a Windows Message into the owning thread. This is the simple way in which interface method calls are serialized.
5. Serialization is COM's way of ensuring that the internal state (properties and other internal data) of a COM Object is not corrupted by multiple threads.
6. Please note that -it is- possible to create and use COM Objects (even ATM based) in a thread that does not have any message loop (case in point : console apps). However, in this case, you will not be able to share COM Interfaces across multiple threads.
7. How do we determine the Thread Model of a COM Object ?
Look up the registry entry of the CLSID of the COM Object, e.g. :
HKEY_CLASSES_ROOT\CLSID\{82124FF1-0610-4607-ACFE-8C16E30AF280}
Here, there will be a "InprocServer32" subkey and here you will find a string value named "ThreadingModel". If the string value is : "Apartment", the COM Object is an Apartment-Threaded Model COM Object. But of course, if you had written the COM Object yourself, you will know what Threading Model you had used.
(continued in part 2)
|
|
|
|
|
8. To marshall an interface pointer from the owning thread to a another thread, use the CoMarshalInterThreadInterfaceInStream() and the CoGetInterfaceAndReleaseStream() APIs.
9. Note that marshalling an interface pointer is just one-half of the game. The other half, which in many ways is more challenging and interesting, involves the marshalling of the PARAMETERS of the interface methods.
10. If you make your interface dual or IDispatch-based, the marshalling of the methods' parameters is performed completely automatically for you curtesy of COM.
11. This is logical because dual and IDispatch interfaces can use only OLE Automation-Compatible Types for parameters (e.g. VARIANTs, BSTRs, etc). COM has already prepared itself to marshall these types of parameters.
12. Non-dual and non-IDispatch based interfaces (aka Custom Interfaces) must provide their own custom marshalling. Proxy and stub DLLs must be created for this purpose.
13. Note also that dual and IDispatch based interfaces also use a proxy/stub DLL to perform parameter marshalling. This proxy/stub DLL is the famous oleaut32.dll (read it as OLE AUTomation 32 DLL).
14. To determine or confirm if your interface uses oleaut32.dll to marshall parameters, look up the registry entry of your Interface, e.g. :
HKEY_CLASSES_ROOT\Interface\{66AD7066-C70F-4136-A108-71DC34F3305C}
Here you will find a "ProxyStubClsid32" subkey. It should have a "Default" string value. The value is the CLSID of the object that implements the marshalling functionality of your interface.
15. Look up the registry entry for this CLSID and you will see the in-proc server DLL of the marshalling object. If your interface is dual or IDispatch based, the CLSID should be :
{00020424-0000-0000-C000-000000000046}
If you look up this CLSID entry in the registry, i.e. :
HKEY_CLASSES_ROOT\CLSID\{00020424-0000-0000-C000-000000000046}
you will find the following information :
15.1 The default string value for this CLSID is "PSOAInterface" (read this as Proxy Stub OLE Automation Interface). PSOAInterface is the "universal marshaler" that creates in-memory COM stub/proxy pairs based on type library information. PSOAInterface is oleaut32's tool that enables VB to do "somewhat native" COM marshaling by mimicking the functionality of "midl /Oicf" on-the-fly. PSOAInterface builds and caches native COM proxies based on type libraries. The downside of PSOAInterface is that it requires interfaces that use it to be OLE-Automation compatible, that is, it must return HRESULTs and its parameters must be OLE-Automation types, since that is what it was built for and that's the set of types that COM type libraries can express.
15.2 There is an "InprocServer32" subkey. In this subkey, you will find that the default string value is "oleaut32.dll".
We hope the above information will be useful to all. There's still quite alot of information to share with all on this same topic. We'll be updating this Knowledge Sharing Article with a Part 2 soon.
|
|
|
|
|
Understand Common Virus Attacks Before They Strike to Better Protect Your Apps
by Jason Fisher
The web address of this article is :
http://msdn.microsoft.com/msdnmag/issues/03/05/VirusHunting/default.aspx
This article presents information on some of the techniques viruses use to launch themselves and get into your system.
|
|
|
|
|