简体   繁体   中英

How to pass a COM object from VBA to C++ DLL

I have an Excel/VBA application which outsources the heavy lifting to C++ via a DLL. I currently pass the inputs/outputs between the two using a long list of arguments which are fundamental data types and pointers thereof. I'm trying to refactor the code on both sides to make it more modular and realize it would be helpful to have a common object I can pass across, rather than having to recreate the structure on the C++ side from an unstructured list of pointers. Thought I'd give COM/.NET a try and this is where I'm at:

1) Created a COM Object using a C# Class Library along the following lines http://www.codeproject.com/Articles/12673/Calling-Managed-NET-C-COM-Objects-from-Unmanaged-C

// My_class.cs
namespace Interop {
  public interface IMy_class { ... }
  public class My_class : IMy_class  { ... }
}

2) Generated Interop.dll and registered Interop.tlb using regasm /codebase /tlb

3) In VBA, add a Reference to the Interop.tlb , stuck on trying to pass an instance to C++ DLL function:

// VBA
Declare Sub my_func Lib "my_func.dll" (ByRef mc as Interop.My_class)

Sub test_interop()
  Dim mc as Interop.My_class
  Set mc = New Interop.My_class
  my_func mc
End Sub

// my_func.cpp (exports my_func to my_func.dll)
#import "Interop.tlb"

void __stdcall my_func(Interop::IMy_classPtr icp) { ... }

VBA seems to create the instance ok but the call to my_func causes Excel to crash. I can see in debug mode that the value of icp received from Excel equals VarPtr(mc) and not ObjPtr(mc) which I believe is the address of the interface. So I tried changing it to my_func(Interop::My_class* mcp) and I can at least get in and out of the function without crashing, but it appears there's no functionality available to My_class. The functionality seems to be wrapped in something called _My_class , so I tried my_func(Interop::_My_class* mcp) but then I crash as soon as I try to interact with mcp . Same result with my_func(Interop::_My_classPtr mcp)

With the original function, I also tried passing ObjPtr(mc) directly to 'icp' using DispCallFunc from VBA. Again I could get in and out of the function ok but crashed when I try to interact with the object. Actually I could read from it without crashing (although not the correct values) but got this error when I tried to write to it: Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually the result of calling a function declared with one calling convention with a function pointer declared with a different calling convention. Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually the result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

Running out of options here and wondering if I'm even close, whether it's impossible, or whether this is just not the right way to do it.

Note I also tested the C# Class Library directly in C++. I can create an instance using CoCreateInstance and interact with it via the interface, so it seems to work ok if initiated from the C++ side.

COM and Interop does some internal plumbing which is tripping you up here. I believe you're looking to do something along the lines of:

 dim myObj as Interop.IMyClass    # Note: Interface, not the class
 set myObj = new Interop.MyClass
 myObj.name = "My Name"
 myObj.age  = 69
 myObj.job  = "Chief Stamp Licker"

 my_func myObj

and then inside my_func:

 std::cout << "Name: " << pMyObj->name() << ", "
           << "Age : " << pMyObj->age()  << ", "
           << "Job : " << pMyObj->job()  << std::endl;

..and have it print out the information send over.

To do this, you need to enhance your interface to provide 'properties'. You need a get and set method for each property you wish to be able to read or write. This may seem tedious, but there are very good reasons why you want to do this.

Alternatively, you can have handful of methods on your interface which can set a number of properties in one go:

myObj.setProperties("My Name", 69, "Chief Stamp Licker")
my_func myObj

..which would be easier/quicker for you to implement, but much less flexible and more time-consuming to extend. Something in-between may suit you - eg setPerson and setOccupation which take more than one parameter.

The reason you cannot just write directly into myObj's variables is that the 'myObj' in the Visual Basic runtime is not the object, it is an interface to a proxy object cooked up by either the Visual Basic runtime environment, the COM subsystem, DCOM subsystem, C# interop layer or some combination thereof, depending on your COM object registration, threading model and how Excel got initialised (which you cannot control).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM