Runtime Compilation of DLL for COM Interop - c#

I am working on a project that builds mathematical models for a user and then can output them in different formats. Right now I support outputting in Python, Java, C++. This works, I just autogenerate the code and call it a day.
However, a new request has been made. The user wants to be able to use the models from within Excel. I did some searching and found http://richnewman.wordpress.com/2007/04/15/a-beginner%E2%80%99s-guide-to-calling-a-net-library-from-excel/
So this is a nice start but I need to do this programmatically. The models are stored as objects in the bigger program. If the user selects to export as a DLL for Excel, I would take some boilerplate code and insert the methods I would want to use.
However, it seems like I need to register the code for COM Interop. My test code creates a DLL I can use it C# and access its methods. But trying to add a reference in Excel 2000 (I know, I know, corporate sucks) VBA doesn't work. It seems that no TLB file is created, so there is nothing for it to load.
If I take the generated code compile it as a standalone having checked the make COM Visible and register for com interop boxes, the TLB is generated but Excel VBA throws an automation error.
So the actual questions.
1) How can I create at runtime a DLL that is Com Visible and Reistered for COM Interop?
2) How do I get Excel to play nice with it.
Simple Example DLL Code Follows:
using System;
using System.Runtime.InteropServices;
namespace VSN
{
[ComVisibleAttribute(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class VSNFunctions
{
public VSNFunctions()
{
}
/// <summary>
/// Adds 2 variables together.
/// </summary>
/// <param name=\"v1\">First Param</param>
/// <param name=\"v2\">Second Param</param>
/// <returns>Sum of v1 and v2</returns>
public double Add2(double v1, double v2)
{
return v1 + v2;
}
public double Sub2(double v1, double v2)
{
return v1 - v2;
}
public double Mul2(double v1, double v2)
{
return v1 * v2;
}
public double div2(double v1, double v2)
{
return v1 / v2;
}
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.CreateSubKey("CLSID\\{"+t.GUID.ToString().ToUpper() + "}\\Programmable");
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.DeleteSubKey("CLSID\\{"+t.GUID.ToString().ToUpper() + "}\\Programmable");
}
}
}
Code To Build the DLL programmitcally Follows:
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
String exeName = String.Format(#"{0}\{1}.dll", System.Environment.CurrentDirectory, "VSNTest");
MessageBox.Show(exeName);
parameters.OutputAssembly = exeName;
parameters.CompilerOptions = "/optimize";
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, DLLString);

How to: Expose Code to VBA in a Visual C# Project
To enable VBA code to call code in a Visual C# project, modify the code so it is visible to COM, and then set the ReferenceAssemblyFromVbaProject property to True in the designer.

Related

RuntimeBinding exception working with vectors of string

I have a C# UWP application that uses a custom UWP Runtime Library written in Visual C++.
In one of my Visual C++ classes I convert a vector of strings to a collection of platform strings, which are understandable to the C# UWP application.
My problem is a Windows Runtime Bindable exception is thrown only in release mode of the application. Everything works perfectly in debug mode.
Visual C++:
// internalModel.cpp - the model I get my vector of strings from
std::vector<std::string> InternalModel::getAllPackages() {
std::vector<std::string> names;
for (auto i = 0U; i < packageOptions.size(); i++) {
// these are strings
names.push_back(packageOptions[i]["name"]);
}
return names;
}
// .h - the method signature called by my C# program
Windows::Foundation::Collections::IVector<Platform::String^>^ GetAllPackages();
// .cpp - the method body called by my C# program
Windows::Foundation::Collections::IVector<Platform::String^>^ VisualModel::GetAllPackages() {
auto s_packages = internalModel->getAllPackages();
auto p_packages = ref new Platform::Collections::Vector<Platform::String^>();
for each (auto s_package in s_packages)
{
p_packages->Append(WinUtil::toPlat(s_package));
}
return p_packages;
}
// WinUtil.h - the util class that converts standard string vector to platform collection of strings
template <class A, class B>
static Platform::Collections::Vector<B^>^ toPlat(std::vector<A*>* s_vector) {
auto p_vector = ref new Platform::Collections::Vector<B^>();
for each (A* s_elem in s_vector) {
p_vector->Append(ref new B(s_elem));
}
return p_vector;
}
C#:
// where I call the Visual C++ from
var packages = VisualLib.GetAllPackages();
foreach (var package in packages)
{
// package should be string here
}
I'm unable to figure out what is causing the exception because my debugger can't enter the UWP Runtime Library.
Perhaps the way I'm converting the vector to platform collection is not right, but then again, it works in debug mode just fine. Also other parts of my program use this same utility function:
static Platform::Collections::Vector<B^>^ toPlat(std::vector<A*>* s_vector)
The exception thrown:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'Exception_WasThrown, Microsoft.CSharp.RuntimeBinder.RuntimeBinderException. For more information, visit http://go.microsoft.com/fwlink/?LinkId=623485'

Excel interop macro out/ref parameters

I have inherited a VB6 app that launches Excel, opens a workbook, and runs a macro on an interval. This macro returns values though its parameters. In my attempts to convert this to C# using interop, I can successfully run the macro, but these parameter values do not get returned.
Is there something missing/incorrect in the code below, or is this simply not supported?
VBA macro:
Sub Foo(bar As Long)
bar = 5
End Sub
C# code:
void CallFoo()
{
// Declared as an object to avoid losing the value in auto-boxing
// The result is the same if declared as int
Object bar = 0;
m_application.Run(m_fooCommand, a);
Console.WriteLine(a); // a is always 0
}
This (roughly) equivalent VB6 code gets the return value just fine.
Dim bar as Long
bar = 0
xlApp.Run "Test.xlsm!Foo", bar
MsgBox bar // prints 5
So as best as I can tell, no, you cannot return values through parameters from VBA to C# in this way. The next best thing is to simply create a type in .NET, make it COM visible, regasm it and then reference that in the VBA script.
So, for completeness...
A return type:
[ComVisible(true)]
[Guid("097B5B52-C73B-4BD0-A540-802D0BC7C49F")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IFooResult
{
int Value { get; set; }
}
[ComVisible(true)]
[Guid("76B6BCBD-6F4D-4457-8A85-CDC48F4A7613")]
[ClassInterface(ClassInterfaceType.None)]
public class FooResult : IFooResult
{
[DispId(1)]
public byte[] Buffer { get; set; }
}
Generate strong name (required by regasm)
sn -k FooLib.snk
Register the assembly
regasm FooLib.dll /tlb:FooLib.tlb /codebase
Then simply reference the library in the VBA project. It can now be passed as an argument and populated in the macro or created in and returned from the macro (I believe this would require cleanup on the .NET side, Marshal.ReleaseComObject()).

Automation Add-ins for MS Excel 2013

I am trying to write a C# automation add-in in Visual Studio 2013. The objective is to be able to call UDFs written in C# from within MS Excel 2013. I have read most of the publicly available materials on the subject and have tried to adapt several simple examples, such as.
Unfortunately, neither of them is written under VS 2013 and MSExcel 2013. Code sample:
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Globalization;
using Microsoft.Win32;
namespace MyFirstAddIn
{
// Early binding. Doesn't need AutoDual.
[Guid("5E6CD676-553F-481E-9104-4701C4DAB272")]
[ComVisible(true)]
public interface IFinancialFunctions
{
double Bid(string symbol);
double Ask(string symbol);
double[,] BidnAsk(string symbol, string direction = null);
}
[Guid("B9B7A498-6F84-43EB-A50C-6D26B72895DA")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class FinancialFunctions : IFinancialFunctions
{
// Private class members.
private static readonly WebClient webClient = new WebClient();
private const string UrlTemplate = "http://finance.yahoo.com/d/quotes.csv?s={0}&f={1}";
// Private method - data download.
private static double GetDoubleDataFromYahoo(string symbol, string field)
{
string request = string.Format(UrlTemplate, symbol, field);
string rawData = webClient.DownloadString(request);
return double.Parse(rawData.Trim(), CultureInfo.InvariantCulture);
}
// Public "interface" methods.
public double Bid(string symbol)
{
return GetDoubleDataFromYahoo(symbol, "b3");
}
public double Ask(string symbol)
{
return GetDoubleDataFromYahoo(symbol, "b2");
}
public double[,] BidnAsk(string symbol, string direction = null)
{
double bid = GetDoubleDataFromYahoo(symbol, "b3");
double ask = GetDoubleDataFromYahoo(symbol, "b2");
return direction == "v" ? new[,]{{bid}, {ask}} : new[,]{{bid, ask}};
}
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type type)
{
Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type, "Programmable"));
RegistryKey key = Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type, "InprocServer32"), true);
key.SetValue("",System.Environment.SystemDirectory + #"\mscoree.dll",RegistryValueKind.String);
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type type)
{
Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type, "Programmable"), false);
}
private static string GetSubKeyName(Type type, string subKeyName)
{
System.Text.StringBuilder s = new System.Text.StringBuilder();
s.Append(#"CLSID\{");
s.Append(type.GUID.ToString().ToUpper());
s.Append(#"}\");
s.Append(subKeyName);
return s.ToString();
}
}
}
I have made the assembly COM-visible via:
Project->Properties->Application->Assembly Information
and I've also registered COM interop in the "Build" tab.
After building, I can see in the registry that registration was successful and the add-in is registered under the correct GUID. However, when I open Excel and go to Developer->Add-ins->Automation, I cannot see my add-in in the list. I verified that the code I'm posting works with Excel 2010 but for some reason I fail to see my add-in in Excel 2013.
Can anyone help me with this?
OK, I have found the cause of the problem. Surprisingly, it turns out that Visual Studio does not have the capability to automatically register 64bit dlls under the correct tree in the registry. The solution is not to register the project for COM interop and manually add commands to invoke the 64bit version of RegAsm as a post-build event. The full path and name of the dll need to be included, so unfortunately this is not a fully automated solution.

Accessing my WPF COM Library from Python

I'd like to run my WPF application from a python script, however am having difficulty.
To achieve this I have converted the WPF application to a COM library as follows:
namespace MyWpfApp
{
[Guid("F75D3377-D677-41BF-B3D5-C677C442228F")]
public interface IMyWpfAppInterface
{
void ShowCOMDialog();
void ClickButton1();
void ClickButton2();
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("D936A84B-8B1C-4D62-B090-C06E3EB5EEE9")]
public class MyWpfClass : IMyWpfAppInterface
{
private static Thread m_runDlgThread;
private static MainWindow m_mainWindow = null;
private static Application m_app = null;
public MyWpfClass() { }
public void ShowCOMDialog()
{
m_msgHelper = new MessageHelper();
m_runDlgThread = new Thread(runDlg);
m_runDlgThread.SetApartmentState(ApartmentState.STA);
m_runDlgThread.Start();
}
public void ClickButton1(){// to do}
public void ClickButton2(){// to do}
private void runDlg()
{
Application m_app = new Application();
m_mainWindow = new MainWindow();
m_app.Run(m_mainWindow);
}
}
}
I have installed my assembly to the global assembly cache and registered the dll as follows
gacutil.exe" /i MyWpfApp.dll
REGASM MyWpfApp.dll /tlb:com.MyWpfApp.tlb
I have tested that I can successfully import the Typelib and run my WPF application from a win32 console application.
#include "stdafx.h"
#import "..\MyWpfApp\bin\Debug\com.MyWpfApp.tlb" named_guids raw_interfaces_only
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL); //Initialize all COM Components
// <namespace>::<InterfaceName>
MyWpfApp::IMyWpfAppInterfacePtr pDotNetCOMPtr;
// CreateInstance parameters
// e.g. CreateInstance (<namespace::CLSID_<ClassName>)
HRESULT hRes =
pDotNetCOMPtr.CreateInstance(MyWpfApp::CLSID_MyWpfClass);
if (hRes == S_OK)
{
const DWORD cTimeout_ms = 500;
HANDLE hEvent = CreateEvent(0, TRUE, FALSE, 0);
BSTR str;
pDotNetCOMPtr->ShowCOMDialog ();
bool toggle = true;
while(true)
{
DWORD dwWait = WaitForSingleObject(hEvent,cTimeout_ms);
if(toggle)
{
pDotNetCOMPtr->ClickButton1();
toggle = false;
}
else
{
pDotNetCOMPtr->ClickButton2();
toggle = true;
}
}
//call .NET COM exported function ShowDialog ()
}
CoUninitialize (); //DeInitialize all COM Components
return 0;
}
When I attempt to access the COM component from Python
import win32com.client
from win32com.client import constants as c
myWpf = win32com.client.Dispatch("MyWpfApp.MyWpfClass")
This fails, reporting
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 114, in _GetGoodDispatchAndUserName
return (_GetGoodDispatch(IDispatch, clsctx), userName)
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 91, in _GetGoodDispatch
IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
com_error: (-2147221164, 'Class not registered', None, None)
I can see the class "MyWpfApp.MyWpfClass" int the registry and also from OLE Viewer.
I've done this kind of thing many times for a C++ based application without any problems. However this was with either an ATL project or MFC app with automation switched on. In this case I have converted WPF application to a dll and converted to COM by hand.
Could someone please advise what else I need to do to run the app from python?
Thanks in advance.
Further Edit :
I can load the typelib in python but still can't access the com class
I ran
import win32com.client
import pythoncom
myApp = pythoncom.LoadTypeLib("D:\\MyWorkSpace\\testProgs\\ATL_COM\\WPF\MyWpfApp\\MyWpfApp\\bin\\Debug\\com.MyWpfApp.tlb")
downloads_stat = None
for index in xrange(0, myApp.GetTypeInfoCount()):
type_name = myApp.GetDocumentation(index)[0]
type_iid = myApp.GetTypeInfo(index).GetTypeAttr().iid
print type_iid
print type_name
if type_name == 'MyWpfClass':
downloads_stat = win32com.client.Dispatch(type_iid)
This confirms that the classes are loaded but I can't access them because they are reported as not registered.
>>>
{B5E3A6C6-09A0-315C-BF3A-CB943389F610}
MessageHelper
{FBE23BB0-3EDC-3A65-90EB-DF84F7545D70}
COPYDATASTRUCT
{8BEE824F-F708-3052-BC21-A9EC4E1BB002}
MainWindow
{8C0044EF-91A9-3CB3-9945-1ACA076F3D7E}
NextPrimeDelegate
{F75D3377-D677-41BF-B3D5-C677C442228F}
IMyWpfAppInterface
{D936A84B-8B1C-4D62-B090-C06E3EB5EEE9}
MyWpfClass
Traceback (most recent call last):
File "D:\MyWorkSpace\testProgs\ATL_COM\WPF\MyWpfApp\MyWin32App\Python\MyPythonClient.py", line 15, in <module>
downloads_stat = win32com.client.Dispatch(type_iid)
File "C:\Python27\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 114, in _GetGoodDispatchAndUserName
return (_GetGoodDispatch(IDispatch, clsctx), userName)
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 91, in _GetGoodDispatch
IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
com_error: (-2147221164, 'Class not registered', None, None)
>>>
I can also confirm that there is an entry 'MyWpfApp.MyWpfClass' in the registry with the class id {D936A84B-8B1C-4D62-B090-C06E3EB5EEE9}.
Further Edit:
I tried installing Python for .Net and ran the following command
import clr;
import sys
sys.path.append('D:\\MyWorkSpace\\testProgs\\ATL_COM\\WPF\\MyWpfApp\\MyWpfApp\\bin\\Debug')
clr.AddReference("MyWpfApp.dll")
However this produced the following error
FileNotFoundException: Unable to find assembly 'MyWpfApp.dll'.
at Python.Runtime.CLRModule.AddReference(String name) in C:\Users\Barton\Documents\Visual Studio 2008\Projects\PySharp\trunk\pythonnet\src\runtime\moduleobject.cs:line 375
My knowledge of COM, Assemblies and typelibs are quite limited so I would appreciate if someone could help me understand what I need to do to access the DLL from python.
When I look at the entry in OLE/COM Object viewer, it points to the tlb file, not the dll, i.e.
Win32=D:\MyWorkSpace\testProgs\ATL_COM\WPF\MyWpfApp\MyWpfApp\bin\Debug\com.MyWpfApp.tlb
So, it is only the tlb file that is registered, not the class. When I run the LoadTypeLib class I am loading this from a path on my hard drive. I need the dll to be registered when I attempt to dispatch. Otherwise, if I can access this directly from a fixed path this would also be good but I don't know how to do this.
Again, thanks for your help.

Unit Testing a c# project which uses native code

I have three projects
1)unmanaged c++ containing full business logic
2)C++/CLI (Project name managed)
3)C# GUI
I have added the library file of unmanaged c++ in C++/CLI and then dll of C++/CLI in C# project.This this all is working fine and execution is trouble-less.
Now i want to do unit testing of C# function that does call the C++/CLI wrapper and then get the results back.I created a unit test using Visual Studio 2010.I added the dll of C++/CLI in my test project.Now when i am trying to execute the test it throws the exception managed.dll not found
Here is the code
[TestMethod()]
public void getstateTest()
{
bool expected=true;
bool actual=false;
try
{
GUI.test target = new test();
expected = true; // TODO: Initialize to an appropriate value
actual = target.getstate();
}
catch (FileNotFoundException exception)
{
MessageBox.Show("Missing file is : " + exception.FileName);
}
Assert.AreEqual(expected, actual);
}
The getstate function is
namespace GUI
{
public class test
{
public bool getstate()
{
bool chk = false;
bool result;
String a = "some path";
String b = "some path"
String c = "some path"
managed objct;
objct = new managed();
objct.Initialize(a, b, c, chk);
objct.Execute();//calls the C++/CLI execute which calls unmanaged C++
result = objct.Executionresult();//gets a bool result
return result;
}
}
}
Same thing works fine when i run the application but on running the test project it says the dll is missing
Sorry if i made it confusing.Please ask if you need more information.Thanks in advance
Have you looked at the test project output folder ? Maybe the managed.dll isn't copied to the output.
Update:
Could you please post fusion log from the exception ? This should give some ideas why the file is not found.
The projects are probably compiling to different directories.
This causes the compiler to compile correctly, but at runtime it can't find the files.
This often happens when you import separate projects into a new solution.
Make the new Unit test compile to the same directory.

Categories

Resources