|
Hi,
I want to use a simple function in a C++ DLL. The declaration in the header file for that function is:
#define API_SPEC __declspec(dllexport)
void API_SPEC API_getApiVersion (std::wstring *version)
That function receive a pointer to a UNICODE string and will put the release ID of that API in that string. (correct me if i'm wrong!)
I use VB.Net to call that DLL and read the content of that string. Here is my code:
Public Class Form1
<DllImport("C:\Api.dll", EntryPoint:="?API_getApiVersion@@YAXPAV?$basic_string@GU?$char_traits@G@std@@V?$allocator@G@2@@std@@@Z", CallingConvention:=CallingConvention.ThisCall, CharSet:=CharSet.Unicode)> _
Public Shared Sub test(ByVal version As String)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim theVersion As String = New String(" "c, 20)
Call test(theVersion )
MessageBox.Show(theVersion)
End Sub
End Class
When I run that code I get:"AccessViolationException was unhandled."
My question: what is wrong with my code? I even try to use a stringbuilder instead of string type but I got the same error.
Thanks for your help!
|
|
|
|
|
Hi,
I don't have the full answer, however here are some pointers:
1.
IMO for writable strings you must use a StringBuilder, give it sufficient capacity, pass it on, then convert it to a string using ToString()
2.
your entypoint seems very bizarre, and way too long. I suggest you check it, perhaps using DUMPBIN/EXPORT
3.
I have an unfinished article[^]dealing with P/Invoke, maybe that can help you.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. [The QA section does it automatically now, I hope we soon get it on regular forums as well]
|
|
|
|
|
Thanks Luc, I will have a look to your article.
I took the entrypoint name using DUMPBIN /EXPORT.
|
|
|
|
|
Following on from Luc's comment, your exported function name is in C++ format, it should be C. Change your header file to put extern "C" { in front of the definition and } after it, and you should get exported as written.
MVP 2010 - are they mad?
|
|
|
|
|
Richard, do you mean that because the DLL has been written in C++ it can't be exported to DotNet?
That I lost a week trying to find a solution when there is not that way?
Unfortunately, because the DLL is from a third party, I can't recompile to change the export statement.
|
|
|
|
|
TremaHD wrote: do you mean that because the DLL has been written in C++ it can't be exported to DotNet?
Not quite, it's just that the usual method is to export class/method names in clear "C" format rather than C++. I think you should still be able to use it, but you probably need the documentation that should come with it. If it's a third party DLL then you should approach the supplier/writer first.
[edit]Also I do not think that you can pass a .NET String reference as a std::wstring pointer.[/edit]
MVP 2010 - are they mad?
|
|
|
|
|
Thanks Richard and Luc for your replies, really appreciated!
Richard, for the string type I agree with you, that's why I tried a Stringbuilder type instead, but no more result.
About the documentation, I though that the header file of the API was enough (.h file). I also have a .lib file but I think that is for C++ so not useful for me with Dot Net. What else could have been useful?
|
|
|
|
|
Your original post shows the following extract from the header file:
#define API_SPEC __declspec(dllexport)
void API_SPEC API_getApiVersion (std::wstring *version)
Since you cannot directly convert a VB.NET String to a C++ std::wstring* I think the only solution is to write a C++ interface that can take a pointer to a StringBuilder object. This code could then call the API passing a pointer to a std::wstring which it will copy to the StringBuilder before returning to the VB.NET code.
Comments, Luc?
MVP 2010 - are they mad?
|
|
|
|
|
Hello all,
I think the solution here is to use the COM interoperability provided by dotNET. Note that I have not yet tried this myself, but I believe you have to use MarshallAs to ensure that VB.NET, in this case, converts the string before the call to a LPWSTR and after the call converts it back to a VB string.
Whether you have to use a stringbuilder or a string here I'm not sure, but my best guess is that you can use a normal string.
Here's a msdn link that might help, though it's not entirely clear to me:
http://msdn.microsoft.com/en-us/library/s9ts558h(VS.71).aspx
I'm not currently in a position to access VB.NET (or any other .NET language for that matter), so I'm sorry I can't give you the complete code.
Maybe one of the other repliers can help you out more.
Good Luck,
MicroVirus
|
|
|
|
|
The link provide good information and I tried but what I got is a Debug Assertion Failed box on the expression: _CrtIsValidHeapPointer(pUserData)
|
|
|
|
|
MicroVirus wrote: I think the solution here is to use the COM interoperability provided by dotNET.
He is already using that. The problem is that the C++ library function expects as input parameter a std::wstring* and I don't think there is any marshalling for such a class, so there has to be a user written intermediate function that accepts a char/byte[] and converts to and from the string class.MVP 2010 - are they mad?
|
|
|
|
|
Ouch right... I obviously overlooked that :S
In that case you're absolutely right and a wrapper needs to be written. Easiest way is to do that in (unmanaged) C++. I'd suggest taking the Windows API path on this one and write a function that takes a LPWSTR and size of the buffer, and returns the result of the function you are trying to access in this buffer.
|
|
|
|
|
As I recommended a few days ago!
<i>MVP 2010 - are they mad?</i>
|
|
|
|
|
AFAIK all extern 'C' does is tell the C++ compiler not to mangle the method names, which makes it easier to get the P/Invoke entrypoint right. Real C++ code can be P/Invoked without problems.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. [The QA section does it automatically now, I hope we soon get it on regular forums as well]
|
|
|
|
|
Any comments on my latest, above?
MVP 2010 - are they mad?
|
|
|
|
|
Hi Richard,
AFAIK and assuming std::wstring is just Unicode, the DllImport attribute needs a Charset.Unicode (or the Declare statement needs a Unicode keyword), as ANSI is the default marshaling for strings.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. [The QA section does it automatically now, I hope we soon get it on regular forums as well]
|
|
|
|
|
Hi Luc,
I haven't done a lot of work in this area but std::wstring is a C++ class that holds a Unicode string. I'm not sure whether the marshalling can handle it directly, so I may try some experiments to see what I can discover.
MVP 2010 - are they mad?
|
|
|
|
|
Hi guys,
After reading Luc's article (by the way, very interesting) and reading your posts, I tried to change the string type to Stringbuilder type. Because the C++ DLL sub has an OUT parameter, I modified the declaration to pass my variable byRef instead of ByVal:
<DllImport("C:\Api.dll", EntryPoint:="?API_getApiVersion@@YAXPAV?$basic_string@GU?$char_traits@G@std@@V?$allocator@G@2@@std@@@Z", CallingConvention:=CallingConvention.ThisCall, CharSet:=CharSet.Unicode)> _
Public Shared Sub test(ByRef version As StringBuilder)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim theVersion As StringBuilder = New Stringbuilder(20)
Call test(theVersion )
MessageBox.Show(theVersion.tostring)
End Sub
End Class
What is happening now is that when the line 'Call test(theVersion)' is executed, the application quit without any warning or error, even if I put a breakpoint at the 'Messagebox.show' line. Am I right to pass my variable byRef instead of byVal. Of course, I tried to change it to use byVal and what i get is an empty string in return.
When you look at the declaration line in the C++ code there is that #DECLARE API_SPEC line and that API_SPEC is also at the beginning of the method API_getApiVersion. Most of the time the DECLARE statement for __declspec(dllexport) use the C or C++ keyword to specify the language type. Here I have a keyword that is use at the beginning of the method definition. What is the purpose of that declare then?
Thanks again.
|
|
|
|
|
Hi,
yes ByRef seems right.
I'm still not happy with your entrypoint, "char_traits" and "allocator" don't belong there AFAIK.
I just now noticed ThisCall, which probably is wrong.
suggestion: if you have the source to the DLL, and can build it again, you could add a method that does what you need, with whatever API you choose. That one could have extern 'C' (hence no mangling), an explicit calling convention, and fill or return a regular string.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. [The QA section does it automatically now, I hope we soon get it on regular forums as well]
modified on Friday, February 5, 2010 9:08 AM
|
|
|
|
|
Following on from Luc's comments, this cannot work as it stands. The C++ method is expecting a reference to a C++ std::wstring class, and you cannot create one in VB. The only answer is to write a C++ wrapper to the DLL which will accept a reference to a character array and then use that intermediate code to convert between that and the wstring type.
MVP 2010 - are they mad?
|
|
|
|
|
Thanks gentlemen for your inputs. I will remove the dust on my old C++ book and go with the wrapper.
|
|
|
|
|
We have a legacy system (in C++/MFC/C# .NET), that talks to SQL server database.
At that time, the Db was not designed for internationalization. Hence the string fields in Db are of type varchar.
One option is to change the varchars to nvarchars. This increases the Db size by 50%.
The existing users of the app would not want to have this, with no tangible benefit.
But it is essential for us to support internationalization.
Is it possible to have 2 sets of Db, one for existing customers and another for new customers (with nvarchar)
with the same code base, or some minor modifications to the code?
Open to ideas/suggestions/comments or questions.
Thanks,
Saleem
|
|
|
|
|
Md Saleem Navalur wrote: One option is to change the varchars to nvarchars. This increases the Db size by 50%.
The existing users of the app would not want to have this, with no tangible benefit.
What difference would it make to the users? Do they specify a limit to the size of the database or are there other limiting factors?
In any case the tangible benefit is that it can now support internationalisation.
Md Saleem Navalur wrote: Is it possible to have 2 sets of Db, one for existing customers and another for new customers (with nvarchar)
with the same code base, or some minor modifications to the code?
At the very least it would be pretty difficult to maintain with 2 'almost' identical databases.
|
|
|
|
|
Yes the size of the Db matters, its already close to 1 TB. Making nvarchars would increase this substantially. Existing users dont really care about localization as they are always in en-US culture.
It is for the new users, we have an issue.
Saleem
|
|
|
|
|
Md Saleem Navalur wrote: s it possible to have 2 sets of Db, one for existing customers and another for new customers (with nvarchar)
with the same code base, or some minor modifications to the code?
Your data access layer should be flexible enough to support this. If it's V1.0, use database A, if V2.0, use database B.
I know the language. I've read a book. - _Madmatt
|
|
|
|