I'm creating a dll in C# which runs a simulation when a single function runSimulation() is called. This dll should be called from VBA, as certain parameter values are given as input in Excel. This is the code I use.
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
namespace Simulation
{
public static class Simulation
{
[ComVisible(true)]
[DllExport("runSimulation", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.SysInt)]
public static int runSimulation()
{
// Do simulation
return 0;
}
}
}
The above code is compiled as a Class Library with x64 as Platform Target and returns Simulation.dll.
VBA:
Public Declare Function runSimulation Lib "Simulation.dll" () As Long
Sub Run_DLL()
ChDir (ActiveWorkbook.Path)
Dim returnValue As Long
returnValue = runSimulation()
End Sub
Running the Visual Basic code returns
run-time error 453: 'Can't find DLL entry point runSimulation in
Simulation.dll'
when it tries to call runSimulation().
As a reference: I've tried running with
[DllExport("runSimulation", CallingConvention = CallingConvention.StdCall)]
instead, but this also doesn't work. I additionally tried using the advice given in https://www.linkedin.com/grp/post/40949-258782989 but it gives me the same error.
I managed to call the 'runSimulation' function in VBA code, by exposing the Simulation class through an interface in C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace NSSimulation
{
public interface ISimulation
{
int runSimulation();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Simulation : ISimulation
{
[ComVisible(true)]
public int runSimulation()
{
return 0;
}
}
}
Then re-compile your project and export your rebuilt library with 'tlbexp.exe' from the Visual Studio command prompt:
tlbexp.exe SimulationLib.dll /out:"SimulationLib.tlb" /win64
Then open the Visual Basic editor in Excel and add a reference to 'SimulationLib.tlb' through "Tools->References->Browse". To verify
Public x As New SimulationLib.Simulation
Sub test()
MsgBox x.runSimulation
End Sub
Related
I am trying access methods of a C# DLL (created one) from PowerBuilder but getting Error: Error calling external function.
C# Code
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace AESKeyGen
{
public static class AESKeyGen_
{
static string secretKey;
private static byte[] generateAppKey()
{
Aes KEYGEN = Aes.Create();
byte[] secretKey = KEYGEN.Key;
return secretKey;
}
[DllExport("AESKey_generate", CallingConvention = CallingConvention.StdCall)]
public static string AESKey_generate()
{
secretKey = Convert.ToBase64String(generateAppKey());
return secretKey;
}
}
}
PowerBuilder Code
FUNCTION string AESKey_generate() LIBRARY "AESKeyGen.dll
ls_AESKey = AESKey_generate()
I could able to test this DLL using another C# app successfully by adding reference.
tried to check methods exported by AESKeyGen DLL using depends.exe but could not any methods listed there.
I did try few things mentioned on internet but no luck.
please suggested.
I have a static library written in C++ that I wanted to access via my C# program. This library includes multiple classes. In my research and workings, I developed a CLR DLL wrapper for the static library to access the class methods (open and close). All successful until I tried to call one of the 'public' functions from the DLL. I receive the f(x) is inaccessible due to its protection level when trying to compile the C# project. f(x) in this case points to JsonMsgClientDll.jmcDll.jmClientClose() and JsonMsgClientDll.jmcDll.jmcOpen(). I have searched other rags to not find anything similar to what I have run into. Any help here would be great. Just a note that of the multiple classes of the static library, I am only trying to port (wrapper) the most basic (open/close) of functions to get it working firstly. All is made public and thus cannot figure out why they are not accessible.
I have listed the necessary code snippets below. The jsonmsgservice namespace is the static library reference where the class is JsonMsgClient. The output of the jmcDLL.cpp is a DLL named JsonMsgClientDll.dll. The noted DLL is referenced properly in the C# project.
jmcDLL.cpp
#include <vcclr.h>
#include "JmsClientConnector.h"
#include "JmsStatus.h"
#include "JsonMsgClient.h"
using namespace System;
using namespace jsonmsgservice;
namespace JsonMsgClientDll
{
public ref class jmcDll
{
public:
// constructor
jmcDll()
{
_myJsonMsgClient = new JsonMsgClient();
}
// destructor
~jmcDll()
{
delete _myJsonMsgClient;
}
// open a connection
JmsStatus::JsonMsgStatus jmcOpen(string ipAddr)
{
return _myJsonMsgClient->SessionOpen(ipAddr);
}
// close a connection
JmsStatus::JsonMsgStatus jmClientClose()
{
return _myJsonMsgClient->SessionClose();
}
private:
JsonMsgClient * _myJsonMsgClient;
};
}
C# Main Window.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.IO.Ports;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using JsonMsgClientDll;
namespace JTC_GUI
{
public partial class MainWindow : Window
{
...
int sockFd = 0;
string ipAddress = "";
uint msgIdVal = 0;
jmcDll jmClient = new jmcDll();
public MainWindow()
{
...
}
private void clientOpenButton_Click(object sender, RoutedEventArgs e)
{
ipAddress = ipAddrInput.Text;
if (...)
...
else
{
// attempting to call wrappered C++ code to open a connection
int jmcStatus = jmClient.jmcOpen(ipAddress);
if (sockFd > 0)
{
...
private void clientCloseButton_Click(object sender, RoutedEventArgs e)
{
if (jmClient.jmClientClose() == 0)
{
...
}
else
{
MessageBox.Show("The connection FAILED to close or was never opened");
}
}
In the C++/CLI code:
JmsStatus::JsonMsgStatus jmcOpen(string ipAddr)
{
return _myJsonMsgClient->SessionOpen(ipAddr);
}
The type of the function parameter is string, which is a native C++ type, while in your C# code, you call this function with a System.String paramter, which is a reference type, so the conversion need to be done here.
The function should be like this:(assuming you're using std)
#include <msclr\marshal_cppstd.h>
JmsStatus::JsonMsgStatus jmcOpen(System::String^ ipAddr)
{
std::string unmanaged = msclr::interop::marshal_as<std::string>(ipAddr);
return _myJsonMsgClient->SessionOpen(unmanaged );
}
I have written a class library. To execute methods that I wrote in my class library, I created a console application. In my console application, I added the class library that I wrote as a reference. I then added the appropriate using statement to my console application. My methods from this library are inaccessible currently. Why?
Here's my class library with a basic method. It was created in .NET framework 3.5.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.DataSourcesGDB;
using ESRI.ArcGIS.Geometry;
namespace RelateTablesValidation
{
[Guid("e1058544-0d84-49be-a406-b4e65707f95b")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("RelateTablesValidation.Validate")]
[ComVisible(true)]
public class Validate : ESRI.ArcGIS.Geodatabase.IClassExtension, ESRI.ArcGIS.Geodatabase.IObjectClassExtension, ESRI.ArcGIS.Geodatabase.IRelatedObjectClassEvents2
{
public void ChangeClassExtension(IObjectClass objectClass, String extensionUID, IPropertySet extensionProperties)
{
ISchemaLock schemaLock = (ISchemaLock)objectClass;
try
{
// Attempt to get an exclusive schema lock.
schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);
// Cast the object class to the IClassSchemaEdit2 interface.
IClassSchemaEdit2 classSchemaEdit = (IClassSchemaEdit2)objectClass;
if (!String.IsNullOrEmpty(extensionUID))
{
// Create a unique identifier (UID) object and change the extension.
UID extUID = new UIDClass();
extUID.Value = extensionUID;
classSchemaEdit.AlterClassExtensionCLSID(extUID, extensionProperties);
}
else
{
// Clear the class extension.
classSchemaEdit.AlterClassExtensionCLSID(null, null);
}
}
catch (COMException comExc)
{
throw new Exception("Could not change class extension.", comExc);
}
finally
{
schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
}
}
}
Here's my console app. RelateTablesValidation is the class library. Also created in .NET Framework 3.5
using System;
using System.Collections.Generic;
using System.Text;
using RelateTablesValidation;
using Esri.ArcGIS.Geodatabase;
using ESRI.ArcGIS.DatasourcesGDB;
namespace ApplyClassExtension
{
class Program
{
[STAThread()]
static void Main(string[] args)
{
//system sees objects from this namespace OK
IWorkspaceFactory workspaceFactory = new FileGDBWorkspaceFactory();
//now when i try to call my method, it doesn't even show up in Intellisense
ChangeClassExtension(method params would go here);
}
}
}
You can't do what you're trying to do.
All methods in C# are inside of objects. You must either make the method static and call it like this:
Validate.ChangeClassExtension(...);
Or don't make it static and instantiate an instance of Validate:
var val = new Validate();
val.ChangeClassExtension(...);
I'm trying to use a precompiled DLL with reflection, to instantiate an interface for my class that is in the DLL. I tried by the book, but it won't work. It throws InvalidCastException when I try to do something like:
ICompute iCompute = (ICompute)Activator.CreateInstance(type);
Where type of course is my class that implements ICompute interface. I'm stuck and don't know what to do. The complete code follows:
This is the DLL content:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication18
{
public class ClassThatImplementsICompute : ICompute
{
public int sumInts(int term1, int term2)
{
return term1 + term2;
}
public int diffInts(int term1, int term2)
{
return term1 - term2;
}
}
}
The actual program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace ConsoleApplication18
{
public interface ICompute
{
int sumInts(int term1, int term2);
int diffInts(int term1, int term2);
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Loading dll...");
Assembly assembly = Assembly.LoadFrom("mylib.dll");
Console.WriteLine("Getting type...");
Type type = assembly.GetType("ConsoleApplication18.ClassThatImplementsICompute");
if (type == null) Console.WriteLine("Could not find class type");
Console.WriteLine("Instantiating with activator...");
//my problem!!!
ICompute iCompute = (ICompute)Activator.CreateInstance(type);
//code that uses those functions...
}
}
}
Can anyone help me? Thanks!
The problem is to do with how you load the assembly with Assembly.LoadFrom().
LoadFrom() load the assembly into different context compared to context of the ICompute interface you are trying to cast to. Try to use Assembly.Load() instead if possible. i.e. put the assembly into the bin / probing path folder and load by the full strong name.
Some references:
http://msdn.microsoft.com/en-us/library/dd153782.aspx
http://blogs.msdn.com/b/suzcook/archive/2003/05/29/57143.aspx (see the disadvantage bit for LoadFrom)
I thought I knew how to do this, but obviously not so I'd appreciate some help!
I can't get my dll to register so I can instantiate it in a VBS, or elsewhere.
I wrote the following sample class, checked "Make assembly COM Visible", checked "Register for COM Interop", then built it.
When I try to instantiate it from VBS I get the "Activex component can't create object" error.
This is the class code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Smurf
{
public class Pants
{
public string Explode(bool Loud)
{
string result;
if (Loud)
result = "BANG";
else
result = "pop";
return result;
}
}
}
...and this is the VBS:
Dim a
Set a = CreateObject("Smurf.Pants")
msgbox("ok")
What else do I need to do?
Thanks :)
[edit]
Forgot to mention, after the first failure I tried REGSVR32 and REGASM - no help!
[/edit]
Note that when I try REGSVR32, I get this message:
The Module "C:...\Smurf.dll" was loaded but the entry-point DllRegisterServer was not found.
Make sure that "C:...\Smurf.dll" is a valid DLL or OCX file and then try again.
How helpful is that??
This is the latest version of the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Smurf
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface IPants
{
[DispId(1)]
string Explode(bool Loud);
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IPantsEvents
{
string Explode(bool Loud);
}
[ComVisible(true)]
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(IPantsEvents))]
public class Pants : IPants
{
public Pants() { }
[ComVisible(true)]
[ComRegisterFunction()]
public static void DllRegisterServer(string key) { }
[ComVisible(true)]
[ComUnregisterFunction()]
public static void DllUnregisterServer(string key) { }
[ComVisible(true)]
public string Explode(bool Loud)
{
string result;
if (Loud)
result = "BANG";
else
result = "pop";
return result;
}
}
}
There could be a few different things at play here. First, you'll want to use the regasm tool with the /codebase /tlb switch from an elevated command prompt (assuming Windows Vista, 7 or Windows Server 2008). Something like:
regasm "Path to Smurf.dll" /codebase /tlb
Once you have registered the dll using regasm you should be able to invoke it using VBS, VBA or VB6.
I was able to use early binding and late binding from VBA to call the Explode method. However, when I tried from VBScript I received the "ActiveX can't create object error as you did."
I'm running on Windows 7 64 bit, and I recalled that this can cause problems when compiling to 32 bit dlls and running them on 64 bit operating systems. On a whim, I fired up a command prompt and entered:
C:\Windows\SysWow64\CScript.exe "Path to VBScript"
The result was that the script ran correctly and displayed "Pop" on screen.
Here's the somewhat simplified C# code I used as well as the contents of the VBScript file.
namespace Smurf
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface IPants
{
string Explode(bool Loud);
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IPantsEvents
{
string Explode(bool Loud);
}
[ComVisible(true)]
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IPantsEvents))]
public class Pants : IPants
{
[ComVisible(true)]
public string Explode(bool Loud)
{
string result;
if (Loud)
result = "BANG";
else
result = "pop";
return result;
}
}
}
VBScript:
Dim x
Set x = CreateObject("Smurf.Pants")
MsgBox (x.Explode(False))
Set x = Nothing