I'm taking a lot of time trying to figure this out, so I thought I could get some help here.
Basically I have a DLL function declared like this in IDL:
[id(1), helpstring("method findFile")] HRESULT findFile(
[in] BSTR fileName,
[out] LONG* someValue
);
How exactly do I declare and invoke from either C++ / C#?
Note: there is a VB6 app that successfully invokes the function. Declaration is:
Private Declare Function findFile Lib "thedll.dll" ( _
ByVal fileName As String, _
ByRef someValueAs Long _
)
The call:
Dim a As String
Dim b As Long
Dim r As long
a = "image.jpg"
b = -1
r = findFile(a, b)
Addendum:
I cannot guarantee that the VB6 code looks like that because I have the executable, I was only told what that portion looks like, so maybe you guys are right and it doesn't match. I did author the C++ DLL, and now I need to get together some code myself that successfully calls the DLL in order to try out stuff and not depend on that exe.
C++ implementation of the DLL function looks like this:
STDMETHODIMP CFinder::findFile(BSTR fileName, LONG* someValue)
{
*someValue = 8;
return S_OK;
}
Untested C# declaration:
[DllImport("thedll.dll", SetLastError=true)]
static extern int findFile([MarshalAs(UnmanagedType.BStr)]string fileName, out int someValue);
Related
I'm working from VBA into .NET.
I have a working version of an interface using CLI and stdcall
I'm trying to remove the resulting dependency on the C++ 2015 runtime, and it looks like I can do it using UnmanagedExports.
But I have a couple of questions.
Can I just use "ref string" as a parameter and have it work?
If so, can I replace it with "out string"?
In either cases, do I have to do any string/string length management?
I currently pass a couple of callbacks in as "int". From an example I saw elsewhere, it looks like on the C# side, using this, I should be able to replace those parameters with Func for a callback such as Function A(p1 as String, p2 as Long, p3 as String) as Long
Any advice would be much appreciated.
You need a combination of StrPtr, possibly StrConv on the Access side, and IntPtr on the .NET side:
'VBA7
Private Declare PtrSafe Function Command Lib "External.dll" (ByVal CommandName As String, ByVal Result As LongPtr, ByRef ResultLength As Long) As Long
'VBA pre7
Private Declare Function Command Lib "External.dll" (ByVal CommandName As String, ByVal Result As Long, ByRef ResultLength As Long) As Long
'Example to use.
'Result will be up to "i" characters - no new string involved
Dim i As Long, x As Long, strResult As String
i = 100
strResult = Space(i)
x = Command(CommandName, Arguments, StrPtr(strResult), i)
If you use StrConv, the string type is up to you. If you don't, the pointer will be pointing to a Unicode array.
C# side:
[DllExport("Command", CallingConvention.StdCall)]
public static int Command(string commandName, string arguments, IntPtr result, out /*or ref*/ int resultLength)
{
string inputStr = Marshal.PtrToStringUni(result); //Unicode
resultLength = inputStr.Length;
int x = MainFunc.Command(commandName, arguments, ref inputStr);
if(null == inputStr)
{
inputStr = "";
}
if(inputStr.Length > resultLength)
{
inputStr = inputStr.Substring(0, resultLength);
}
byte[] outputBytes = Encoding.Unicode.GetBytes(inputStr);
Marshal.Copy(outputBytes, 0, result, outputBytes.Length);
resultLength = inputStr.Length;
return x;
}
I have this c++ function :
unsigned int ReadUserMemory( unsigned char *lpBuffer, unsigned int iAddress, unsigned int nNumberOfByte );
Which reads nNumberOfByte bytes from iAddress of a memory and places it in lpBuffer.
I created an ATL Object for it( Going to be used in C# ) with this interface :
[id(6), helpstring("method ReadUserMemory")] HRESULT ReadUserMemory(BYTE* lpBuffer, [in] ULONG iAddress, [in] ULONG nNumberOfByte, [out,retval] ULONG* result);
I'm neither C# programmer nor experienced ATL programmer! Now I'm going to test this function in C# with this code :
byte [] b = new byte[100];
axObj.ReadUserMemory(b, 0, 100);
But apparently this code is wrong. How to call this method? Thanks in advance.
I think Peter R has a very valid point, in that using COM is complicated, because it requires learning COM as well as C++ and C#, as you've discovered. C++ interop is another option, but that also requires learning a third technology, called C++/CLI, which is probably a better fit than COM, but can still be problematic. Both COM and C++/CLI are quite big, complicated beasts, so you might be better off just declaring the C function you need in a dll and using P/Invoke, see e.g. http://msdn.microsoft.com/en-us/magazine/cc164123.aspx. If so, the magic you want can be found in How can I pass a pointer to an array using p/invoke in C#?.
Assuming you want to continue down the COM route, I had a play around with it, and to me it looks like you need to get something that is going to work via the type library that is embedded in the COM dll. In short, I couldn't make the COM interop work with unsigned char *lpBuffer, although perhaps someone else can! The type-library friendly type to use is a SAFEARRAY, which is essentially the way that VB likes to see arrays.
In this case, your idl becomes
[id(1)] HRESULT ReadUserMemory([out] SAFEARRAY(byte)* buffer, [in] ULONG iAddress, [in] ULONG nNumberOfByte, [out,retval] ULONG* result);
Your C++ implementation looks like something like this
STDMETHODIMP CObj::ReadUserMemory(SAFEARRAY ** pBuffer, ULONG iAddress, ULONG nNumberOfByte, ULONG* result)
{
if (pBuffer== nullptr)
return E_POINTER;
SAFEARRAY*& psa = *pBuffer;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = nNumberOfByte;
psa = SafeArrayCreate(VT_UI1, 1, rgsabound);
if(psa == NULL)
return E_OUTOFMEMORY;
void* data = nullptr;
SafeArrayAccessData(psa, &data);
for(int i=0; i<nNumberOfByte; ++i) {
((char*)data)[i] = (char)i;
}
SafeArrayUnaccessData(psa);
return S_OK;
}
The SafeArrayUnaccessData should be called even if the code after the SafeArrayAccessData fails.
The C# client now looks like this, notably we have changed from byte[] to System.Array
static void Main(string[] args)
{
RUMLib.IObj axObj = new RUMLib.Obj();
Array a = null;
axObj.ReadUserMemory(out a, 2, 6);
for (int i = 0; i < a.Length; ++i)
{
Console.Write("{0},", a.GetValue(i));
}
Console.WriteLine();
}
and the output of the program is
0,1,2,3,4,5,
Note that there will v likely be marshalling, i.e. copying of the data, as the data comes back from the SAFEARRAY to the C# array. This might be prohibitively costly for you, in which case suggest you go with C++/CLI or P/Invoke.
I am currently developing an Excel add-in in C#, and I am using interop with a C++ library. Both are developed by me. The C++ library is not a COM component, but just a DLL exporting some functions.
I have one C# function, that I want to pass a number and two strings to the C++ function, and the C++ function returns a string. First I tried to return a string directly, so the prototypes look something like
//C#
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string Function([MarshalAs(UnmanagedType.I4)] int number,
[MarshalAs(UnmanagedType.LPStr)]string str1,
[MarshalAs(UnmanagedType.LPStr)] string str2);
//C++
extern "C" __declspec(dllexport) string Function(int, char*, char*);
(Btw, I set already the calling convention in C# for this function as Cdecl)
Then when I call the C# version, and stepped into the C++ (when debugging), I found that the parameters are shifted, so the integer is the value of the pointer to the first string, and the first string is the second string, and the second string points to somewhere else. I also checked the memory, and found that the parameters were not passed according to the _cdecl convention, the integer was the last one pushed into the stack (which is ok), but the positions of the two strings were switched. There are many other information that the compiler added, so I don't want to go further deep into this.
However, later I changed the return string as a parameter, and the prototypes look like
//C#
private static extern void Function([MarshalAs(UnmanagedType.I4)]int number,
[MarshalAs(UnmanagedType.LPStr)] string str1,
[MarshalAs(UnmanagedType.LPStr)] string str2,
[MarshalAs(UnmanagedType.LPStr), Out] StringBuilder returnStr);
//C++
extern "C" __declspec(dllexport) void Function(int, char*, char*, string&);
And now the parameters are passed correctly.
So my first question is, how does this happen?
My second question is, even using the second method, I could not get the program run correctly, the program crashed when returning from the C++ function (actually in the C++ function, the only thing being done is calling another member function of a class, so the function is just like a wrapper of the member function, and the program crashed exactly when returning in the member function. Since the warpper function just calls the member function, so I cannot tell where the problem is).
Does anyone have an idea how to do this? So the main point of the function is to take a number and two strings and return another string.
Thanks in advanced.
look at this article about the usage of stringbuilder in unmanaged code
try something like this:
//C#
public string Function(int number, string str1, string str2)
{
StringBuilder sbStr1 = new StringBuilder(str1);
StringBuilder sbStr2 = new StringBuilder(str2);
StringBuilder sbReturnStr = new StringBuilder(1024);
Function(number, sbStr1 , sbStr2 , sbReturnStr , sbReturnStr.Capacity);
return sbReturnStr.ToString();
}
//C# p/invoke
private static extern void Function(int number, StringBuilder str1, StringBuilder str2, StringBuilder returnStrBuffer, int size);
//C++
extern "C" __declspec (dllexport) void __stdcall Function(int number, const char* str1, const char* str2, char* returnStrBuffer, int size)
{
//fill the returnStrBuffer
strncpy(returnStrBuffer, "blablalb", size); //example!!
}
I've created a DLL function for using inside C# using DLLImport but having troubles in calling the method, as I'm getting memory corruption problems;
[DllImport("mydll.dll", EntryPoint = "callinmydll")]
public static extern int testdllcall(double *firstinput, long firstcount, double *secondoutput, long secondcount);
Here's part of the C++ library header;
extern "C" {
mydll_API int callinmydll(double *in, long firstcount, double *out, long secondcount);
}
Implementation.
mydll_API int callinmydll(double *in, long firstcount, double *out, long secondcount)
{
for( int i =0 ; i < 10 ; i++ )
{
*(out + i) = (*(in + i) + 10 );
}
return 0;
}
Now when my DLLImport function calls the callinmydll function and passes valid data to it, this is where things get interesting. The pointer in contains data, as does firstcount. Although everything beyond this point is corrupted. Why? Curiously I rearrange my function to be double, double*, long, long now the corruption happens after third parameter. I'm curious as to what's going on, as I'm passing valid data; two valid pointers, and int cast to int64.
Help!
In Win32 C, a long is still 32-bits. Your C# signature is using a long which is 64-bits. Your second and forth parameter should be an int in the C# signature.
See this table for more information.
So your signature looks like so:
[DllImport("mydll.dll", EntryPoint = "callinmydll")]
public static extern int testdllcall(double *firstinput, int firstcount, double *secondoutput, int secondcount);
Additionally, make sure your calling convention is correct as Ramhound pointed out in the comments. Your C function looks like it is using the CDecl convention, and .NET defaults to StdCall. You can set the calling convention in the attribute:
[DllImport("mydll.dll", EntryPoint = "callinmydll", CallingConvention = CallingConvention.Cdecl)]
I'm converting a function from Visual Basic 6.0 as:
Declare Function RequestOperation Lib "archivedll" (ByVal dth As Long, ByVal searchRequestBuf As String, ByVal buflen As Long, ByVal FieldNum As Long, ByVal OP As Long, ByVal value As String) As Long
In C#, I'm declare the function as:
[DllImport("archivedll")]
public static extern int RequestOperation(int dth ,StringBuilder searchRequestBuf, int bufferLen, int fieldNum, int op, string value);
When call RequestOperation from C#, it throws an exception:
[System.AccessViolationException] =
{"Attempted to read or write protected
memory. This is often an indication
that other memory is corrupt."}
I have successful in calling many other functions like this, but only this function throws the exception.
This function is clearly not throwing an AccessViolationException - instead, it is generating an access violation fault by "attempting to read or write protected memory". .NET is translating that fault into an AccessViolationException.
You'll have to go figure out why it is "attempting to read or write protected memory". In particular, did you initialize the StringBuilder you are passing to it? Please post the code you use to call this method.
I think the StringBuilder in the function declaration has something to do with it. You should use just plain String instead.
/// Return Type: int
///dth: int
///searchRequestBuf: BSTR->OLECHAR*
///buflen: int
///FieldNum: int
///OP: int
///value: BSTR->OLECHAR*
[System.Runtime.InteropServices.DllImportAttribute("<Unknown>", EntryPoint="RequestOperation")]
public static extern int RequestOperation(int dth, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.BStr)] string searchRequestBuf, int buflen, int FieldNum, int OP, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.BStr)] string value) ;