By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
431,966 Members | 2,054 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 431,966 IT Pros & Developers. It's quick & easy.

Object reference not set to an instance of an object error during netapi32 call

P: n/a
Hi,

I am trying to get/set Terminal server information in the active directory
on a windows 2000 domain. Since the ADSI calls for TS don't work until W2K3,
I need to do it myself.

I'm fairly new to VB.NET, so I need some help.

Here is a code snippit :

Private Function GetDsDCName(ByVal Domainname As String) As String
Dim pInfo As IntPtr
Dim Computername As String = SystemInformation.ComputerName
Dim DomainGuid As guid
Dim ClientSiteName As String = ""
Dim Info As New PDOMAIN_CONTROLLER_INFO
Dim m_LastError As Long
If Exported("netapi32", "DSGetDCNameA") Then
m_LastError = DsGetDcNameA(Computername, Domainname, DomainGuid,
ClientSiteName, DS_DIRECTORY_SERVICE_REQUIRED, Info)
If m_LastError = NO_ERROR Then
Domainname = Info.DomainControllerName
NetApiBufferFreeIntPtr(pInfo)
End If
Else
MsgBox("DSGetDCName is not supported")
End If
End Function

The declare function for DSGetDCNameA is :

Declare Function DsGetDcNameA Lib "netapi32.dll" Alias "DsGetDcNameA" _
(ByVal ComputerName As String, ByVal DomainName As String, ByVal DomainGuid
As Guid, _
ByVal SiteName As String, ByVal Flags As Long, ByRef DomainControllerInfo As
PDOMAIN_CONTROLLER_INFO) As Long

Private Declare Function NetApiBufferFree& Lib "netapi32" _
(ByVal Buffer As Long)

And PDOMAIN_CONTROLLER INFO is a structure, created like this :

Structure PDOMAIN_CONTROLLER_INFO
Public DomainControllerName As String
Public DomainControllerAddress As String
Public DomainControllerAddressType As Long
Public DomainGuid As Guid
Public DomainName As String
Public DnsForestName As String
Public Flags As Long
Public DcSiteName As String
Public ClientSiteName As String
End Structure

