mirror of
https://github.com/SSCLI/sscli_20021101
synced 2026-06-08 12:28:57 +00:00
9fa3874800
Moved the original file to the archive subfolder.
299 lines
11 KiB
HTML
299 lines
11 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
<html>
|
|
<head>
|
|
<title>Native and Managed Code Interop on Rotor</title>
|
|
<link rel="stylesheet" type="text/css" href="../rotor.css">
|
|
<body>
|
|
|
|
<h1 align="left">Native and Managed Code Interop in the Shared Source CLI</h1>
|
|
|
|
|
|
<p>The Microsoft® Shared Source CLI (SSCLI) implementation supports reduced interoperation between native and managed code as compared to
|
|
the Microsoft .NET Framework implementation on Microsoft® Windows®.
|
|
Because of platform independence requirements, the SSCLI does not support COM
|
|
interop in any form. This includes calling from managed code out to an
|
|
unmanaged COM implementation as well as native code calling into managed code
|
|
through COM wrappers over managed classes.</p>
|
|
|
|
|
|
<p>The following tables indicate the interoperability options available on
|
|
either implementation.</p>
|
|
|
|
|
|
<table border="1" width="100%" >
|
|
<tr VALIGN="top">
|
|
<th width="21%">Managed Code Calling Native Code</th>
|
|
<th width="39%">Microsoft .NET Framework on Windows</th>
|
|
<th width="40%">Microsoft Shared Source CLI</th>
|
|
</tr>
|
|
<tr VALIGN="top">
|
|
<td width="21%">COM interop</td>
|
|
<td width="39%">Supported.
|
|
<p>Managed wrapper classes allow managed code to create instances of unmanaged COM
|
|
classes and invoke methods.</td>
|
|
<td width="40%">Not supported.</td>
|
|
</tr>
|
|
<tr VALIGN="top">
|
|
<td width="21%">Platform invoke</td>
|
|
<td width="39%">Supported.
|
|
<p>Managed code can call directly to stdcall or cdecl C-style functions
|
|
exported by dynamic libraries implemented in native code.</td>
|
|
<td width="40%">Supported.
|
|
<p>No changes from the .NET Frameworks on Windows.</td>
|
|
</tr>
|
|
</table>
|
|
|
|
|
|
<p> </p>
|
|
|
|
|
|
<table border="1" width="100%" >
|
|
<tr VALIGN="top">
|
|
<th width="21%">Native Code Calling Managed Code</th>
|
|
<th width="40%">Microsoft .NET Framework on Windows</th>
|
|
<th width="39%">Microsoft Shared Source CLI</th>
|
|
</tr>
|
|
<tr VALIGN="top">
|
|
<td width="21%">COM interop</td>
|
|
<td width="40%">Supported.
|
|
<p>Unmanaged code can use COM wrapper classes to create instances of managed classes
|
|
and invoke methods.</td>
|
|
<td width="39%">Not supported.</td>
|
|
</tr>
|
|
<tr VALIGN="top">
|
|
<td width="21%">Managed Extensions for C++ </td>
|
|
<td width="40%">Supported.
|
|
<p>Native C++ classes can be compiled in the same assembly as managed
|
|
classes and can interoperate directly. Managed methods can be exported
|
|
directly as dynamic library entry points.</td>
|
|
<td width="39%">Not supported.</td>
|
|
</tr>
|
|
<tr VALIGN="top">
|
|
<td width="21%">Delegates</td>
|
|
<td width="40%">Supported.
|
|
<p>Managed code implements a delegate that is exported as a function
|
|
pointer to native code. </td>
|
|
<td width="39%">Supported.
|
|
<p>No changes from the .NET Framework on Windows.</td>
|
|
</tr>
|
|
<tr VALIGN="top">
|
|
<td width="21%">Foreign Function Interface (FFI)</td>
|
|
<td width="40%">Not supported.</td>
|
|
<td width="39%">Supported.
|
|
<p>This is a light-weight substitute for COM interop.</td>
|
|
</tr>
|
|
</table>
|
|
|
|
|
|
<h2>Foreign Function Interface (FFI)</h2>
|
|
<p>Because the SSCLI implementation does not support either the Managed
|
|
Extensions for C++ or COM interop, the Foreign Function Interface (FFI) was
|
|
implemented. The FFI allows code implemented in unmanaged native C to
|
|
create instances of managed classes and invoke methods. The following
|
|
FFI class and interface definitions are located in <tt>
|
|
corffi.h </tt>and <tt>
|
|
mscoree.h. </tt>These files are generated during the build process.</p>
|
|
<hr>
|
|
<pre>/* modeled after System.Reflection.BindingFlags */
|
|
typedef enum CorFFIBindingFlags
|
|
{
|
|
CorFFIInvokeMethod = 0x0100,
|
|
// CreateInstance = 0x0200,
|
|
CorFFIGetField = 0x0400,
|
|
CorFFISetField = 0x0800,
|
|
CorFFIGetProperty = 0x1000,
|
|
CorFFISetProperty = 0x2000,
|
|
} CorFFIBindingFlags;
|
|
|
|
interface IManagedInstanceWrapper : IUnknown {
|
|
public:
|
|
HRESULT InvokeByName([in] LPCWSTR MemberName,
|
|
[in] INT32 BindingFlags, /* one or more of CorFFIBindingFlags */
|
|
[in] INT32 ArgCount,
|
|
[optional,in,out] VARIANT *ArgList,
|
|
[optional,out] VARIANT *pRetVal);
|
|
};
|
|
|
|
STDAPI
|
|
ClrCreateManagedInstance([in] LPCWSTR typeName,
|
|
[in] REFIID riid, /* only IID_IManagedInstanceWrapper and IID_IUnknown are valid for FFI */
|
|
[out] IManagedInstanceWrapper **ppInstanceWrapper);</pre>
|
|
|
|
<hr>
|
|
<p>The SSCLI platform adaptation layer (PAL) runtime supports a limited version of some COM-style types: rotor_palrt.h defines HRESULT, IUnknown* and VARIANT. Since
|
|
IManagedInstanceWrapper derives from IUnknown, FFI calls can return managed object references
|
|
by having the marshaler
|
|
create a new IManagedInstanceWrapper and store it in the VARIANT as an IUnknown*. This allows integer
|
|
types, floating point types, strings, and objects to be passed
|
|
through FFI in both directions ([in] parameters, [out] parameters,
|
|
and return values).</p>
|
|
|
|
<p>FFI is modeled on COM semantics, so some familiarity with COM programming is required to use it.
|
|
Standard COM reference-counting semantics must be followed. For more
|
|
details, consult
|
|
standard COM reference documentation. There are many
|
|
sources for information on COM programming, for example,
|
|
<a href="http://msdn.microsoft.com/library/en-us/dncomg/html/msdn_com_co.asp">
|
|
The COM Programmer's Cookbook</a> and many other articles on
|
|
<a href="http://msdn.microsoft.com">msdn.microsoft.com</a> as well as the
|
|
Microsoft Visual C++® .NET documentation.</p>
|
|
|
|
<h4>FFI and Object References</h4>
|
|
|
|
<p>Object references are treated as vectors of function pointers.
|
|
</p>
|
|
|
|
<h4>FFI and Strings</h4>
|
|
|
|
<p>VT_BST should be used for by-value strings and VT_BSTR | VT_BYREF used for by-reference strings.</p>
|
|
|
|
<h4>FFI and VARIANT</h4>
|
|
|
|
<p>The following VARIANT types can be marshaled:</p>
|
|
|
|
<ul>
|
|
<li>VT_EMPTY</li>
|
|
<li>VT_NULL</li>
|
|
<li>VT_I2</li>
|
|
<li>VT_I4</li>
|
|
<li>VT_R4</li>
|
|
<li>VT_R8</li>
|
|
<li>VT_DATE</li>
|
|
<li>VT_BSTR</li>
|
|
<li>VT_BOOL</li>
|
|
<li>VT_UNKNOWN </li>
|
|
<li>VT_DECIMAL</li>
|
|
<li>VT_I1 </li>
|
|
<li>VT_UI1 </li>
|
|
<li>VT_UI2 </li>
|
|
<li>VT_UI4 </li>
|
|
<li>VT_I8</li>
|
|
<li>VT_UI8 </li>
|
|
<li>VT_INT</li>
|
|
<li>VT_UINT </li>
|
|
<li>VT_VOID</li>
|
|
</ul>
|
|
|
|
<p>Note: VT_BYREF can be marshaledbut VT_ARRAY is not supported.<h3>
|
|
FFI Sample Code</h3>
|
|
<pre>
|
|
IManagedInstanceWrapper *pWrap;
|
|
VARIANT RetVal;
|
|
HRESULT hr;
|
|
|
|
hr = ClrCreateManagedInstance(
|
|
L"System.Random,mscorlib,PublicKeyToken=b03f5f7f11d50a3a",
|
|
IID_IManagedInstanceWrapper,
|
|
(void**)&pWrap);
|
|
if (FAILED(hr)) {
|
|
fprintf(stderr, "ClrCreateManagedInstance failed with hr=0x%08x\n", hr);
|
|
return;
|
|
}
|
|
|
|
VariantClear(&RetVal);
|
|
hr = pWrap->InvokeByName(
|
|
L"Next", CorFFIInvokeMethod, 0, NULL, &RetVal);
|
|
|
|
if (FAILED(hr)) {
|
|
fprintf(stderr, "InvokeMethodByName failed with hr=0x%08x\n", hr);
|
|
return;
|
|
}
|
|
|
|
printf("System.Random.Next() returned %d\n", V_I4(&RetVal));
|
|
|
|
pWrap->Release();
|
|
</pre>
|
|
<h3>SSCLI FFI Implementation Location</h3>
|
|
|
|
|
|
<p>The implementation of the SSCLI FFI interop functionality can be found in the
|
|
following source files:</p>
|
|
|
|
|
|
<ul>
|
|
<li>%ROTOR_DIR%\clr\src\dlls\shim\shim.cpp</li>
|
|
<li>%ROTOR_DIR%\clr\src\inc\mscoree.h</li>
|
|
<li>%ROTOR_DIR%\clr\src\vm\ceemain.cpp</li>
|
|
<li>%ROTOR_DIR%\clr\src\inc\corffi.h</li>
|
|
<li>%ROTOR_DIR%\clr\src\vm\comcallwrapper.cpp</li>
|
|
<li>%ROTOR_DIR%\clr\src\vm\comcallwrapper.h</li>
|
|
</ul>
|
|
<p>The following tests show examples of using the FFI functionality:</p>
|
|
<ul>
|
|
<li>%ROTOR_DIR%\tests\dev\interoptest.cs</li>
|
|
<li>%ROTOR_DIR%\tests\dev\ffitest\ffitest.cpp</li>
|
|
</ul>
|
|
<h2>Wrapping cdecl Native Function Callbacks With Managed Delegates</h2>
|
|
|
|
|
|
<p>Both the .NET Framework on Windows and the SSCLI implementation support
|
|
delegates that can be exposed to native code as either stdcall or cdecl function
|
|
pointers. However, there is no existing syntax in C# to generate the
|
|
common intermediate language (CIL)
|
|
to support exporting delegates as cdecl function pointers. Note that this
|
|
only impacts cdecl APIs that are callback functions where the native code needs to call back
|
|
on a function pointer into the managed code for notification purposes. An
|
|
example of this would be a native API that requires a function pointer to call
|
|
back a function for
|
|
progress indication or resource enumeration.</p>
|
|
|
|
|
|
<p>There are several ways to work around this limitation. </p>
|
|
|
|
|
|
<ul>
|
|
<li>Create native wrappers over the cdecl APIs where the function pointers are
|
|
translated from cdecl to stdcall and back. You would only need to wrap
|
|
the API functions that require function pointers for callbacks. You can
|
|
then access the stdcall wrapper library from C# using delegates with platform
|
|
invoke for the callbacks.</li>
|
|
<li>Write the wrapper code in C# and modify the CIL.
|
|
|
|
|
|
<ul>
|
|
<li>Compile the wrapper code written in C# to an assembly.</li>
|
|
<li>Use the CIL Disassembler to store the resulting assembly as raw CIL.
|
|
</li>
|
|
<li>Modify the CIL to change the calling convention from stdcall to cdecl by
|
|
specifying the modopt for cdecl on the Invoke method of the delegate::
|
|
<blockquote>
|
|
<pre> modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)</pre>
|
|
</blockquote>
|
|
</li>
|
|
<li>Use the CIL Assembler to compile the modified CIL back into an assembly.</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<h2>Shared Source CLI Platform Invoke</h2>
|
|
|
|
|
|
<p>In general, the SSCLI implementation of a platform invoke method is the same as that
|
|
provided by the Microsoft .NET Framework on Windows. </p>
|
|
|
|
|
|
<p>For example, the SSCLI implementation follows the same rules as those
|
|
for the .NET Framework for string marshaling.
|
|
Strings are marshaled by read-only value. The value that is returned is the pinned
|
|
string object itself, so you can not modify it because the CLI string data type is immutable.</p>
|
|
|
|
|
|
<p>For some examples of using platform invoke in SSCLI please see
|
|
%ROTOR_DIR%\tests\dev\interoptest.cs.</p>
|
|
|
|
|
|
<p>Managed pointers an be marshaled using [MarshalAs(UnmanagedType.IUnknown)].
|
|
Because almost any type can be turned into the type Object through boxing, it
|
|
should be possible to marshal almost all types as IUnknown. If the
|
|
default marshaling does not work for your requirements, you can create a custom
|
|
marshaler in the same way as that supported in the .NET Framework.</p>
|
|
|
|
|
|
<p>Unlike the .NET Framework implementation of a platform invoke method, the SSCLI
|
|
implementation does not support VARIANT marshalling.<br>
|
|
</p>
|
|
|
|
<hr>
|
|
|
|
<p><i>Copyright (c) 2002 Microsoft Corporation. All rights reserved.</i></p>
|
|
</body>
|
|
</html> |