First off, I'll admit I'm cargo-culting this a little bit -- my nice clean sample code doesn't work when I'm wedging it into the real world. That being said...
I have a DLL called CPierce.CSharpCall.dll that has something like the following C# in it:
namespace CPierce.CSharpBridge
{
[System.Runtime.InteropServices.Guid("3D08DF02-EFBA-4A65-AD84-B08ADEADBEEF")]
public interface ICSide
{
// interface definition omitted...
}
[System.Runtime.InteropServices.Guid("CBC04D81-398B-4B03-A3D1-C6D5DEADBEEF")]
public partial class CSide : ICSide
{
// class definition omitted...
}
}
This is registered with regasm /tlb, etc.. Then, my C++ code looks something like this:
#import "CPierce.CSharpCall.tlb" named_guids
// Contains syntax errors!
int myfunc()
{
HRESULT hRes = S_OK;
CoInitialize(NULL);
CPierce.CSharpBridge::ICSide *pManagedInterface = NULL;
hRes = CoCreateInstance(
CPierce.CSharpBridge::CLSID_Class1,
NULL, CLSCTX_INPROC_SERVER,
CPierce.CSharpBridge::ICSide,
reinterpret_cast<void**> (&pManagedInterface));
// Calls to the interface omitted....
CoUninitialize();
return 0;
}
The problem is, of course, the syntactically wrong bit about CPierce.CSharpBridge. I know in C++ if I want to have a similar namespace to the C# code I could say:
namespace CPierce
{
namespace CSharpBridge
{
// definitions go here
}
}
But I don't think that's what I'm looking for here, since I just need to refer to two constants that are in another namespace without putting the entire method in that namespace.
What is the C++ syntax I need to complete this call to CoCreateInstance?
Update: On deeper (much deeper) inspection, I'm finding that my .tlb file created by regasm is nearly empty. When I catenated all of my source into a single .cs file and compile with:
csc /debug /t:library BigFile.cs
regasm BigFile.dll /tlb:BigFile.tlb
I get a hefty (and useful) tlb file.
When I compile the whole project from Visual Studio, I'm getting a .DLL all right, but regasm doesn't do anything with it but produce a minimal .tlb file. (ildasm shows almost no differences between the two DLL's)
If I compile BigFile.cs in Visual Studio, I get a DLL that's also useless.
I'm stumped.
C++ doesn't use the . operator to delimit namespaces; it uses ::. You would use CPierce::CSharpBridge instead of CPierce.CSharpBridge. Unfortunately, that doesn't seem to help you because you don't know what namespace is actually being generated by the TLB.
A simple solution to that is to not use namespaces at all and import without them:
#import "CPierce.CSharpCall.tlb" named_guids no_namespace
This appears to be a problem on the C#/Visual Studio side. I'll abandon this question and open an appropriate one. Thank you for your help in narrowing it down.
Related
I've struggle several hours on that and I can't find what I'm doing wrong.
I created a new C# dll project, here is the content of the only class it contain:
using System;
using System.Runtime.InteropServices;
namespace PolygonSl {
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Config {
[ComVisible(true)]
public string GetCompany() {
return "POL";
}
}
}
I basically remove everything from it trying to make it work, the only reference is System.
I checked the Make assembly COM-Visible flag on the Assembly Information and my project is signed (seams required for codebase).
It compiling fine, after that, I called RegAsm.exe, giving it my dll, I added /codebase and /tlb, the command is successful.
When I go to my VBA project, I can add my new tlb file to the references, working fine. After, I can use it in my code, the autocomplete is working and I can compile with no errors.
Then, when I execute, I got this:
Run-time error '430':
Class does not support Automation or does not support expected interface
Here is my code sample in the VBA:
Private Sub Button1_Click()
'With CreateObject("PolygonSl.Config")
With New PolygonSl.Config
MessBox .GetCompany, MB_OK, "Test"
End With
End Sub
I tried late binding and my code is running fine with it but I'd like to be able to use the autocomplete.
Anyone have a suggestion on what I could try to make it work?
Edit (Adding some details on my environment)
I work on VS2008 for projects related to Dynamics SL (one of the Microsoft ERPs)
I'm on Windows Server 2008 R8 Standard, running from VMWare
Compiling on Framework 3.5, Release, x86, Dynamics SL client is 32 bits
I tried my dll on Dynamics but also on Excel to be sure that the problem was not Dynamics ;)
I think you need to define an interface to be able to see getcompany.
using System;
using System.Runtime.InteropServices;
namespace PolygonSl
{
[Guid("6DC1808F-81BA-4DE0-9F7C-42EA11621B7E")]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IConfig
{
string GetCompany();
}
[Guid("434C844C-9FA2-4EC6-AB75-45D3013D75BE")]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.ClassInterface(ClassInterfaceType.None)]
public class Config : IConfig
{
public string GetCompany()
{
return "POL";
}
}
}
You can generate the interface automatically by placing the cursor in the class definition and using Edit.Refactor.ExtractInterface.
I'd have to admit that I'm at the absolute edge of my abilities here and the above is put together based on examples I've seen elsewhere.
Edit
The following test code works fine on my PC
Option Explicit
Sub polygontest()
Dim my_polygon As SOPolygon.Config
Set my_polygon = New SOPolygon.Config
Debug.Print my_polygon.GetCompany
End Sub
Where SOPolygon is the project name.
I'm using IronPython for fetching inner classes from C# dll.
for example:
namespace Platform.CardHost {
internal class ExtensionManager : IExtensionManager, IDisposable {
//... other code
IronPython Code
import clr
clr.AddReference('Platform.CardHost')
import Platform.CardHost.ExtensionManager
# raise ImportError: No module named ExtensionManager
# if it add to ref
clr.AddReference('Platform.CardHost.ExtensionManager')
# raise Error
# IOError: System.IO.IOException: Could not add reference to assembly
# Platform.CardHost.ExtensionManager
How can I import ExtensionManager? Or is this not possible?
So like I already wrote:
make ExtensionManager public if you want to access it from somewhere else than your assembly.
The definition of internalis
The type or member can be accessed by any code in the same assembly, but not from another assembly.
what you could do, to make it only available for another assembly is, to make it visible for a friend assembly:
using System.Runtime.CompilerServices;
using System;
[assembly:InternalsVisibleTo("my_friend_assembly")]
internal class ExtensionManager : IExtensionManager, IDisposable
{
}
else, I don't see any reason why making it internal but trying to access it from another assembly/your ironpython-script. Yeah, for friend assemblies, there are reasons, for sure.
for your new update: "I can't change class":
so maybe, the guy who wrote that class doesn't want you to import the class from elsewhere? That's the use of internal,protected,private and public.
Imho, it would be really bad to define in C# a class as internal, so you can't import it from C#, but IronPython still lets you import it.
for sure, you could try getting the code from the assembly, change it and make it again to a new assembly, like you wrote. But that's a lot of work and possibly in the end, it won't work.
Thanks to Matthias Burger for a fact that prompted the idea.
i try to decompile dll. because the file were large after he disassemble, it can't assemble without problem.
I wrote to the guy, he say me use C# interface ICardHost.
here how i use it, maybe for someone who meet similar problem.
clr.AddReference('Platform.CardHost')
from Platform import CardHost
from Platform.CardHost import ICardHost
host = CardHost.CardHost.CreateInstance(session)
# ExtensionManager is internal class but it available by interface
# here how to use C# interface
em = ICardHost.ExtensionManager.__get__(host)
as it in C#
// cardHost
public sealed class CardHost : Component, ICardHost
// ICardHost
public interface ICardHost {
IExtensionManager ExtensionManager { get; }
Here are the steps what I have done.
I have created a .dll file using C# with the content as
public static int MyFunction(int dummy)
{
MessageBox.Show("I am in dll");
return 0;
}
I have also created an MFC application (exe) with content as
int main()
{
int a = MyFunction(0);
return 0;
}
Is this right way to do the call ?
Note:
i. I have changed my MFC application to /cl (common language run time support)
ii. I have also added C# file in the MFC's Reference.
Problems I faced:
Error 1 error C3861: 'MyFunction': identifier not found
Warning 2 warning C4793: 'MyDialog::`vcall'{132}'' : function compiled as native :
I have used the following command in the MyDialog.cpp file as a last line, the warning is solved.
#pragma unmanaged
Now how to solve the Error?
The C# code is placed inside a class, say MyClass. If you want to call a static member of this class in C++/CLI, you need to use MyClass::MyFunction(0).
Finally, you would need to add the namespace: MyNamespace::MyClass::MyFunction(0).
You can compile your C++ program with the /clr flag and use C++/CLI to simply call any .NET code (as long as you include a reference to it).
You could also use a COM callable wrapper for your C# code (compiled in a DLL) -> check this article
I have a C# DLL file project (my_cs_dll.dll) which defines a static class with a static member function.
namespace Foo
{
public static class Bar
{
public static double GetNumber() { return 1.0; }
}
}
I also have a C++ DLL project which is using /clr.
#using <my_cs_dll.dll>
double get_number_from_cs() { return Foo::Bar::GetNumber(); }
I've added a reference to 'my_cs_dll.dll' in the C++ project Common Properties references section (copy local/copy dependencies are both True).
And I've also added the path to 'my_cs_dll.dll' in the C++ project Configuration Properties C/C++ General 'Resolve#using References' section.
Everything builds without error, however at runtime I keep getting a 'System.IO.FileNotFound' exception from the system claiming it can't find the my_cs_dll.dll assembly.
Both DLL files are definitely present in the same directory from which I'm running.
I have tried all sorts of variations on the settings mentioned above and read everything I could find on manged/unmanaged interop, but I can't seem to get my brain around what is wrong...
I'm using Visual Studio 2008 and .NET 3.5.
It sounds like your C# assembly is not being resolved at runtime. Is your C# dll in the same directory as (or a subdirectory of) your executable? It's been a while since I did this, but my recollection is that unless your assembly is installed in the GAC, it must be in the directory (or a subdirectory) where your executable is located, as opposed to the location of the dll that's using it. This has to do with the .NET security features.
If you are still having problems, you can try using resolving the assembly yourself. In your clr-enabled C++ project, try adding the following:
using namespace System;
using namespace System.Reflection;
void Resolve()
{
AppDomain::CurrentDomain->AssemblyResolve +=
gcnew ResolveEventHandler(OnAssemblyResolve);
}
Assembly ^OnAssemblyResolve(Object ^obj, ResolveEventArgs ^args)
{
#ifdef _DEBUG
String ^path = gcnew String(_T("<path to your debug directory>"));
#else
String ^path = gcnew String(_T("<path to your release directory>"));
#endif
array<String^>^ assemblies =
System::IO::Directory::GetFiles(path, _T("*.dll"));
for (long ii = 0; ii < assemblies->Length; ii++) {
AssemblyName ^name = AssemblyName::GetAssemblyName(assemblies[ii]);
if (AssemblyName::ReferenceMatchesDefinition(gcnew AssemblyName(args->Name), name)) {
return Assembly::Load(name);
}
}
return nullptr;
}
You may have to tweak the code a little bit to get it to compile in your project. In my case, I made the two functions static methods of a class in my clr-enabled project. Just make sure you call the Resolve() function early on in your code, i.e., before you try to call get_number_from_cs().
While using COM is an option, it is not necessary. You're on the right path with your current approach. If you want some hand-holding, take a look at this CodeProject example. It's the one I following to get my unmanaged application to use my managed assemblies.
I've made a simple C# DLL (that's part of a much larger project) using VS2005. I need to use the DLL in Excel via VBA code so I am using COM Interop on the assembly. I am trying to make the build process automatically generate the necessary TLB file so that I don't need to go to the command line and use regasm after every build.
My problem is that although the DLL compiles and builds fine, it does not generate a TLB file. Instead, the error in the title prints out in the output box.
I've gotten other DLLs to build TLB files by going to the project's properties in VS2005 -> Build -> Output -> Check "Register for COM interop". Also I have [assembly: ComVisible(true)] in the AssemblyInfo.cs.
Here's the summary of the source for the problem DLL and the DLL that it references for a return type:
using System;
using System.IO;
using System.Runtime.InteropServices;
using SymbolTable;
namespace ProblemLibrary
{
public class Foo
{
public Foo(string filename)
{
...
}
// method to read a text file into a SymbolTable
public SymbolTable BuildDataSet(string[] selected)
{
...
}
}
}
Here is a summary of SymbolTable.dll. It holds a return type that ProblemLibrary uses.
using System;
using System.Collections.Generic;
namespace SymbolTable
{
public class SymbolTable
{
readonly Dictionary<SymbolInfoStub, string> _symbols = new Dictionary<SymbolInfoStub, string>();
/*methods that interact with Dictionary snipped*/
}
}
You need to have ctor without any params.
You should have GuidAttribute and ProgIdAttribute around the classes.
Its better to mark the assembly as ComVisible(false) and mark explicitly the classes that need export.
Use interfaces for your classes.
Make sure the you have GuidAttribute in the assembly level.
[Guid("<PUT-GUID-HERE-1>")]
[ComVisible(true)]
interface IFoo
{
void DoFoo();
}
[Guid("<PUT-GUID-HERE-2>")]
[ComVisible(true)]
[ProgId("ProgId.Foo")]
class Foo : IFoo
{
public void DoFoo()
{
}
}
In the AssemblyInfo.cs file, make sure you have the following:
// 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(true)]
UPDATE:
Read: How can I make use of .NET objects from within Excel VBA?
Which links to:
http://richnewman.wordpress.com/2007/04/15/a-beginner%E2%80%99s-guide-to-calling-a-net-library-from-excel/
I saw a similar problem. I got an error like:
warning MSB3391: does not contain any
types that can be unregistered for COM
Interop.
I followed all the rules (ComVisible, etc.) but nothing worked.
Solution: I had to put something in the default constructor so that it would not be optimized away. The moment I had something there, the registration finished with no message and the component was visible in the registry.
Interesting note: a friend of mine managed to register the original DLL with the empty default constructor on his machine (64-bit Windows-7, VS2008-Professional, like mine). However, his REGASM.EXE was:
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\regasm.exe
while mine was:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe
So it could be some difference between versions of the .NET framework - maybe the later version is optimizing too much and the REGASM does not account for that.