The problem I'm having is that during the call
m_LastError = DsGetDcNameA(Computername, Domainname, DomainGuid,
ClientSiteName, DS_DIRECTORY_SERVICE_REQUIRED, Info)
I get the error message mentioned in the subject.
Can anybody point me to the problem? I tried just about anything I can think
of, to no avail... :( If I get this error correctly, it would suggest that
I didn't instantiate something, but I wouldn't know what. I have declared
all vars before using them to call the function with, as far as I know, I
don't need to create an instance of the function to be able to call it. Or
do I? If so, how do I do that?

Any help is appreciated.

Kurt.
Nov 20 '05 #1
Share this Question
Share on Google+
5 Replies


P: n/a
On 2003-11-16, Kurt Van Campenhout <ma*******@networksolvers.com> wrote:

Some stuff in line:
Hi,

I am trying to get/set Terminal server information in the active directory
on a windows 2000 domain. Since the ADSI calls for TS don't work until W2K3,
I need to do it myself.

I'm fairly new to VB.NET, so I need some help.

Here is a code snippit :

Private Function GetDsDCName(ByVal Domainname As String) As String
Dim pInfo As IntPtr
Dim Computername As String = SystemInformation.ComputerName
Dim DomainGuid As guid
Dim ClientSiteName As String = ""
Dim Info As New PDOMAIN_CONTROLLER_INFO
Dim m_LastError As Long
If Exported("netapi32", "DSGetDCNameA") Then
m_LastError = DsGetDcNameA(Computername, Domainname, DomainGuid,
ClientSiteName, DS_DIRECTORY_SERVICE_REQUIRED, Info)
If m_LastError = NO_ERROR Then
Domainname = Info.DomainControllerName
NetApiBufferFreeIntPtr(pInfo)
End If
Else
MsgBox("DSGetDCName is not supported")
End If
End Function

Private Function GetDsDCName (ByVal DomainName As String) As String
Dim pInfo As IntPtr
Dim ComputerName As String = SystemInformation.ComputerName
Dim DomainGuid As Guid
Dim ClientName As String = String.Empty
Dim Info As DOMAIN_CONTROLLER_INFO
Dim Status As Integer

Try

Status = DsGetName( _
ComputerName, _
DomainName, _
DomainGuid, _
ClientSiteName, _
DS_DIRECTORY_SERVICE_REQUIRED, _
pInfo)
Catch
MessageBox.Show("DSGetDCName is not supported")
Return String.Empty
End Try

If Status = NO_ERROR Then
' Get the data pointed to
Info = CType( _
Marshal.PtrToStructure( _
pInfo,
GetType(DOMAIN_CONTROLLER_INFO)), _
DOMAIN_CONTROLLER_INFO)
' Free the pointer
NetApiBufferFree(pInfo)

' Return the data...
Return Info.DomainControllerName
End If

End Function
The declare function for DSGetDCNameA is :

Declare Function DsGetDcNameA Lib "netapi32.dll" Alias "DsGetDcNameA" _
(ByVal ComputerName As String, ByVal DomainName As String, ByVal DomainGuid
As Guid, _
ByVal SiteName As String, ByVal Flags As Long, ByRef DomainControllerInfo As
PDOMAIN_CONTROLLER_INFO) As Long
Declare Auto Function DsGetDcName Lib "netapi32" _
(ByVal ComputerName As String, _
ByVal DomainName As String, _
ByRef DomainGuid As Guid, _
ByVal SiteName As String, _
ByVal Flags As Integer,
ByRef DomainControllerInfo As IntPtr) As Integer
Private Declare Function NetApiBufferFree& Lib "netapi32" _
(ByVal Buffer As Long)

Private Declare NetApiBufferFree Lib "netapi32" _
(ByVal Buffer As IntPtr) As Integer
And PDOMAIN_CONTROLLER INFO is a structure, created like this :

Structure PDOMAIN_CONTROLLER_INFO
Public DomainControllerName As String
Public DomainControllerAddress As String
Public DomainControllerAddressType As Long
Public DomainGuid As Guid
Public DomainName As String
Public DnsForestName As String
Public Flags As Long
Public DcSiteName As String
Public ClientSiteName As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Structure DOMAIN_CONTROLLER_INFO
Public DomainControllerName As String
Public DomainControllerAddress As String
Public DomainControllerAddressType As Integer
Public DomainGuid As Guid
Public DomainName As String
Public DomainForestName As String
Public Flags As Integer
Public DcSiteName As String
Public ClientSiteName As String
End Sturcture
The problem I'm having is that during the call
m_LastError = DsGetDcNameA(Computername, Domainname, DomainGuid,
ClientSiteName, DS_DIRECTORY_SERVICE_REQUIRED, Info)
I get the error message mentioned in the subject.
Can anybody point me to the problem? I tried just about anything I can think
of, to no avail... :( If I get this error correctly, it would suggest that
I didn't instantiate something, but I wouldn't know what. I have declared
all vars before using them to call the function with, as far as I know, I
don't need to create an instance of the function to be able to call it. Or
do I? If so, how do I do that?

Any help is appreciated.

Kurt.


Kurt,

I've inserted some stuff in line, but it is a little out of order. I
can't guarentee that the above is 100% correct since it is air code, and
since I'm booted into Linux right now - I can't verify it :). But,
there are a couple of things I think I should point out...

1. VB.NET Long = 64-Bit, WIN32 int/long = 32-Bit. When doing P/Invoke
from VB.NET you should use the Integer or Int32 type since they are
32-bits.

2. When dealing with WIN32 calls that take strings as parameters - you
should try to use the Auto modifier rather then aliasing to either the A
or W function. The main reason is that .NET strings are already
Unicode, and if your on an NT system it is much more effiecient to call
the W functions since you avoid the
Unicode->Ansi->Unicode->Ansi->Unicode conversion that takes place when
you call the A function. Further, if the parameter is an out parameter,
you should not use System.String - you should replace it with
System.Text.StringBuilder

The error you are getting most likely is the Guid paramter. It is a
structure, and you are attempting to pass it ByVal to a parameter that
is expecting a pointer to a Guid. Anyway, if you have any more
trouble/questions - just post a follow-up. Well get you taken care of
:)

--
Tom Shelton
MVP [Visual Basic]
Nov 20 '05 #2

P: n/a
Hi Tom,

I'll try that. Thanks for the tips. I found this nice article (KB292631) on
the MS KB, but it doesn't work in .NET. It was my starting point though. It
would be nice if MS changed those examples (or added onto them) for VB.NET.
At least there's still some good people on usenet :)

