C# Bible - Jeff Ferguson, Brian Patterson, Jason Beres
.pdf// Create a handler for the event private __COMObject_COMEventEventHandler
COMEventHandlerInstance;
private void button1_Click(object sender, System.EventArgs e)
{
// create new instance of the COMObject class COMObject ObjectInstance;
short Num1; short Num2; short Sum;
ObjectInstance = new COMObjectClass();
Num1 = 5;
Num2 = 6;
//Call the SquareIt method
Sum = ObjectInstance.SquareIt(ref Num1, ref
Num2);
listBox1.Items.Add (Sum.ToString()); listBox1.Items.Add (ObjectInstance.Message);
// Set the value of message different than the
default
ObjectInstance.Message = "C# Rocks";
COMEventHandlerInstance = new __COMObject_COMEventEventHandler(COMEventHandler);
ObjectInstance.COMEvent += COMEventHandlerInstance;
ObjectInstance.FireCOMEvent();
}
void COMEventHandler(ref string Message)
{
listBox1.Items.Add(Message);
}
}
}
The output from this application looks similar to what is shown in Figure 34-6.
Figure 34-6: Output from the C# client using the COM component
Like any other object in .NET, you use the new operator to create a new instance of the COMObject class, as the following snippet demonstrates:
ObjectInstance = new COMObject();
Once the variable name ObjectInstance is instantiated, you use the object just as you would any other .NET object; nothing special needs to be done. The RCW handles all of the Interop, type conversions and object marshalling for the types, so you are completely hidden from any of the COM marshalling internals that are occurring.
If you have used COM Interop from VB .NET, you will notice something different about the way the parameters are passed to the methods in C#. If you look at the C# code for the SquareIt method, note the addition of the Ref keyword:
Num1 = 5;
Num2 = 6;
//Call the SquareIt method
Sum = ObjectInstance.SquareIt(ref Num1, ref Num2);
Visual Basic COM servers may pass values by value or by reference. Your C# code needs to use the appropriate keywords when passing parameters into COM method calls. You can use ILDASM to help you determine whether a parameter should be passed by value or by reference.
Open the assembly generated by Tlbimp using the ILDASM tool and look at the definition of the method that you want to call. In this case, you need to call the SquareIt() method. The SquareIt() method is listed in the assembly with the following signature:
SquareIt : int16(int16&,int16&)
The type of the return value returned by the method follows the colon. The signature of the SquareIt() method lists a return type of int16, which, in Intermediate Language parlance, denotes a 16-bit integer. The ampersands that follow the parameter types signify that the
In the .NET Framework, the CLR reports errors by throwing exceptions when things go wrong. In COM, HRESULTs are the avenue in which errors are reported, so the RCW needs to be able the map the HRESULT for a given error to the equivalent .NET exception.
Table 34-2 maps the standard HRESULTs in COM to their counterparts as .NET exceptions.
Table 34-2: HRESULTs to .NET Exceptions
HRESULT |
|
.NET Exception |
|
|
|
MSEE_E_APPDOMAINUNLOADED |
|
AppDomainUnloadedException |
|
|
|
COR_E_APPLICATION |
|
ApplicationException |
|
|
|
COR_E_ARGUMENT or E_INVALIDARG |
|
ArgumentException |
|
|
|
COR_E_ARGUMENTOUTOFRANGE |
|
ArgumentOutOfRangeException |
|
|
|
COR_E_ARITHMETIC or |
|
ArithmeticException |
ERROR_ARITHMETIC_OVERFLOW |
|
|
|
|
|
COR_E_ARRAYTYPEMISMATCH |
|
ArrayTypeMismatchException |
|
|
|
COR_E_BADIMAGEFORMAT or |
|
BadImageFormatException |
ERROR_BAD_FORMAT |
|
|
|
|
|
COR_E_COMEMULATE_ERROR |
|
COMEmulateException |
|
|
|
COR_E_CONTEXTMARSHAL |
|
ContextMarshalException |
|
|
|
COR_E_CORE |
|
CoreException |
|
|
|
NTE_FAIL |
|
CryptographicException |
|
|
|
COR_E_DIRECTORYNOTFOUND or |
|
DirectoryNotFoundException |
ERROR_PATH_NOT_FOUND |
|
|
|
|
|
COR_E_DIVIDEBYZERO |
|
DivideByZeroException |
|
|
|
COR_E_DUPLICATEWAITOBJECT |
|
DuplicateWaitObjectException |
|
|
|
COR_E_ENDOFSTREAM |
|
EndOfStreamException |
|
|
|
COR_E_TYPELOAD |
|
EntryPointNotFoundException |
|
|
|
COR_E_EXCEPTION |
|
Exception |
|
|
|
COR_E_EXECUTIONENGINE |
|
ExecutionEngineException |
|
|
|
COR_E_FIELDACCESS |
|
FieldAccessException |
|
|
|
COR_E_FILENOTFOUND or |
|
FileNotFoundException |
ERROR_FILE_NOT_FOUND |
|
|
|
|
|
COR_E_FORMAT |
|
FormatException |
|
|
|
COR_E_INDEXOUTOFRANGE |
|
IndexOutOfRangeException |
|
|
|
COR_E_INVALIDCAST or |
|
InvalidCastException |
E_NOINTERFACE |
|
|
|
|
|
COR_E_INVALIDCOMOBJECT |
|
InvalidComObjectException |
|
|
|
COR_E_INVALIDFILTERCRITERIA |
|
InvalidFilterCriteriaException |
|
|
|
COR_E_INVALIDOLEVARIANTTYPE |
|
InvalidOleVariantTypeException |
|
|
|
COR_E_INVALIDOPERATION |
|
InvalidOperationException |
Table 34-2: HRESULTs to .NET Exceptions
HRESULT |
|
.NET Exception |
|
|
|
COR_E_IO |
|
IOException |
|
|
|
COR_E_MEMBERACCESS |
|
AccessException |
|
|
|
COR_E_METHODACCESS |
|
MethodAccessException |
|
|
|
COR_E_MISSINGFIELD |
|
MissingFieldException |
|
|
|
COR_E_MISSINGMANIFESTRESOURCE |
|
MissingManifestResourceException |
|
|
|
COR_E_MISSINGMEMBER |
|
MissingMemberException |
|
|
|
COR_E_MISSINGMETHOD |
|
MissingMethodException |
|
|
|
COR_E_MULTICASTNOTSUPPORTED |
|
MulticastNotSupportedException |
|
|
|
COR_E_NOTFINITENUMBER |
|
NotFiniteNumberException |
|
|
|
E_NOTIMPL |
|
NotImplementedException |
|
|
|
COR_E_NOTSUPPORTED |
|
NotSupportedException |
|
|
|
COR_E_NULLREFERENCE or E_POINTER |
|
NullReferenceException |
|
|
|
COR_E_OUTOFMEMORY or |
|
OutOfMemoryException |
E_OUTOFMEMORY |
|
|
|
|
|
COR_E_OVERFLOW |
|
OverflowException |
|
|
|
COR_E_PATHTOOLONG or |
|
PathTooLongException |
ERROR_FILENAME_EXCED_RANGE |
|
|
|
|
|
COR_E_RANK |
|
RankException |
|
|
|
COR_E_REFLECTIONTYPELOAD |
|
ReflectionTypeLoadException |
|
|
|
COR_E_REMOTING |
|
RemotingException |
|
|
|
COR_E_SAFEARRAYTYPEMISMATCH |
|
SafeArrayTypeMismatchException |
|
|
|
COR_E_SECURITY |
|
SecurityException |
|
|
|
COR_E_SERIALIZATION |
|
SerializationException |
|
|
|
COR_E_STACKOVERFLOW or |
|
StackOverflowException |
ERROR_STACK_OVERFLOW |
|
|
|
|
|
COR_E_SYNCHRONIZATIONLOCK |
|
SynchronizationLockException |
|
|
|
COR_E_SYSTEM |
|
SystemException |
|
|
|
COR_E_TARGET |
|
TargetException |
|
|
|
COR_E_TARGETINVOCATION |
|
TargetInvocationException |
|
|
|
COR_E_TARGETPARAMCOUNT |
|
TargetParameterCountException |
|
|
|
COR_E_THREADABORTED |
|
ThreadAbortException |
|
|
|
COR_E_THREADINTERRUPTED |
|
ThreadInterruptedException |
|
|
|
COR_E_THREADSTATE |
|
ThreadStateException |
|
|
|
COR_E_THREADSTOP |
|
ThreadStopException |
|
|
|
COR_E_TYPELOAD |
|
TypeLoadException |
|
|
|
COR_E_TYPEINITIALIZATION |
|
TypeInitializationException |
[DllImport("user32.dll")]
public static extern int MessageBox(int hWnd, String text, String caption, uint type);
When using platform invoke, you may need to alter the default behavior of the interoperability between the managed and unmanaged code. You can do this by modify the fields of the DLLImportAttribute class. Table 34-4 describes the fields that can be customized for the DLLImportAttribute class.
|
|
Table 34-4: DLLImportAttribute Fields |
Object Field |
|
Description |
|
|
|
EntryPoint |
|
Specifies the DLL entry point to be called |
|
|
|
CharSet |
|
Controls the way that string arguments should be marshaled to the |
|
|
function. The default is CharSet.Ansi. |
|
|
|
ExactSpelling |
|
Prevents an entry point from being modified to correspond to the |
|
|
character set. The default value varies by programming language. |
|
|
|
CallingConvention |
|
Specifies the calling-convention values used in passing method |
|
|
arguments. The default is WinAPI, which corresponds to __stdcall |
|
|
for the 32-bit Intel-based platforms. |
|
|
|
PreserveSig |
|
Indicates that the managed method signature should not be |
|
|
transformed into an unmanaged signature that returns an |
|
|
HRESULT, and might have an additional [out, retval] argument |
|
|
for the return value. |
|
|
The default is True (the signature should not be transformed). |
|
|
|
SetLastError |
|
Enables the caller to use the Marshal.GetLastWin32Error API |
|
|
function to determine whether an error occurred while executing |
|
|
the method. In Visual Basic, the default is True; in C# and C++, |
|
|
the default is False. |
|
|
|
Calling DLL functions from C# is similar to calling them from Visual Basic 6. With the DLLImport attribute, however, you are simply passing the DLL's name and the method that you need to call.
Note It is recommended that your DLL function calls be grouped in separate classes. This simplifies coding, isolates the external function calls, and reduces overhead.
Summary
This chapter described how to use COM objects in .NET code and how to use the Tlbimp utility to generate .NET assemblies. You also took a brief look at how to interpret generated assemblies. In addition, you learned how to write COM client code in C#, including calling COM methods and working with COM properties. As you can see, the .NET Framework enables you to easily integrate existing COM code into your .NET applications. This easy integration gives you the opportunity to slowly move portions of an application to .NET, without having to rewrite all of the COM component logic in C#.
Chapter 35: Working with COM+ Services
In This Chapter
Microsoft has steadily enhanced the functionality of the COM subsystem since it was first released in 1993. One of the most significant enhancements to the COM programming model was introduced in 1997 with the release of Microsoft Transaction Server (MTS). MTS, first released as an add-on to Windows NT 4.0, enabled developers to develop components using an object broker that provided transaction, role-based security, and resource pooling services.
With the release of Windows 2000, Microsoft elevated the programming model offered by MTS to a first-class subsystem. COM+ is, in large part, a merging of the traditional COM programming model and the MTS programming model. For the first time, Windows provided support for both traditional COM (or unconfigured) components with attributed (or configured) MTS-style components directly from the operating system.
The .NET Framework offers both styles of components to developers writing componentbased software. This chapter examines how to develop C# classes that you can use as configured components with COM+.
Caution Although the .NET Framework is available on a variety of operating system platforms, COM+ is not available on the same set of platforms. Components written in C# that take advantage of COM+ services can only be used on platforms that support COM+. The COM+ class code built into the .NET Framework throws an exception of class PlatformNotSupported if your code attempts to access a feature that does not exist on the runtime platform.
Understanding the System.EnterpriseServices Namespace
Any C# class can be used by COM clients as a COM component, regardless of the class's inheritance tree. C# classes can be derived from nothing more than System.Object and still be used as COM components. Taking advantage of COM+ services within your C# classes, however, requires a more stringent inheritance policy.
The System.EnterpriseServices namespace provides the classes, enumerations, structures, delegates, and interfaces that you need to write applications that take advantage of COM+ and its enterprise-level services. If you have written components in C++ or Visual Basic 6 that ended up running inside of the COM+ Services Runtime, most of this chapter will seem familiar to you. From the standpoint of an experienced COM+ developer, the System.EnterpriseServices namespace wraps the functionality to which you previously had programmatic access. If you have written components in VB6, then you will be happy to see that previously unavailable features, such as object pooling, are now fully available to you through the Framework. Services, such as just-in-time (JIT) activation, object pooling, transaction processing, and shared property management, are all available as classes or attributes in the System.EnterpriseServices namespace.
Table 35-1 describes each of the classes available in the System.EnterpriseServices namespace.