I am working with a DLL written in C++ provided by an external source with documentation but no source code.
I need to instantiate a callback function in VB.NET that is to be called by the C++ module in certain cases. The documentation provided looks like;
Function I call to establish the call back;
extern "C" int EDIT_API Do_RunStuff(
const int MyID,
const char* my_tag1,
const char* my_tag2,
const char* some_data,
const int some_options,
int* some_count,
void* owner,
void* callback_func);
The template for the call back function is;
typedef void (*CallbackRunStuff)( void* ptr2object, const char* my_tag1,
const char* my_name, const char* some_code,
const char* error_type, const char* message );
So I created my VB.NET code;
Declare the function;
Declare Function Do_RunStuff Lib "SOME.DLL" (ByVal MyID As Integer, _
ByVal my_tag1 As String, _
ByVal my_tag2 As String, _
ByVal some_data As String, _
ByVal some_options As Integer, _
ByRef some_count As Integer, _
ByRef owner As IntPtr, _
ByRef callback_func As IntPtr) As Integer
Call the function;
Dim objEditsErrorHandler As New editsErrorHandlerDelegate(AddressOf editsErrorHandler)
Dim objThis As GCHandle = GCHandle.Alloc(Me)
Dim ptrEditsErrorHandler As IntPtr = Marshal.GetFunctionPointerForDelegate(objEditsErrorHandler)
Dim ptrThis As IntPtr = GCHandle.ToIntPtr(objThis)
intResult = Do_RunStuff(_AnID, strTag1, strTag2, strSomeData, someValue, SomeCount, ptrThis, ptrEditsErrorHandler)
Create delegate;
Public Delegate Sub editsErrorHandlerDelegate(ByRef objThis As IntPtr, ByVal strEditTag As String, ByVal strEditName As String, ByVal strAdminCode As String, ByVal strErrorType As String, ByVal strMessage As String)
Create Function;
Public Sub editsErrorHandler(ByRef objThis As IntPtr, ByVal strEditTag As String, ByVal strEditName As String, ByVal strAdminCode As String, ByVal strErrorType As String, ByVal strMessage As String)
Debug.Print("*****====================*****")
Debug.Print("Error Type: " & strErrorType)
Debug.Print("Message: " & strMessage)
End Sub
Everything works swimmingly, when no callback is needed. When it attempts to do a call back to editsErrorHandler the program aborts with;
An unhandled exception of type 'System.AccessViolationException' occurred in
I do not know where to go from here. The DLL's developer is a C++ person and does not have an answer.
Any Ideas?
EDIT 1
In the documentation they show how to call from C++
Call to module that may do call back;
FMyEngineClass->Do_RunStuff(my_tag1, my_tag2, some_data,
some_options, &some_count, this, ShowErrorMessages);
Definition of function that is called back;
static void ShowErrorMessages(void* caller, const char* my_tag1,
const char* some_name, const char* some_code,
const char* error_type, const char* message)
Well if anyone else finds this, my particular problem was one silly line;
<UnmanagedFunctionPointer(CallingConvention.Cdecl)>
That needs to go immediately before;
Public Delegate Sub editsErrorHandlerDelegate(ByRef objThis As IntPtr, ByVal strEditTag As String, ByVal strEditName As String, ByVal strAdminCode As String, ByVal strErrorType As String, ByVal strMessage As String)
like so;
<UnmanagedFunctionPointer(CallingConvention.Cdecl)>
Public Delegate Sub editsErrorHandlerDelegate(ByRef objThis As IntPtr, ByVal strEditTag As String, ByVal strEditName As String, ByVal strAdminCode As String, ByVal strErrorType As String, ByVal strMessage As String)
You don't need to explicitly use GCHandle to pass function pointers, and I seriously doubt you got the API declaration right (you seem to be getting away with it because you don't seem to use the parameters you got wrong).
I believe the following is correct:
<UnmanagedFunctionPointer(CallingConvention.Cdecl)> _
Public Delegate Sub editsErrorHandlerDelegate(ByVal objThis As IntPtr, <[In]()> ByVal strEditTag As String, <[In]()> ByVal strEditName As String, <[In]()> ByVal strAdminCode As String, <[In]()> ByVal strErrorType As String, <[In]()> ByVal strMessage As String)
Declare Function Do_RunStuff Lib "SOME.DLL" (ByVal MyID As Integer, _
ByVal my_tag1 As String, _
ByVal my_tag2 As String, _
ByVal some_data As String, _
ByVal some_options As Integer, _
ByRef some_count As Integer, _
ByVal owner As WhateverClassThisResidesIn, _
ByVal callback_func As editsErrorHandlerDelegate) As Integer
Public Sub editsErrorHandler(ByVal objThis As IntPtr, ByVal strEditTag As String, ByVal strEditName As String, ByVal strAdminCode As String, ByVal strErrorType As String, ByVal strMessage As String)
Debug.Print("*****====================*****")
Debug.Print("Error Type: " & strErrorType)
Debug.Print("Message: " & strMessage)
End Sub
Do_RunStuff(_AnID, strTag1, strTag2, strSomeData, someValue, someCount, Me, AddressOf editsErrorHandler)
If WhateverClassThisResidesIn is a structure, that will need to be ByRef owner As ThatStructure.
Related
Is there a way to query windows how much time is left until it goes to suspend/sleep mode?
I am using vbscript and suspect there might be a WMI answer, but any language like c/c++, *.NET, or even java, if possible, may fit my needs.
EDIT
I want to be able to query Windows with a method, not to be alerted by events when it is about to suspend.
There's no API to know how much time is left since Windows will try to complete entering S3 (Sleep) or S4 (Hibernate) as quick as possible.
Windows will send a notification to all processes about the pending power state change and allow applications to prepare for that event.
You can find most of what you need here.
Basically you have 20 seconds to process the first message. Your process can delay replying to the message, taking care of all the various power cycle tasks, e.g. close files, save your state, etc.
You might want to call CallNtPowerInformation API function that takes the following params:
NTSTATUS WINAPI CallNtPowerInformation(
_In_ POWER_INFORMATION_LEVEL InformationLevel,
_In_ PVOID lpInputBuffer,
_In_ ULONG nInputBufferSize,
_Out_ PVOID lpOutputBuffer,
_In_ ULONG nOutputBufferSize
);
In the InformationLevel paramer you pass the SystemPowerInformation enum value that fills the lpOutputBuffer with a SYSTEM_POWER_INFORMATION structure:
typedef struct _SYSTEM_POWER_INFORMATION {
ULONG MaxIdlenessAllowed;
ULONG Idleness;
ULONG TimeRemaining;
UCHAR CoolingMode;
} SYSTEM_POWER_INFORMATION, *PSYSTEM_POWER_INFORMATION;
Then get the TimeRemaining, which is expressed in seconds.
edit: tested in VB.NET.
edit: code added.
Queryer.vb
Imports System.Runtime.InteropServices
Public Class Queryer
Const SystemPowerInformation As Integer = 12
Const STATUS_SUCCESS As Integer = 0
Private Structure SYSTEM_POWER_INFORMATION
Public MaxIdlenessAllowed As UInteger
Public Idleness As UInteger
Public TimeRemaining As Integer
Public CoolingMode As Byte
End Structure
<DllImport("PowrProf.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Shared Function CallNtPowerInformation(
ByVal InformationLevel As Int32,
ByVal lpInputBuffer As IntPtr,
ByVal nInputBufferSize As UInt32,
ByVal lpOutputBuffer As IntPtr,
ByRef nOutputBufferSize As UInt32) As UInt32
End Function
Public Function Query() As Integer
Dim PowerInformation As SYSTEM_POWER_INFORMATION
Dim Status As IntPtr = IntPtr.Zero
Dim ReturnValue As UInteger
Try
Status = Marshal.AllocCoTaskMem(Marshal.SizeOf(GetType(SYSTEM_POWER_INFORMATION)))
ReturnValue = CallNtPowerInformation(SystemPowerInformation, Nothing, 0, Status, Marshal.SizeOf(GetType(SYSTEM_POWER_INFORMATION)))
PowerInformation = Marshal.PtrToStructure(Status, GetType(SYSTEM_POWER_INFORMATION))
Catch ex As Exception
Return 0
Finally
Marshal.FreeCoTaskMem(Status)
End Try
Return PowerInformation.TimeRemaining
End Function
End Class
Form:
Public Class Form1
Public Sub Loader() Handles Me.Load
Dim rolex As New Timer()
rolex.Interval = 500
AddHandler rolex.Tick, AddressOf TimerScattato
rolex.Start()
End Sub
Private Sub TimerScattato()
Dim secondi As Integer = q.Query()
Dim iSpan As TimeSpan = TimeSpan.FromSeconds(secondi)
lblTimeLeft.Text = String.Format("{0,2}:{1,2}:{2,2}", iSpan.Hours, iSpan.Minutes, iSpan.Seconds)
End Sub
Private q As New Queryer
End Class
VB.NET
<DllImport("MyCtesting.dll")> _
Private Shared Sub test(ByRef _IMSS_TEXT As String)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
test(Label1.Text)
End Sub
C++
using boost::lexical_cast;
using boost::bad_lexical_cast;
extern "C" __declspec(dllexport) void test(std::string &name)
{
for (int i=0l;i<10;i++)
{
name = lexical_cast<string>(i);
}
}
Why this is throwing error
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Thank in advance.
Today I tried to make my first DLL and my first application which would use a DLL.
The DLL is made in C++ and this is the code I'm calling:
void Graph::findPath_r(Node* pStart, Node* pEnd, std::vector<cell> &road, cell &costMx)
{
//.....
if(pEnd->getParent() != NULL)
{
while(!path.empty())
{
road.push_back(path.top()->getName());
costMx += path.top()->getGCost();
path.pop();
}
return;
}
return;
}
vector <int>tbcway;
int FUNCTION CalculatePath(int Start, int End, int * Array, int &cost)
{
dgraph->findPath_r(xNode[Start].NodeID ,xNode[End].NodeID,tbcway,cost);
dgraph->reset();
std::copy(tbcway.begin(), tbcway.end(), Array);
tbcway.clear();
return 1;
}
and this is how I declared it in VB.net and called it:
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("RCP.dll")> _
Public Shared Function LOAD_SYSTEM() As Boolean
End Function
<DllImport("RCP.dll")> _
Public Shared Function GetPluginVersion() As Integer
End Function
<DllImport("RCP.dll")> _
Public Shared Function CalculatePath(ByVal StartNode As Integer, ByVal EndNode As Integer, ByRef Array() As Array, ByRef cost As Integer) As Integer
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LOAD_SYSTEM()
MsgBox(GetPluginVersion().ToString())
Dim path(4096) As Array
Dim movecost As Integer
CalculatePath(1702, 27932, path, movecost)
End Sub
End Class
So, what is wrong with this code?
The error I am getting is:
A call to PInvoke function 'RCP GUI!RCP_GUI.Form1::CalculatePath' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
This is probably a calling convention mismatch.
Try decorating your DllImport with different calling conventions to see which works (my guess is that it should be cdecl).
I'm really struggling with the following pinvoke call. I've tried numerous different ways of doing this but still no joy.
The call runs through, but I get a 'Bad parameter' message back with suggests there is something wrong with the struct, since I've ran through a c++ example of this code and the parameters are all correct.
I'd be so grateful for any help, I've googled myself mad already!
in the c++ header file:
int __stdcall DVSNET_OpenChannel(HANDLE hServer,unsigned long nChannel,DVSNET_CHANNEL_INFO *pChannelInfo,HANDLE *phChannel);
typedef struct tagDVSNET_CHANNEL_INFO
{
unsigned long lStructSize;
unsigned long dwStreamNo;
unsigned long nProtocol;
HWND hWndDisplay;
unsigned long bPlayStart;
unsigned long dwBackFrameCount;
unsigned long dwFlag;
} DVSNET_CHANNEL_INFO;
My definitions for the import:
<DllImport("DVSNETClient.dll")> _
Public Shared Function DVSNET_OpenChannel(ByVal hServer As System.IntPtr, ByVal nChannel As UInteger, ByRef pChannelInfo As IntPtr, ByRef phChannel As IntPtr) As Integer
End Function
<StructLayout(LayoutKind.Sequential)> _
Public Structure tagDVSNET_CHANNEL_INFO
Public lStructSize As UInteger
Public dwStreamNo As UInteger
Public nProtocol As UInteger
Public hWndDisplay As IntPtr
Public bPlayStart As UInteger
Public dwBackFrameCount As UInteger
Public dwFlag As UInteger
End Structure
My Calling code:
Private Sub OpenChannel()
Dim intRet As Integer
Dim ChannelInfo As New tagDVSNET_CHANNEL_INFO
Dim HWD As New System.IntPtr
ChannelInfo.lStructSize = System.Runtime.InteropServices.Marshal.SizeOf(ChannelInfo)
ChannelInfo.nProtocol = 0
ChannelInfo.dwStreamNo = 0
ChannelInfo.dwBackFrameCount = 10
ChannelInfo.hWndDisplay = HWD
ChannelInfo.bPlayStart = 0 ' dont display
'Channelinfo.dwFlag =
' Initialize unmanged memory to hold the struct.
Dim ptr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(ChannelInfo))
System.Runtime.InteropServices.Marshal.StructureToPtr(ChannelInfo, ptr, True)
Dim nChannel As UInteger = 1
intRet = TotemPoleLib.DVSNET_OpenChannel(hServer, nChannel, ptr, hChannel)
'... snip ...
End Sub
Many thanks in advance!
I don't know VB.Net syntax, but if you understand C# then here are the proper P/Invoke declarations for DVSNET_CHANNEL_INFO and DVSNET_OpenChannel:
[StructLayout(LayoutKind.Sequential)]
struct DVSNET_CHANNEL_INFO
{
uint lStructSize;
uint dwStreamNo;
uint nProtocol;
IntPtr hWndDisplay;
uint bPlayStart;
uint dwBackFrameCount;
uint dwFlag;
}
static class DVSNETClient
{
[DllImport("DVSNETClient.dll")]
public static extern int DVSNET_OpenChannel(
IntPtr hServer,
uint nChannel,
ref DVSNET_CHANNEL_INFO pChannelInfo,
ref IntPtr phChannel
);
}
Your DVSNET_CHANNEL_INFO appears fine, but the 3rd argument of DVSNET_OpenChannel should simply be a ref DVSNET_CHANNEL_INFO; all the marshaling is done for you automatically, no real need for the Marshal class here. The only thing you need to do is initialize ChannelInfo.lStructSize to the value of Marshal.SizeOf(typeof(DVSNET_CHANNEL_INFO)) before calling DVSNET_OpenChannel. It would probably be sensible to do this in a non-default constructor for DVSNET_CHANNEL_INFO, but given that it's a struct, there is of course no way to enforce use of that constructor over the implicit default constructor.
Note that semantically it's possible that pChannelInfo and/or phChannel should be out rather than ref, but it's not possible to tell by the signature of DVSNET_OpenChannel. In any case, using ref will work regardless, though it will be less efficient than using out if out is indeed warranted.
Is it possible to make this work?
template<class T>
fun(T * t) { t->someMemberFunc(); }
... somewhere in the code:
ManagedType ^ managedP = gcnew ManagedType();
UnmanagedType * unmanagedP = new UnmanagedType();
fun(managedP);
You can't take the address of a managed object. The garbage collector can move it around in memory, invalidating the pointer value at any time. At a minimum you would have to pin the object first. Short from me not being able to come up with a valid syntax, pinning just to make a method call cannot be desirable. You'll need to declare the argument as a tracking handle:
template<typename T>
void fun(T^ t) { t->method(); }
This may be unrelated to your question, but GCHandle and gcroot may be useful; they allow you to get an unmanaged object encapsulating the handle to a managed object inside:
// hold_object_reference.cpp
// compile with: /clr
#include "gcroot.h"
using namespace System;
#pragma managed
class StringWrapper {
private:
gcroot<String ^ > x;
public:
StringWrapper() {
String ^ str = gcnew String("ManagedString");
x = str;
}
void PrintString() {
String ^ targetStr = x;
Console::WriteLine("StringWrapper::x == {0}", targetStr);
}
};
#pragma unmanaged
int main() {
StringWrapper s;
s.PrintString();
}
-----------------
Imports System
Imports System.IO
Imports System.Threading
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports System.Security.Permissions
Public Delegate Function CallBack(ByVal handle As Integer, ByVal param As IntPtr) As Boolean
Module LibWrap
' passing managed object as LPARAM
' BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
<DllImport("user32.dll")> _
Function EnumWindows(ByVal cb As CallBack, ByVal param As IntPtr) As Boolean
End Function
End Module 'LibWrap
Module App
Sub Main()
Run()
End Sub
<SecurityPermission(SecurityAction.Demand, UnmanagedCode:=true)> _
Sub Run()
Dim tw As TextWriter = System.Console.Out
Dim gch As GCHandle = GCHandle.Alloc(tw)
Dim cewp As CallBack
cewp = AddressOf CaptureEnumWindowsProc
' platform invoke will prevent delegate to be garbage collected
' before call ends
LibWrap.EnumWindows(cewp, GCHandle.ToIntPtr(gch))
gch.Free()
End Sub
Function CaptureEnumWindowsProc(ByVal handle As Integer, ByVal param As IntPtr) As Boolean
Dim gch As GCHandle = GCHandle.FromIntPtr(param)
Dim tw As TextWriter = CType(gch.Target, TextWriter)
tw.WriteLine(handle)
Return True
End Function
End Module