Kurt.
Nov 20 '05 #3

P: n/a
Hi,

With the help of Tom, I got it to work, that is... The snippit of code that
I had in my previous post.

I wonder if you could help me out again, Tom, since I'm confused about the
use of pointers in VB.NET. I am used to pointers in Pascal, but the pointer
philosophy in VB.NET kinda passes me by.. :(

The function declaration that you gave me for the DSGetDCName api call was
the following :

Declare Auto function DsGetDCName Lib "netapi32" _
(Byval Computername as string, _
Byval Domainname as string,_
ByRef Domainguid as guid,_
ByVal Sitename as String,_
ByVal Flags as Integer,
Byref DomainControllerInfo as IntPtr) as Integer

The API Call is defined in the SDK as follows :
DWORD DsGetDcName(
LPCTSTR ComputerName,
LPCTSTR DomainName,
GUID* DomainGuid,
LPCTSTR SiteName,
ULONG Flags,
PDOMAIN_CONTROLLER_INFO* DomainControllerInfo
);

What I know about C notation, indeed GUID* and PDOMAIN_CONTROLLER_INFO* mean
pointers to the structure. Therefore, Byref.

But, according to the docs in the SDK, computername is "[in] Pointer to a
null-terminated string that specifies the name of the server to process this
function. Typically, this parameter is NULL, that indicates the local
computer is used. Windows NT 4.0 server does not support remote calling of
this function, so this computer name cannot be a Windows NT 4.0 server
unless it is the local computer."

So, this seems to be a pointer as well... Yet, Byval, and in the calling of
the function, we use a parameter which is Dimmed as String.

This confuses me already... I would've expected that to be passed ByRef as
well.Or, is it like in Pascal, that strings are actually pointers to a
memory structure, which would mean that what you are actually shoveling to
the function, IS a pointer. I guess that is the case then?

With that in mind, I'm trying to convert another function call, namely
WTSQueryUserConfig, which is defined in the SDK as :

BOOL WTSQueryUserConfig(
LPTSTR pServerName,
LPTSTR pUserName,
WTS_CONFIG_CLASS WTSConfigClass,
LPTSTR* ppBuffer,
DWORD* pBytesReturned
);

so, I would do it the following way :

Private declare auto function WTSQueryUserConfig "wtsapi32.dll"_
(Byval pServerName as String, ByVal pUsername as String, _
ByVal WTS_Config_Class as Integer, ByRef ppBuffer as String, Byref
pBytesReturned as Intptr) as Boolean

where
ppBuffer
[out] Pointer to a variable that receives a pointer to the requested
information. The format and contents of the data depend on the information
class specified in the WTSConfigClass parameter. To free the returned
buffer, call the WTSFreeMemory function.
pBytesReturned
[out] Pointer to a variable that receives the size, in bytes, of the data
returned in ppBuffer.
But, how do I convert the pBytesReturned to an Integer again? I need to copy
pBytesReturned bytes in ppBuffer to another string, and return that info to
the calling procedure, since I need to free the memory afterwards by calling
WTSFreeMemory (according to the SDK). If I free the memory before I copy it,
the string would be gone.

I was thinking of this :

Public Function GetTSProfilePath(ByVal Domainname As String, ByVal
TargetUserobject As String, ByRef TSProfilePath As String) As Boolean
Dim Retval As Integer
Dim Howmany As IntPtr
Dim Buffer As String = String.Empty 'Empty string to hold the info
Retval = WTSQueryUserConfig(GetDsDCName(Domainname), TargetUserobject,
WTSUserConfigTerminalServerProfilePath, Buffer, Howmany)
If Retval <> 0 Then
TSProfilePath = Buffer 'Since it is already a string, I guess this
would be possible.
WTSFreeMemory(Buffer)
GetTSProfilePath = True
Else
GetTSProfilePath = False
End If
End Function

And again, I get the same error !!! This is driving me crazy... I was pretty
good at pascal programming, even pointers, but .NET is driving me crazy :~
btw, I'm getting the error at the function call (retval = WTSQuer...)

But I wanna learn, and I can't find all the info I need in the MSDN
library... Got any good books about this???

So, Tom, if you want, can you help me again? You don't need to give me the
code, but explain to me WHY it doesn't work. The hints you gave me last time
have made me see a couple of other things in my code that were wrong. (of
course, if you wanna offer the code, who am I to say no :)

Thanks again,

Kurt.
Nov 20 '05 #4

P: n/a
On 2003-11-17, Kurt Van Campenhout <ma*******@networksolvers.com> wrote:
Hi,

With the help of Tom, I got it to work, that is... The snippit of code that
I had in my previous post.

I'm glad you got it to work :) I would have given you tested code - but
I don't normally boot into windows at home...
I wonder if you could help me out again, Tom, since I'm confused about the
use of pointers in VB.NET. I am used to pointers in Pascal, but the pointer
philosophy in VB.NET kinda passes me by.. :(

I'm glad that you're used to them. Once you get past the surface
details, you'll find that the concept is pretty much the same (at least
as in C, I don't know pascal :)
The function declaration that you gave me for the DSGetDCName api call was
the following :

Declare Auto function DsGetDCName Lib "netapi32" _
(Byval Computername as string, _
Byval Domainname as string,_
ByRef Domainguid as guid,_
ByVal Sitename as String,_
ByVal Flags as Integer,
Byref DomainControllerInfo as IntPtr) as Integer

The API Call is defined in the SDK as follows :
DWORD DsGetDcName(
LPCTSTR ComputerName,
LPCTSTR DomainName,
GUID* DomainGuid,
LPCTSTR SiteName,
ULONG Flags,
PDOMAIN_CONTROLLER_INFO* DomainControllerInfo
);

What I know about C notation, indeed GUID* and PDOMAIN_CONTROLLER_INFO* mean
pointers to the structure. Therefore, Byref.

That's right. I changed the last argment to be an IntPtr though,
because this structure was allocated by the API call and needed to be
released by NetApiBufferFree - hence the ByRef DomainController As
IntPtr and a call to Marshal.PtrToStructure to retrieve the actual
information.
But, according to the docs in the SDK, computername is "[in] Pointer to a
null-terminated string that specifies the name of the server to process this
function. Typically, this parameter is NULL, that indicates the local
computer is used. Windows NT 4.0 server does not support remote calling of
this function, so this computer name cannot be a Windows NT 4.0 server
unless it is the local computer."

So, this seems to be a pointer as well... Yet, Byval, and in the calling of
the function, we use a parameter which is Dimmed as String.

Yep. It is a pointer to a constant string of characters. If it was a
buffer to be changed I would have passed System.Text.StringBuilder.
Much less overhead for the Marshaller because of the immutability of
strings in .NET. In fact, in C# you have to pass it as
system.text.stringbuilder or you won't get a return value.
This confuses me already... I would've expected that to be passed ByRef as
well.Or, is it like in Pascal, that strings are actually pointers to a
memory structure, which would mean that what you are actually shoveling to
the function, IS a pointer. I guess that is the case then?

Yep. Strings are objects. The variable is really just a reference to
the acutall character buffer. The Auto keyword simply tells the runtime
to pick the appropriate function for the platform. In other words, use
the W (wide character) version on NT based systems, and the A (Ansi)
version of the function on Win9x. This is good because you can avoid
the unnecessary character conversions that have been the norm for VB.
On NT systems, if you call the A version the runtime converts your
Unicode string to Ansi and calls the Api function. The Api function
then converts your string bace to Unicode and calls the W version of the
function. Once the W version returns, the Ansi version converts the
string backs to Ansi and returns it to the runtime marshaller that then
converts it back to Unicode... So thats:

Unicode -> Ansi -> Unicode -> Ansi -> Unicode

That's a lot of extra work :)
With that in mind, I'm trying to convert another function call, namely
WTSQueryUserConfig, which is defined in the SDK as :

BOOL WTSQueryUserConfig(
LPTSTR pServerName,
LPTSTR pUserName,
WTS_CONFIG_CLASS WTSConfigClass,
LPTSTR* ppBuffer,
DWORD* pBytesReturned
);

so, I would do it the following way :

Private declare auto function WTSQueryUserConfig "wtsapi32.dll"_
(Byval pServerName as String, ByVal pUsername as String, _
ByVal WTS_Config_Class as Integer, ByRef ppBuffer as String, Byref
pBytesReturned as Intptr) as Boolean

where
ppBuffer
[out] Pointer to a variable that receives a pointer to the requested
information. The format and contents of the data depend on the information
class specified in the WTSConfigClass parameter. To free the returned
buffer, call the WTSFreeMemory function.
pBytesReturned
[out] Pointer to a variable that receives the size, in bytes, of the data
returned in ppBuffer.

Actually, in this case since you need to call WTSFreeMemory to free the
string buffer I would declare it like this:

Private Declare Auto Function WTSQueryUserConfig "wtsapi32" _
(ByVal pServerName As String, _
ByVal pUserName As String, _
ByVal WTSConfigClass As Integer, _
ByRef ppBuffer As IntPtr, _
ByRef pBytesReturned As Integer) As Boolean
Private Declare Sub WTSFreeMemory "wtsapi32" (ByVal pMemory As IntPtr)

Then call it like

Dim ppBuffer As IntPtr
Dim pBytesReturned As Integer
Dim Buffer As String

If (WTSQueryUserConfig( _
"MyServer", _
"MyUser", _
TheConfigClass, _
ppBuffer, _
pBytesReturned)) Then

Buffer = Marshal.PtrToStringAuto(ppBuffer)
WTSFreeMemory(ppBuffer)
Return Buffer
Else
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If

Again, that is untested - but from the docs this would be my first try
anyway :)

But, how do I convert the pBytesReturned to an Integer again? I need to copy
pBytesReturned bytes in ppBuffer to another string, and return that info to
the calling procedure, since I need to free the memory afterwards by calling
WTSFreeMemory (according to the SDK). If I free the memory before I copy it,
the string would be gone.

Aaah, I see you already realize what I said above :). Look above for
the answer (well, at least it should be pretty close to the answer :)
I was thinking of this :

