-
-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add example for accessing .NET components via COM interop (#919)
- Loading branch information
1 parent
69a32f5
commit 173e655
Showing
9 changed files
with
953 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using System.Runtime.InteropServices; | ||
|
||
namespace MyLibrary | ||
{ | ||
[ComVisible(true)] | ||
[Guid("4C2DDA7F-9DC9-46FD-A107-832254B2EEBE")] | ||
public interface IExampleCom | ||
{ | ||
string GetMessage(); | ||
int GetSum(int a, int b); | ||
} | ||
|
||
[ComVisible(true)] | ||
[Guid("36B142F2-97DC-4594-96A4-8160EEB7184C")] | ||
public class ExampleCom : IExampleCom | ||
{ | ||
public string GetMessage() => "Hello from .NET!"; | ||
public int GetSum(int a, int b) => a + b; | ||
} | ||
} | ||
|
49 changes: 49 additions & 0 deletions
49
examples/com_interop/MySolution/MyLibrary/MyLibrary.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | ||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
<ProjectGuid>{BE1A9D69-94D6-4B3D-8844-3BE2769AF167}</ProjectGuid> | ||
<OutputType>Library</OutputType> | ||
<AppDesignerFolder>Properties</AppDesignerFolder> | ||
<RootNamespace>MyLibrary</RootNamespace> | ||
<AssemblyName>MyLibrary</AssemblyName> | ||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> | ||
<FileAlignment>512</FileAlignment> | ||
<Deterministic>true</Deterministic> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
<OutputPath>bin\Debug\</OutputPath> | ||
<DefineConstants>DEBUG;TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
<RegisterForComInterop>true</RegisterForComInterop> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
<DebugType>pdbonly</DebugType> | ||
<Optimize>true</Optimize> | ||
<OutputPath>bin\Release\</OutputPath> | ||
<DefineConstants>TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Reference Include="System" /> | ||
<Reference Include="System.Core" /> | ||
<Reference Include="System.Xml.Linq" /> | ||
<Reference Include="System.Data.DataSetExtensions" /> | ||
<Reference Include="Microsoft.CSharp" /> | ||
<Reference Include="System.Data" /> | ||
<Reference Include="System.Net.Http" /> | ||
<Reference Include="System.Xml" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Compile Include="Class1.cs" /> | ||
<Compile Include="Properties\AssemblyInfo.cs" /> | ||
</ItemGroup> | ||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||
</Project> |
33 changes: 33 additions & 0 deletions
33
examples/com_interop/MySolution/MyLibrary/Properties/AssemblyInfo.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System.Reflection; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
// General Information about an assembly is controlled through the following | ||
// set of attributes. Change these attribute values to modify the information | ||
// associated with an assembly. | ||
[assembly: AssemblyTitle("MyLibrary")] | ||
[assembly: AssemblyDescription("")] | ||
[assembly: AssemblyConfiguration("")] | ||
[assembly: AssemblyCompany("")] | ||
[assembly: AssemblyProduct("MyLibrary")] | ||
[assembly: AssemblyCopyright("Copyright © Halil Durmus 2024")] | ||
[assembly: AssemblyTrademark("")] | ||
[assembly: AssemblyCulture("")] | ||
|
||
// Setting ComVisible to false makes the types in this assembly not visible | ||
// to COM components. If you need to access a type in this assembly from | ||
// COM, set the ComVisible attribute to true on that type. | ||
[assembly: ComVisible(false)] | ||
|
||
// The following GUID is for the ID of the typelib if this project is exposed to COM | ||
[assembly: Guid("be1a9d69-94d6-4b3d-8844-3be2769af167")] | ||
|
||
// Version information for an assembly consists of the following four values: | ||
// | ||
// Major Version | ||
// Minor Version | ||
// Build Number | ||
// Revision | ||
// | ||
[assembly: AssemblyVersion("1.0.0.0")] | ||
[assembly: AssemblyFileVersion("1.0.0.0")] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.11.35327.3 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyLibrary", "MyLibrary\MyLibrary.csproj", "{BE1A9D69-94D6-4B3D-8844-3BE2769AF167}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{BE1A9D69-94D6-4B3D-8844-3BE2769AF167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{BE1A9D69-94D6-4B3D-8844-3BE2769AF167}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{BE1A9D69-94D6-4B3D-8844-3BE2769AF167}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{BE1A9D69-94D6-4B3D-8844-3BE2769AF167}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {38B18BDE-9196-4EC1-A63E-9D255F61D064} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
# Accessing a .NET Component through COM Interop | ||
|
||
This example demonstrates how to access a **.NET component** from Dart using | ||
COM (Component Object Model) interop. It covers two different ways of | ||
interacting with a .NET component exposed to COM: | ||
|
||
- **Early Binding** through direct method access using the COM object's VTable | ||
(Virtual function table). | ||
- **Late Binding** via the [IDispatch] interface. | ||
|
||
## Folder Structure | ||
|
||
- **MySolution/** | ||
- **MyLibrary/**: Contains the C# class library that exposes the .NET | ||
component to COM. | ||
- **main.dart**: Demonstrates calling methods from the .NET component using both | ||
_early_ and _late_ binding. | ||
|
||
## .NET Component: `MyLibrary` | ||
|
||
`MyLibrary` is a C# class library designed to be used through COM. It defines an | ||
interface and a class that implement two methods: one that returns a string and | ||
another that returns the sum of two integers: | ||
|
||
```csharp | ||
[ComVisible(true)] | ||
[Guid("4C2DDA7F-9DC9-46FD-A107-832254B2EEBE")] | ||
public interface IExampleCom | ||
{ | ||
string GetMessage(); | ||
int GetSum(int a, int b); | ||
} | ||
|
||
[ComVisible(true)] | ||
[Guid("36B142F2-97DC-4594-96A4-8160EEB7184C")] | ||
public class ExampleCom : IExampleCom | ||
{ | ||
public string GetMessage() => "Hello from .NET!"; | ||
public int GetSum(int a, int b) => a + b; | ||
} | ||
``` | ||
|
||
### Annotations Explained | ||
|
||
- **`[ComVisible(true)]`**: Marks the class and interface as visible to COM | ||
clients, including Dart. | ||
- **`[Guid("...")]`**: Assigns a unique identifier (GUID) to both the interface | ||
and class, required for COM registration. You can generate GUIDs with tools | ||
like `guidgen` or online GUID generators. | ||
|
||
## Building and Registering the .NET Component | ||
|
||
### Steps to Build | ||
|
||
1. Open the solution file `MySolution.sln` in Visual Studio. | ||
2. In **Solution Explorer**, right-click on the `MyLibrary` project and select | ||
**Build**. | ||
|
||
This will compile the library, generate DLL and TLB files, and register it for | ||
COM interop. Ensure that the **Register for COM Interop** option is enabled in | ||
the project settings so that the component is automatically registered after it | ||
is built. | ||
|
||
### Manual Registration | ||
|
||
If you have a third-party library and compiled DLL and TLB files, you can | ||
register the component manually using the [regasm] tool: | ||
|
||
1. Open a command prompt with administrator privileges. | ||
2. Navigate to the directory containing the DLL and TLB files. | ||
3. Run the following command: | ||
|
||
```cmd | ||
regasm MyLibrary.dll /tlb:MyLibrary.tlb | ||
``` | ||
|
||
After these steps, the .NET component will be accessible from Dart. | ||
|
||
## Accessing the .NET Component from Dart | ||
|
||
The `main.dart` file shows how to call methods from the .NET component using | ||
both _early_ and _late_ binding. | ||
|
||
### Early Binding | ||
|
||
Early binding, also known as VTable binding is used whenever a COM object's | ||
`IUnknown` interface is called. To use early binding on an object, you need to | ||
know the structure of the COM object's VTable, which includes both standard | ||
methods (from `IUnknown`) and any custom methods unique to the object. If the | ||
method signatures and their positions in the VTable are known ahead of time, you | ||
can call these custom methods in the same manner as the `IUnknown` methods. | ||
|
||
### Late Binding | ||
|
||
In late binding, the specific method or property being called is determined at | ||
runtime. This process involves using the `IDispatch` methods to locate the | ||
desired function, akin to looking up a page number in a table of contents rather | ||
than having it printed directly in the text. | ||
|
||
The two key functions that facilitate late binding are `GetIDsOfNames` and | ||
`Invoke`. The `GetIDsOfNames` maps method names (as strings) to a unique | ||
identifier known as a **dispid**. Once the dispid for the desired function is | ||
obtained, you can invoke it using the `Invoke` function. | ||
|
||
### Choosing Between Early and Late Binding | ||
|
||
The decision between early binding and late binding largely depends on your | ||
project's design and requirements. However, **early binding** is generally | ||
recommended in most scenarios. | ||
|
||
**Early Binding** is faster because your application binds directly to the | ||
memory address of the function being called. This eliminates the overhead of | ||
runtime lookups, making it at least twice as fast as late binding in terms of | ||
overall execution speed. | ||
|
||
On the other hand, **Late Binding** can be beneficial in specific situations: | ||
|
||
- It is useful when the exact interface of a COM object is not known at | ||
compile-time. | ||
|
||
- Late binding can also help address compatibility issues between different | ||
versions of a component that may have modified or adapted its interface. | ||
|
||
The benefits of **early binding** make it the optimal choice whenever possible. | ||
|
||
## Understanding the VTable in COM | ||
|
||
The VTable is a structure used in COM to manage method calls. Each COM object | ||
has a pointer that points to its VTable, which contains pointers to the object's | ||
methods. | ||
|
||
```plaintext | ||
+----------------------------------------------------------------+ | ||
| COM Object Memory Layout | | ||
+----------------------------------------------------------------+ | ||
| +-------------------+ +----------------------+ | | ||
| p --> | v-table pointer | --> | Function 1 pointer | | | ||
| +-------------------+ +----------------------+ | | ||
| | Function 2 pointer | | | ||
| +----------------------+ | | ||
| | Function 3 pointer | | | ||
| +----------------------+ | | ||
| | ... | | | ||
| +----------------------+ | | ||
+----------------------------------------------------------------+ | ||
``` | ||
|
||
When a client (like Dart) wants to invoke a method on a COM object, it uses the | ||
object's pointer to access the VTable. The VTable contains pointers to each | ||
method, allowing the client to call methods using their respective offsets. The | ||
first method is accessed at offset 1, the second at offset 2, and so on. | ||
|
||
The `IUnknown` interface is the base for all COM interfaces, providing methods | ||
for managing COM object lifecycles (like reference counting). | ||
|
||
```plaintext | ||
+----------------------------------------------------------------+ | ||
| IUnknown Interface Memory Layout | | ||
+----------------------------------------------------------------+ | ||
| +-------------------+ +----------------------+ | | ||
| p --> | v-table pointer | --> | QueryInterface | | | ||
| +-------------------+ +----------------------+ | | ||
| | AddRef | | | ||
| +----------------------+ | | ||
| | Release | | | ||
| +----------------------+ | | ||
+----------------------------------------------------------------+ | ||
``` | ||
|
||
COM interfaces can inherit from other interfaces. For instance, `IDispatch` | ||
inherits from `IUnknown`. It includes methods for invoking methods by name and | ||
retrieving type information. | ||
|
||
```plaintext | ||
+----------------------------------------------------------------+ | ||
| IDispatch Interface Memory Layout | | ||
+----------------------------------------------------------------+ | ||
| +-------------------+ +----------------------+ | | ||
| p --> | v-table pointer | --> | QueryInterface | | | ||
| +-------------------+ +----------------------+ | | ||
| | AddRef | | | ||
| +----------------------+ | | ||
| | Release | | | ||
| +----------------------+ | | ||
| | GetTypeInfoCount | | | ||
| +----------------------+ | | ||
| | GetTypeInfo | | | ||
| +----------------------+ | | ||
| | GetIDsOfNames | | | ||
| +----------------------+ | | ||
| | Invoke | | | ||
| +----------------------+ | | ||
+----------------------------------------------------------------+ | ||
``` | ||
|
||
The `IExampleCom` interface extends `IDispatch`, inheriting all its methods and | ||
adding its own: | ||
|
||
```plaintext | ||
+----------------------------------------------------------------+ | ||
| IExampleCom Interface Memory Layout | | ||
+----------------------------------------------------------------+ | ||
| +-------------------+ +----------------------+ | | ||
| p --> | v-table pointer | --> | QueryInterface | | | ||
| +-------------------+ +----------------------+ | | ||
| | AddRef | | | ||
| +----------------------+ | | ||
| | Release | | | ||
| +----------------------+ | | ||
| | GetTypeInfoCount | | | ||
| +----------------------+ | | ||
| | GetTypeInfo | | | ||
| +----------------------+ | | ||
| | GetIDsOfNames | | | ||
| +----------------------+ | | ||
| | Invoke | | | ||
| +----------------------+ | | ||
| | GetMessage | | | ||
| +----------------------+ | | ||
| | GetSum | | | ||
| +----------------------+ | | ||
+----------------------------------------------------------------+ | ||
``` | ||
|
||
[IDispatch]: https://learn.microsoft.com/windows/win32/api/oaidl/nn-oaidl-idispatch | ||
[regasm]: https://learn.microsoft.com/dotnet/framework/tools/regasm-exe-assembly-registration-tool |
Oops, something went wrong.