Public Function GetTSProfilePath(ByVal Domainname As String, ByVal
TargetUserobject As String, ByRef TSProfilePath As String) As Boolean
Dim Retval As Integer
Dim Howmany As IntPtr
Dim Buffer As String = String.Empty 'Empty string to hold the info
Retval = WTSQueryUserConfig(GetDsDCName(Domainname), TargetUserobject,
WTSUserConfigTerminalServerProfilePath, Buffer, Howmany)
If Retval <> 0 Then
TSProfilePath = Buffer 'Since it is already a string, I guess this
would be possible.
WTSFreeMemory(Buffer)
GetTSProfilePath = True
Else
GetTSProfilePath = False
End If
End Function

And again, I get the same error !!! This is driving me crazy... I was pretty
good at pascal programming, even pointers, but .NET is driving me crazy :~
btw, I'm getting the error at the function call (retval = WTSQuer...)

But I wanna learn, and I can't find all the info I need in the MSDN
library... Got any good books about this???

Hmm, no I don't really have any good books. Every thing I've learned
about P/Invoke I've learned from the docs, experimenting, and reading
the *.framework.interop group - especially the posts that comes from one
particular master - Mattias Sojorn (sp?)...

Acutally, I just did a quick search for P/Invoke on Google - and there
looks like quite a few good articles on the subject.... Though, there
seems to be more related to C# then VB.NET - the concepts are the same.
So, Tom, if you want, can you help me again? You don't need to give me the
code, but explain to me WHY it doesn't work. The hints you gave me last time
have made me see a couple of other things in my code that were wrong. (of
course, if you wanna offer the code, who am I to say no :)


Aaah, the old give a man a fish thing :) Well, I did give some code -
but it probably won't work straight off. But it should give you a push
in the right direction...

--
Tom Shelton
MVP [Visual Basic]
Nov 20 '05 #5

P: n/a
Hi,

I got my little program to work !! It is updating Terminal Server profile
paths nicely, even does it for an entire group!!

It was actually my very first program in VB.NET, and the first program I
wrote that makes use of API calls and directoryservices...

Me happy camper :)

Thanks to you guys (especially Tom - I owe you one... If you ever come visit
Belgium, I'll buy you a Belgian Beer)

I learned a lot about vb.net and directory service calls. I am going to
update it with more functionality later, but for now it does what it is
supposed to do.

If anyone wants a small program (the exe is 100KB in size) that can browse
an AD, and change TS Profile paths for one user, or for multiple users by
selecting a group, mail me, and I'll send it to you. Now it's still free :p
Mind you, I'm sure there are still a few bugs in it which I haven't found
yet. The error detection could be a little better too, but that is for
version 0.0.0.2 :)

Thanks, and happy coding !

Kurt.
Nov 20 '05 #6

This discussion thread is closed

Replies have been disabled for this discussion.